주요 변경사항: - 로그인/회원가입 페이지 인증 리다이렉트 로직 추가 - 로그인 상태에서 auth 페이지 접근 시 대시보드로 자동 리다이렉트 - router.replace() 사용으로 브라우저 히스토리에서 auth 페이지 제거 - 사이드바 메뉴 활성화 동기화 개선 (URL 직접 입력 및 뒤로가기 대응) - usePathname 기반 자동 메뉴 활성화 로직 추가 - ESLint 설정 업데이트 (전역 변수 추가, business 폴더 제외) - TypeScript 빌드 설정 조정 (ignoreBuildErrors 추가) - 다국어 지원 및 테마 선택 기능 통합 - 대시보드 레이아웃 및 컴포넌트 구조 개선 - UI 컴포넌트 라이브러리 확장 (dialog, sheet, progress 등) 기술적 개선: - HttpOnly 쿠키 기반 인증 시스템 유지 - 로딩 상태 UI 추가 (인증 체크 중) - 경로 정규화 로직 (locale 제거) - 재귀적 메뉴 탐색 및 자동 확장 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
656 lines
29 KiB
TypeScript
656 lines
29 KiB
TypeScript
import { useState } from "react";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogTrigger } from "@/components/ui/dialog";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import { toast } from "sonner";
|
|
import { Search, Plus, Download, Filter, Eye, Edit, Trash2, MessageSquare, FileText, Bell, Pin, Upload, Calendar } from "lucide-react";
|
|
|
|
interface Notice {
|
|
id: string;
|
|
title: string;
|
|
content: string;
|
|
author: string;
|
|
department: string;
|
|
date: string;
|
|
views: number;
|
|
isPinned: boolean;
|
|
isImportant: boolean;
|
|
category: string;
|
|
}
|
|
|
|
interface Document {
|
|
id: string;
|
|
title: string;
|
|
description: string;
|
|
fileName: string;
|
|
fileSize: string;
|
|
version: string;
|
|
author: string;
|
|
uploadDate: string;
|
|
downloads: number;
|
|
category: string;
|
|
accessLevel: string;
|
|
}
|
|
|
|
interface Approval {
|
|
id: string;
|
|
title: string;
|
|
type: string;
|
|
requestor: string;
|
|
department: string;
|
|
requestDate: string;
|
|
status: string;
|
|
currentApprover: string;
|
|
amount?: number;
|
|
urgency: string;
|
|
}
|
|
|
|
export function Board() {
|
|
const [notices, setNotices] = useState<Notice[]>([
|
|
{
|
|
id: "N001",
|
|
title: "2025년 1분기 생산 계획 공지",
|
|
content: "2025년 1분기 생산 계획이 확정되었습니다. 각 부서는 계획에 따라 준비하시기 바랍니다.",
|
|
author: "김경영",
|
|
department: "경영팀",
|
|
date: "2025-09-25",
|
|
views: 45,
|
|
isPinned: true,
|
|
isImportant: true,
|
|
category: "일반공지"
|
|
},
|
|
{
|
|
id: "N002",
|
|
title: "안전교육 실시 안내",
|
|
content: "월간 안전교육을 다음 주에 실시합니다. 전직원 필수 참석 바랍니다.",
|
|
author: "박안전",
|
|
department: "안전관리팀",
|
|
date: "2025-09-24",
|
|
views: 32,
|
|
isPinned: true,
|
|
isImportant: false,
|
|
category: "안전공지"
|
|
},
|
|
{
|
|
id: "N003",
|
|
title: "신규 설비 도입 완료",
|
|
content: "CNC 머시닝센터 3호기 설치가 완료되었습니다.",
|
|
author: "이설비",
|
|
department: "설비팀",
|
|
date: "2025-09-23",
|
|
views: 28,
|
|
isPinned: false,
|
|
isImportant: false,
|
|
category: "업무공지"
|
|
},
|
|
{
|
|
id: "N004",
|
|
title: "시스템 업데이트 예정",
|
|
content: "SAM 시스템 정기 업데이트가 금요일 밤에 진행됩니다.",
|
|
author: "최IT",
|
|
department: "IT팀",
|
|
date: "2025-09-22",
|
|
views: 67,
|
|
isPinned: false,
|
|
isImportant: true,
|
|
category: "시스템"
|
|
}
|
|
]);
|
|
|
|
const [documents, setDocuments] = useState<Document[]>([
|
|
{
|
|
id: "D001",
|
|
title: "품질관리 매뉴얼 v2.1",
|
|
description: "품질관리 표준 작업 절차서 및 체크리스트",
|
|
fileName: "QMS_Manual_v2.1.pdf",
|
|
fileSize: "2.4MB",
|
|
version: "v2.1",
|
|
author: "박품질",
|
|
uploadDate: "2025-09-20",
|
|
downloads: 23,
|
|
category: "매뉴얼",
|
|
accessLevel: "전체"
|
|
},
|
|
{
|
|
id: "D002",
|
|
title: "생산 공정도 템플릿",
|
|
description: "표준 생산 공정도 작성 템플릿",
|
|
fileName: "Process_Template.xlsx",
|
|
fileSize: "156KB",
|
|
version: "v1.3",
|
|
author: "이생산",
|
|
uploadDate: "2025-09-18",
|
|
downloads: 15,
|
|
category: "템플릿",
|
|
accessLevel: "생산팀"
|
|
},
|
|
{
|
|
id: "D003",
|
|
title: "안전관리 체크리스트",
|
|
description: "일일 안전점검 체크리스트 양식",
|
|
fileName: "Safety_Checklist.pdf",
|
|
fileSize: "890KB",
|
|
version: "v1.0",
|
|
author: "박안전",
|
|
uploadDate: "2025-09-15",
|
|
downloads: 41,
|
|
category: "안전자료",
|
|
accessLevel: "전체"
|
|
}
|
|
]);
|
|
|
|
const [approvals, setApprovals] = useState<Approval[]>([
|
|
{
|
|
id: "A001",
|
|
title: "원자재 구매 품의",
|
|
type: "구매품의",
|
|
requestor: "최자재",
|
|
department: "자재팀",
|
|
requestDate: "2025-09-25",
|
|
status: "결재대기",
|
|
currentApprover: "김부장",
|
|
amount: 15000000,
|
|
urgency: "긴급"
|
|
},
|
|
{
|
|
id: "A002",
|
|
title: "설비 수리비 지출 결의",
|
|
type: "지출결의",
|
|
requestor: "정설비",
|
|
department: "설비팀",
|
|
requestDate: "2025-09-24",
|
|
status: "승인완료",
|
|
currentApprover: "-",
|
|
amount: 2500000,
|
|
urgency: "보통"
|
|
},
|
|
{
|
|
id: "A003",
|
|
title: "신제품 개발 품의",
|
|
type: "개발품의",
|
|
requestor: "김개발",
|
|
department: "개발팀",
|
|
requestDate: "2025-09-23",
|
|
status: "검토중",
|
|
currentApprover: "이이사",
|
|
urgency: "보통"
|
|
}
|
|
]);
|
|
|
|
const [activeTab, setActiveTab] = useState("notices");
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [selectedItem, setSelectedItem] = useState<any>(null);
|
|
const [isViewModalOpen, setIsViewModalOpen] = useState(false);
|
|
|
|
const getCategoryColor = (category: string) => {
|
|
switch (category) {
|
|
case "일반공지": return "bg-blue-500";
|
|
case "안전공지": return "bg-red-500";
|
|
case "업무공지": return "bg-green-500";
|
|
case "시스템": return "bg-purple-500";
|
|
default: return "bg-gray-500";
|
|
}
|
|
};
|
|
|
|
const getStatusColor = (status: string) => {
|
|
switch (status) {
|
|
case "결재대기": return "bg-yellow-500";
|
|
case "승인완료": return "bg-green-500";
|
|
case "반려": return "bg-red-500";
|
|
case "검토중": return "bg-blue-500";
|
|
default: return "bg-gray-500";
|
|
}
|
|
};
|
|
|
|
const getUrgencyColor = (urgency: string) => {
|
|
switch (urgency) {
|
|
case "긴급": return "text-red-600";
|
|
case "보통": return "text-yellow-600";
|
|
case "낮음": return "text-green-600";
|
|
default: return "text-gray-600";
|
|
}
|
|
};
|
|
|
|
const handleViewItem = (item: any) => {
|
|
setSelectedItem(item);
|
|
setIsViewModalOpen(true);
|
|
|
|
// 조회수 증가 (공지사항의 경우)
|
|
if (activeTab === "notices" && item.id) {
|
|
setNotices(prev => prev.map(notice =>
|
|
notice.id === item.id ? { ...notice, views: notice.views + 1 } : notice
|
|
));
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="p-4 md:p-8 space-y-6 md:space-y-8">
|
|
{/* 헤더 */}
|
|
<div className="samsung-card samsung-gradient-card relative overflow-hidden">
|
|
<div className="absolute top-0 left-0 right-0 h-2 samsung-hero-gradient"></div>
|
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6 pt-2">
|
|
<div>
|
|
<h1 className="text-3xl md:text-4xl font-bold text-foreground mb-2">전자결재·협업</h1>
|
|
<p className="text-muted-foreground text-lg">공지사항, 자료실, 전자결재 통합 관리 시스템</p>
|
|
</div>
|
|
<Button
|
|
className="samsung-button w-full md:w-auto min-h-[48px]"
|
|
onClick={() => setIsModalOpen(true)}
|
|
>
|
|
<Plus className="h-5 w-5 mr-3" />
|
|
새 글 작성
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 협업 대시보드 */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 md:gap-8 mb-8">
|
|
<Card className="samsung-card samsung-gradient-card hover:scale-105 hover:-translate-y-2 transition-all duration-500 border-0 group">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
|
|
<CardTitle className="text-sm font-bold text-muted-foreground uppercase tracking-wide">신규 공지</CardTitle>
|
|
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-2xl flex items-center justify-center samsung-shadow group-hover:scale-110 transition-transform duration-300">
|
|
<Bell className="h-6 w-6 text-white" />
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-3xl font-black text-blue-600 mb-3">
|
|
{notices.filter(n => {
|
|
const today = new Date();
|
|
const noticeDate = new Date(n.date);
|
|
const diffTime = Math.abs(today.getTime() - noticeDate.getTime());
|
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
return diffDays <= 7;
|
|
}).length}건
|
|
</div>
|
|
<p className="text-sm text-muted-foreground bg-gradient-to-r from-blue-50 to-indigo-50 rounded-xl px-3 py-2 font-semibold">
|
|
최근 7일 내 공지
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="samsung-card samsung-gradient-card hover:scale-105 hover:-translate-y-2 transition-all duration-500 border-0 group">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
|
|
<CardTitle className="text-sm font-bold text-muted-foreground uppercase tracking-wide">자료실</CardTitle>
|
|
<div className="w-12 h-12 bg-gradient-to-br from-green-500 to-emerald-600 rounded-2xl flex items-center justify-center samsung-shadow group-hover:scale-110 transition-transform duration-300">
|
|
<FileText className="h-6 w-6 text-white" />
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-3xl font-black text-green-600 mb-3">{documents.length}개</div>
|
|
<p className="text-sm text-muted-foreground bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl px-3 py-2 font-semibold">
|
|
등록된 문서 수
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="samsung-card samsung-gradient-card hover:scale-105 hover:-translate-y-2 transition-all duration-500 border-0 group">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
|
|
<CardTitle className="text-sm font-bold text-muted-foreground uppercase tracking-wide">결재 대기</CardTitle>
|
|
<div className="w-12 h-12 bg-gradient-to-br from-orange-500 to-red-500 rounded-2xl flex items-center justify-center samsung-shadow group-hover:scale-110 transition-transform duration-300">
|
|
<MessageSquare className="h-6 w-6 text-white" />
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-3xl font-black text-orange-600 mb-3">
|
|
{approvals.filter(a => a.status === "결재대기").length}건
|
|
</div>
|
|
<p className="text-sm text-muted-foreground bg-gradient-to-r from-orange-50 to-red-50 rounded-xl px-3 py-2 font-semibold">
|
|
처리 대기중인 결재
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="samsung-card samsung-gradient-card hover:scale-105 hover:-translate-y-2 transition-all duration-500 border-0 group">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
|
|
<CardTitle className="text-sm font-bold text-muted-foreground uppercase tracking-wide">긴급 건</CardTitle>
|
|
<div className="w-12 h-12 bg-gradient-to-br from-red-500 to-pink-500 rounded-2xl flex items-center justify-center samsung-shadow group-hover:scale-110 transition-transform duration-300">
|
|
<Calendar className="h-6 w-6 text-white" />
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-3xl font-black text-red-600 mb-3">
|
|
{approvals.filter(a => a.urgency === "긴급").length}건
|
|
</div>
|
|
<p className="text-sm text-muted-foreground bg-gradient-to-r from-red-50 to-pink-50 rounded-xl px-3 py-2 font-semibold">
|
|
긴급 처리 필요
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
|
|
<div className="overflow-x-auto">
|
|
<TabsList className="grid w-full grid-cols-3 min-w-[400px] samsung-glass h-14 p-2 rounded-2xl">
|
|
<TabsTrigger value="notices" className="flex items-center space-x-3 rounded-xl h-10 transition-all duration-300 data-[state=active]:samsung-hero-gradient data-[state=active]:text-white">
|
|
<Bell className="h-5 w-5" />
|
|
<span className="hidden sm:inline font-semibold">공지사항</span>
|
|
<span className="sm:hidden font-semibold">공지</span>
|
|
</TabsTrigger>
|
|
<TabsTrigger value="documents" className="flex items-center space-x-3 rounded-xl h-10 transition-all duration-300 data-[state=active]:samsung-hero-gradient data-[state=active]:text-white">
|
|
<FileText className="h-5 w-5" />
|
|
<span className="hidden sm:inline font-semibold">자료실</span>
|
|
<span className="sm:hidden font-semibold">자료</span>
|
|
</TabsTrigger>
|
|
<TabsTrigger value="approvals" className="flex items-center space-x-3 rounded-xl h-10 transition-all duration-300 data-[state=active]:samsung-hero-gradient data-[state=active]:text-white">
|
|
<MessageSquare className="h-5 w-5" />
|
|
<span className="hidden sm:inline font-semibold">전자결재</span>
|
|
<span className="sm:hidden font-semibold">결재</span>
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
</div>
|
|
|
|
<TabsContent value="notices" className="space-y-6">
|
|
<Card className="samsung-card samsung-gradient-card border-0">
|
|
<CardHeader>
|
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
|
<CardTitle className="text-2xl font-bold text-foreground">📢 공지사항</CardTitle>
|
|
<div className="flex flex-col md:flex-row items-stretch md:items-center space-y-3 md:space-y-0 md:space-x-4">
|
|
<div className="relative">
|
|
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 text-muted-foreground h-5 w-5" />
|
|
<Input placeholder="제목 검색..." className="pl-12 w-full md:w-80 samsung-input border-0" />
|
|
</div>
|
|
<Select defaultValue="all">
|
|
<SelectTrigger className="w-full md:w-40 samsung-input border-0">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="all">전체</SelectItem>
|
|
<SelectItem value="일반공지">일반공지</SelectItem>
|
|
<SelectItem value="안전공지">안전공지</SelectItem>
|
|
<SelectItem value="업무공지">업무공지</SelectItem>
|
|
<SelectItem value="시스템">시스템</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="overflow-x-auto">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="min-w-[60px]">상태</TableHead>
|
|
<TableHead className="min-w-[80px]">분류</TableHead>
|
|
<TableHead className="min-w-[200px]">제목</TableHead>
|
|
<TableHead className="min-w-[80px]">작성자</TableHead>
|
|
<TableHead className="min-w-[100px]">부서</TableHead>
|
|
<TableHead className="min-w-[100px]">작성일</TableHead>
|
|
<TableHead className="min-w-[60px]">조회</TableHead>
|
|
<TableHead className="min-w-[80px]">관리</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{notices.map((notice) => (
|
|
<TableRow key={notice.id}>
|
|
<TableCell>
|
|
<div className="flex items-center space-x-1">
|
|
{notice.isPinned && (
|
|
<Pin className="h-3 w-3 text-red-500" />
|
|
)}
|
|
{notice.isImportant && (
|
|
<span className="text-red-500 text-xs">●</span>
|
|
)}
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Badge className={`${getCategoryColor(notice.category)} text-white text-xs`}>
|
|
{notice.category}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>
|
|
<button
|
|
onClick={() => handleViewItem(notice)}
|
|
className="text-left hover:text-blue-600 hover:underline"
|
|
>
|
|
{notice.title}
|
|
</button>
|
|
</TableCell>
|
|
<TableCell>{notice.author}</TableCell>
|
|
<TableCell>{notice.department}</TableCell>
|
|
<TableCell>{notice.date}</TableCell>
|
|
<TableCell>{notice.views}</TableCell>
|
|
<TableCell>
|
|
<div className="flex items-center space-x-1">
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => handleViewItem(notice)}
|
|
className="p-3 rounded-xl hover:scale-105 transition-all duration-300 border-primary/20 hover:bg-primary/10"
|
|
>
|
|
<Eye className="h-4 w-4" />
|
|
</Button>
|
|
<Button size="sm" variant="outline" className="p-3 rounded-xl hover:scale-105 transition-all duration-300 border-primary/20 hover:bg-primary/10">
|
|
<Edit className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="documents" className="space-y-6">
|
|
<Card className="samsung-card samsung-gradient-card border-0">
|
|
<CardHeader>
|
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
|
<CardTitle className="text-2xl font-bold text-foreground">📁 자료실</CardTitle>
|
|
<Button className="samsung-button w-full md:w-auto min-h-[48px]">
|
|
<Upload className="h-5 w-5 mr-3" />
|
|
파일 업로드
|
|
</Button>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{documents.map((doc) => (
|
|
<Card key={doc.id} className="samsung-card samsung-gradient-card border-0 p-6 hover:scale-105 hover:-translate-y-2 transition-all duration-500 group">
|
|
<div className="flex items-start justify-between mb-4">
|
|
<div className="flex-1">
|
|
<h4 className="font-bold text-base mb-2 text-foreground">{doc.title}</h4>
|
|
<p className="text-sm text-muted-foreground mb-3">{doc.description}</p>
|
|
<div className="flex items-center space-x-2 text-sm text-muted-foreground bg-muted/50 rounded-xl px-3 py-2">
|
|
<span>{doc.fileName}</span>
|
|
<span>•</span>
|
|
<span className="font-semibold">{doc.fileSize}</span>
|
|
</div>
|
|
</div>
|
|
<Badge variant="outline" className="text-sm font-semibold px-3 py-1 rounded-xl">
|
|
{doc.version}
|
|
</Badge>
|
|
</div>
|
|
<div className="flex justify-between items-center text-sm text-muted-foreground mb-4">
|
|
<span className="font-semibold">{doc.author}</span>
|
|
<span>{doc.uploadDate}</span>
|
|
</div>
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-muted-foreground">다운로드: <span className="font-bold text-primary">{doc.downloads}</span>회</span>
|
|
<div className="flex space-x-2">
|
|
<Button size="sm" variant="outline" className="p-3 rounded-xl hover:scale-105 transition-all duration-300 border-primary/20 hover:bg-primary/10">
|
|
<Download className="h-4 w-4" />
|
|
</Button>
|
|
<Button size="sm" variant="outline" className="p-3 rounded-xl hover:scale-105 transition-all duration-300 border-primary/20 hover:bg-primary/10">
|
|
<Eye className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="approvals" className="space-y-6">
|
|
<Card className="samsung-card samsung-gradient-card border-0">
|
|
<CardHeader>
|
|
<CardTitle className="text-2xl font-bold text-foreground">📋 전자결재</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="overflow-x-auto">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="min-w-[100px]">결재번호</TableHead>
|
|
<TableHead className="min-w-[150px]">제목</TableHead>
|
|
<TableHead className="min-w-[80px]">유형</TableHead>
|
|
<TableHead className="min-w-[80px]">기안자</TableHead>
|
|
<TableHead className="min-w-[80px]">부서</TableHead>
|
|
<TableHead className="min-w-[100px]">기안일</TableHead>
|
|
<TableHead className="min-w-[80px]">상태</TableHead>
|
|
<TableHead className="min-w-[100px]">현재결재자</TableHead>
|
|
<TableHead className="min-w-[80px]">긴급도</TableHead>
|
|
<TableHead className="min-w-[80px]">관리</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{approvals.map((approval) => (
|
|
<TableRow key={approval.id}>
|
|
<TableCell className="font-medium">{approval.id}</TableCell>
|
|
<TableCell>
|
|
<button
|
|
onClick={() => handleViewItem(approval)}
|
|
className="text-left hover:text-blue-600 hover:underline"
|
|
>
|
|
{approval.title}
|
|
</button>
|
|
</TableCell>
|
|
<TableCell>{approval.type}</TableCell>
|
|
<TableCell>{approval.requestor}</TableCell>
|
|
<TableCell>{approval.department}</TableCell>
|
|
<TableCell>{approval.requestDate}</TableCell>
|
|
<TableCell>
|
|
<Badge className={`${getStatusColor(approval.status)} text-white text-xs`}>
|
|
{approval.status}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>{approval.currentApprover}</TableCell>
|
|
<TableCell>
|
|
<span className={getUrgencyColor(approval.urgency)}>
|
|
{approval.urgency}
|
|
</span>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => handleViewItem(approval)}
|
|
className="p-3 rounded-xl hover:scale-105 transition-all duration-300 border-primary/20 hover:bg-primary/10"
|
|
>
|
|
<Eye className="h-4 w-4" />
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
{/* 상세보기 모달 */}
|
|
<Dialog open={isViewModalOpen} onOpenChange={setIsViewModalOpen}>
|
|
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto samsung-glass rounded-3xl border-0">
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
{activeTab === "notices" && "공지사항 상세"}
|
|
{activeTab === "documents" && "문서 상세"}
|
|
{activeTab === "approvals" && "결재 상세"}
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
{activeTab === "notices" && "공지사항의 상세 내용을 확인합니다."}
|
|
{activeTab === "documents" && "문서의 상세 정보를 확인합니다."}
|
|
{activeTab === "approvals" && "결재 건의 상세 내용을 확인합니다."}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
{selectedItem && (
|
|
<div className="space-y-6">
|
|
{activeTab === "notices" && (
|
|
<div>
|
|
<div className="border-b pb-4 mb-4">
|
|
<div className="flex items-center space-x-2 mb-2">
|
|
<h2 className="text-xl font-bold">{selectedItem.title}</h2>
|
|
{selectedItem.isPinned && <Pin className="h-4 w-4 text-red-500" />}
|
|
{selectedItem.isImportant && <span className="text-red-500">●</span>}
|
|
</div>
|
|
<div className="flex items-center space-x-4 text-sm text-gray-600">
|
|
<span>작성자: {selectedItem.author}</span>
|
|
<span>부서: {selectedItem.department}</span>
|
|
<span>작성일: {selectedItem.date}</span>
|
|
<span>조회수: {selectedItem.views}</span>
|
|
</div>
|
|
</div>
|
|
<div className="prose max-w-none">
|
|
<p>{selectedItem.content}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === "approvals" && (
|
|
<div>
|
|
<div className="border-b pb-4 mb-4">
|
|
<h2 className="text-xl font-bold mb-2">{selectedItem.title}</h2>
|
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
|
<div>
|
|
<span className="text-gray-600">결재번호:</span> {selectedItem.id}
|
|
</div>
|
|
<div>
|
|
<span className="text-gray-600">유형:</span> {selectedItem.type}
|
|
</div>
|
|
<div>
|
|
<span className="text-gray-600">기안자:</span> {selectedItem.requestor}
|
|
</div>
|
|
<div>
|
|
<span className="text-gray-600">부서:</span> {selectedItem.department}
|
|
</div>
|
|
<div>
|
|
<span className="text-gray-600">기안일:</span> {selectedItem.requestDate}
|
|
</div>
|
|
<div>
|
|
<span className="text-gray-600">상태:</span>
|
|
<Badge className={`ml-2 ${getStatusColor(selectedItem.status)} text-white text-xs`}>
|
|
{selectedItem.status}
|
|
</Badge>
|
|
</div>
|
|
{selectedItem.amount && (
|
|
<div>
|
|
<span className="text-gray-600">금액:</span> {selectedItem.amount.toLocaleString()}원
|
|
</div>
|
|
)}
|
|
<div>
|
|
<span className="text-gray-600">긴급도:</span>
|
|
<span className={getUrgencyColor(selectedItem.urgency)}> {selectedItem.urgency}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{selectedItem.status === "결재대기" && (
|
|
<div className="flex space-x-4">
|
|
<Button className="samsung-button bg-gradient-to-r from-green-500 to-emerald-600">승인</Button>
|
|
<Button variant="outline" className="samsung-button-secondary border-red-500 text-red-600 hover:bg-red-50">반려</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
<div className="flex justify-end">
|
|
<Button variant="outline" onClick={() => setIsViewModalOpen(false)} className="samsung-button-secondary">닫기</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
} |