From 95866a13ba4e1c235373834460aa284b763fe0d9 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 23 Jun 2025 09:03:29 +0000 Subject: (최겸) 기술영업 RFQ 개발(0620 요구사항, 첨부파일, REV 등) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tech-sales-quotation-attachments-sheet.tsx | 231 +++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 lib/techsales-rfq/table/tech-sales-quotation-attachments-sheet.tsx (limited to 'lib/techsales-rfq/table/tech-sales-quotation-attachments-sheet.tsx') diff --git a/lib/techsales-rfq/table/tech-sales-quotation-attachments-sheet.tsx b/lib/techsales-rfq/table/tech-sales-quotation-attachments-sheet.tsx new file mode 100644 index 00000000..21c61773 --- /dev/null +++ b/lib/techsales-rfq/table/tech-sales-quotation-attachments-sheet.tsx @@ -0,0 +1,231 @@ +"use client" + +import * as React from "react" +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetDescription, +} from "@/components/ui/sheet" +import { Button } from "@/components/ui/button" +import { Download, FileText, File, ImageIcon, AlertCircle } from "lucide-react" +import { Badge } from "@/components/ui/badge" +import { Separator } from "@/components/ui/separator" +import { formatDate } from "@/lib/utils" +import prettyBytes from "pretty-bytes" + +// 견적서 첨부파일 타입 정의 +export interface QuotationAttachment { + id: number + quotationId: number + revisionId: number + fileName: string + originalFileName: string + fileSize: number + fileType: string | null + filePath: string + description: string | null + uploadedBy: number + vendorId: number + isVendorUpload: boolean + createdAt: Date + updatedAt: Date +} + +// 견적서 정보 타입 +interface QuotationInfo { + id: number + quotationCode: string | null + vendorName?: string + rfqCode?: string +} + +interface TechSalesQuotationAttachmentsSheetProps + extends React.ComponentPropsWithRef { + quotation: QuotationInfo | null + attachments: QuotationAttachment[] + isLoading?: boolean +} + +export function TechSalesQuotationAttachmentsSheet({ + quotation, + attachments, + isLoading = false, + ...props +}: TechSalesQuotationAttachmentsSheetProps) { + + // 파일 아이콘 선택 함수 + const getFileIcon = (fileName: string) => { + const ext = fileName.split('.').pop()?.toLowerCase(); + if (!ext) return ; + + // 이미지 파일 + if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp'].includes(ext)) { + return ; + } + // PDF 파일 + if (ext === 'pdf') { + return ; + } + // Excel 파일 + if (['xlsx', 'xls', 'csv'].includes(ext)) { + return ; + } + // Word 파일 + if (['docx', 'doc'].includes(ext)) { + return ; + } + // 기본 파일 + return ; + }; + + // 파일 다운로드 처리 + const handleDownload = (attachment: QuotationAttachment) => { + const link = document.createElement('a'); + link.href = attachment.filePath; + link.download = attachment.originalFileName || attachment.fileName; + link.target = '_blank'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + // 리비전별로 첨부파일 그룹핑 + const groupedAttachments = React.useMemo(() => { + const groups = new Map(); + + attachments.forEach(attachment => { + const revisionId = attachment.revisionId; + if (!groups.has(revisionId)) { + groups.set(revisionId, []); + } + groups.get(revisionId)!.push(attachment); + }); + + // 리비전 ID 기준 내림차순 정렬 (최신 버전이 위에) + return Array.from(groups.entries()) + .sort(([a], [b]) => b - a) + .map(([revisionId, files]) => ({ + revisionId, + files: files.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) + })); + }, [attachments]); + return ( + + + + 견적서 첨부파일 + +
+
견적서: {quotation?.quotationCode || "N/A"}
+ {quotation?.vendorName && ( +
벤더: {quotation.vendorName}
+ )} + {quotation?.rfqCode && ( +
RFQ: {quotation.rfqCode}
+ )} +
+
+
+ +
+ {isLoading ? ( +
+
+
+

첨부파일 로딩 중...

+
+
+ ) : attachments.length === 0 ? ( +
+ +

첨부파일이 없습니다

+

+ 이 견적서에는 첨부된 파일이 없습니다. +

+
+ ) : ( +
+
+
+ 첨부파일 ({attachments.length}개) +
+
+ + {groupedAttachments.map((group, groupIndex) => ( +
+ {/* 리비전 헤더 */} +
+ + {group.revisionId === 0 ? "초기 버전" : `버전 ${group.revisionId}`} + + + ({group.files.length}개 파일) + +
+ + {/* 해당 리비전의 첨부파일들 */} + {group.files.map((attachment) => ( +
+
+ {getFileIcon(attachment.fileName)} +
+ +
+
+
+

+ {attachment.originalFileName || attachment.fileName} +

+
+ + {prettyBytes(attachment.fileSize)} + + + {attachment.isVendorUpload ? "벤더 업로드" : "시스템"} + +
+

+ {formatDate(attachment.createdAt)} +

+ {attachment.description && ( +

+ {attachment.description} +

+ )} +
+
+
+ +
+ {/* 다운로드 버튼 */} + +
+
+ ))} + + {/* 그룹 간 구분선 (마지막 그룹 제외) */} + {groupIndex < groupedAttachments.length - 1 && ( + + )} +
+ ))} +
+ )} +
+ + + ) +} \ No newline at end of file -- cgit v1.2.3