243 lines
8.4 KiB
TypeScript
243 lines
8.4 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState } from "react";
|
|
import { FileText, Download, Calendar, Folder, Search } from "lucide-react";
|
|
|
|
interface Document {
|
|
id: string;
|
|
name: string;
|
|
category: "계약서" | "보험" | "세금계산서" | "기타";
|
|
size: string;
|
|
uploadDate: string;
|
|
url: string;
|
|
description?: string;
|
|
}
|
|
|
|
// 목 데이터
|
|
const mockDocuments: Document[] = [
|
|
{
|
|
id: "1",
|
|
name: "2025년 1월 세금계산서.pdf",
|
|
category: "세금계산서",
|
|
size: "1.2 MB",
|
|
uploadDate: "2025-01-05",
|
|
url: "/documents/tax-invoice-202501.pdf",
|
|
description: "1월 매출 세금계산서",
|
|
},
|
|
{
|
|
id: "2",
|
|
name: "차량보험증권_서울12가3456.pdf",
|
|
category: "보험",
|
|
size: "856 KB",
|
|
uploadDate: "2024-12-20",
|
|
url: "/documents/insurance-vehicle-1.pdf",
|
|
description: "1톤 트럭 종합보험",
|
|
},
|
|
{
|
|
id: "3",
|
|
name: "운송계약서_ABC물류.pdf",
|
|
category: "계약서",
|
|
size: "2.4 MB",
|
|
uploadDate: "2024-12-15",
|
|
url: "/documents/contract-abc-logistics.pdf",
|
|
description: "ABC물류 연간 운송 계약",
|
|
},
|
|
{
|
|
id: "4",
|
|
name: "2024년 12월 세금계산서.pdf",
|
|
category: "세금계산서",
|
|
size: "1.1 MB",
|
|
uploadDate: "2024-12-05",
|
|
url: "/documents/tax-invoice-202412.pdf",
|
|
},
|
|
{
|
|
id: "5",
|
|
name: "화물배상책임보험증권.pdf",
|
|
category: "보험",
|
|
size: "720 KB",
|
|
uploadDate: "2024-11-30",
|
|
url: "/documents/cargo-insurance.pdf",
|
|
description: "화물 배상책임보험",
|
|
},
|
|
{
|
|
id: "6",
|
|
name: "차고지 임대계약서.pdf",
|
|
category: "계약서",
|
|
size: "1.8 MB",
|
|
uploadDate: "2024-11-15",
|
|
url: "/documents/garage-lease-contract.pdf",
|
|
},
|
|
];
|
|
|
|
export default function DocumentWidget() {
|
|
const [documents] = useState<Document[]>(mockDocuments);
|
|
const [filter, setFilter] = useState<"all" | Document["category"]>("all");
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
|
|
const filteredDocuments = documents.filter((doc) => {
|
|
const matchesFilter = filter === "all" || doc.category === filter;
|
|
const matchesSearch =
|
|
searchTerm === "" ||
|
|
doc.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
doc.description?.toLowerCase().includes(searchTerm.toLowerCase());
|
|
return matchesFilter && matchesSearch;
|
|
});
|
|
|
|
const getCategoryIcon = (category: Document["category"]) => {
|
|
switch (category) {
|
|
case "계약서":
|
|
return "📄";
|
|
case "보험":
|
|
return "🛡️";
|
|
case "세금계산서":
|
|
return "💰";
|
|
case "기타":
|
|
return "📁";
|
|
}
|
|
};
|
|
|
|
const getCategoryColor = (category: Document["category"]) => {
|
|
switch (category) {
|
|
case "계약서":
|
|
return "bg-blue-100 text-blue-700";
|
|
case "보험":
|
|
return "bg-green-100 text-green-700";
|
|
case "세금계산서":
|
|
return "bg-amber-100 text-amber-700";
|
|
case "기타":
|
|
return "bg-gray-100 text-gray-700";
|
|
}
|
|
};
|
|
|
|
const handleDownload = (doc: Document) => {
|
|
// 실제로는 백엔드 API 호출
|
|
alert(`다운로드: ${doc.name}\n(실제 구현 시 파일 다운로드 처리)`);
|
|
};
|
|
|
|
const stats = {
|
|
total: documents.length,
|
|
contract: documents.filter((d) => d.category === "계약서").length,
|
|
insurance: documents.filter((d) => d.category === "보험").length,
|
|
tax: documents.filter((d) => d.category === "세금계산서").length,
|
|
};
|
|
|
|
return (
|
|
<div className="flex h-full flex-col bg-gradient-to-br from-slate-50 to-blue-50">
|
|
{/* 헤더 */}
|
|
<div className="border-b border-gray-200 bg-white px-4 py-3">
|
|
<div className="mb-3 flex items-center justify-between">
|
|
<h3 className="text-lg font-bold text-gray-800">📂 문서 관리</h3>
|
|
<button className="rounded-lg bg-primary px-3 py-1.5 text-sm text-white transition-colors hover:bg-primary/90">
|
|
+ 업로드
|
|
</button>
|
|
</div>
|
|
|
|
{/* 통계 */}
|
|
<div className="mb-3 grid grid-cols-4 gap-2 text-xs">
|
|
<div className="rounded bg-gray-50 px-2 py-1.5 text-center">
|
|
<div className="font-bold text-gray-700">{stats.total}</div>
|
|
<div className="text-gray-600">전체</div>
|
|
</div>
|
|
<div className="rounded bg-blue-50 px-2 py-1.5 text-center">
|
|
<div className="font-bold text-blue-700">{stats.contract}</div>
|
|
<div className="text-blue-600">계약서</div>
|
|
</div>
|
|
<div className="rounded bg-green-50 px-2 py-1.5 text-center">
|
|
<div className="font-bold text-green-700">{stats.insurance}</div>
|
|
<div className="text-green-600">보험</div>
|
|
</div>
|
|
<div className="rounded bg-amber-50 px-2 py-1.5 text-center">
|
|
<div className="font-bold text-amber-700">{stats.tax}</div>
|
|
<div className="text-amber-600">계산서</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 검색 */}
|
|
<div className="mb-3 relative">
|
|
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
|
|
<input
|
|
type="text"
|
|
placeholder="문서명 검색..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
className="w-full rounded border border-gray-300 py-2 pl-10 pr-3 text-sm focus:border-primary focus:outline-none"
|
|
/>
|
|
</div>
|
|
|
|
{/* 필터 */}
|
|
<div className="flex gap-2">
|
|
{(["all", "계약서", "보험", "세금계산서", "기타"] as const).map((f) => (
|
|
<button
|
|
key={f}
|
|
onClick={() => setFilter(f)}
|
|
className={`rounded px-3 py-1 text-xs font-medium transition-colors ${
|
|
filter === f ? "bg-primary text-white" : "bg-gray-100 text-gray-600 hover:bg-gray-200"
|
|
}`}
|
|
>
|
|
{f === "all" ? "전체" : f}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 문서 리스트 */}
|
|
<div className="flex-1 overflow-y-auto p-4">
|
|
{filteredDocuments.length === 0 ? (
|
|
<div className="flex h-full items-center justify-center text-gray-400">
|
|
<div className="text-center">
|
|
<div className="mb-2 text-4xl">📭</div>
|
|
<div>문서가 없습니다</div>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{filteredDocuments.map((doc) => (
|
|
<div
|
|
key={doc.id}
|
|
className="group flex items-center gap-3 rounded-lg border border-gray-200 bg-white p-3 shadow-sm transition-all hover:border-primary hover:shadow-md"
|
|
>
|
|
{/* 아이콘 */}
|
|
<div className="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-lg bg-gray-50 text-2xl">
|
|
{getCategoryIcon(doc.category)}
|
|
</div>
|
|
|
|
{/* 정보 */}
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-start justify-between gap-2">
|
|
<div className="flex-1 min-w-0">
|
|
<div className="truncate font-medium text-gray-800">{doc.name}</div>
|
|
{doc.description && (
|
|
<div className="mt-0.5 truncate text-xs text-gray-600">{doc.description}</div>
|
|
)}
|
|
<div className="mt-1 flex items-center gap-3 text-xs text-gray-500">
|
|
<span className={`rounded px-2 py-0.5 ${getCategoryColor(doc.category)}`}>
|
|
{doc.category}
|
|
</span>
|
|
<span className="flex items-center gap-1">
|
|
<Calendar className="h-3 w-3" />
|
|
{new Date(doc.uploadDate).toLocaleDateString()}
|
|
</span>
|
|
<span>{doc.size}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 다운로드 버튼 */}
|
|
<button
|
|
onClick={() => handleDownload(doc)}
|
|
className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-primary text-white transition-colors hover:bg-primary/90"
|
|
title="다운로드"
|
|
>
|
|
<Download className="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|