투두리스트, 예약요청, 정비,문서
This commit is contained in:
242
frontend/components/dashboard/widgets/DocumentWidget.tsx
Normal file
242
frontend/components/dashboard/widgets/DocumentWidget.tsx
Normal file
@@ -0,0 +1,242 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user