From 688d9884ca98b50d04ac78fc1f6e28e034a519c0 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 3 Sep 2025 12:44:32 +0000 Subject: (대표님) rfq-last 작업, vendorDocu 스키마 변경, 벤더 문서 관련 변경 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/rfq-last/table/rfq-attachments-dialog.tsx | 351 ++++++++++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 lib/rfq-last/table/rfq-attachments-dialog.tsx (limited to 'lib/rfq-last/table/rfq-attachments-dialog.tsx') diff --git a/lib/rfq-last/table/rfq-attachments-dialog.tsx b/lib/rfq-last/table/rfq-attachments-dialog.tsx new file mode 100644 index 00000000..253daaa2 --- /dev/null +++ b/lib/rfq-last/table/rfq-attachments-dialog.tsx @@ -0,0 +1,351 @@ +"use client" + +import * as React from "react" +import { format } from "date-fns" +import { Download, FileText, Eye, ExternalLink, Loader2 } from "lucide-react" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { ScrollArea } from "@/components/ui/scroll-area" +import { Skeleton } from "@/components/ui/skeleton" +import { toast } from "sonner" +import { RfqsLastView } from "@/db/schema" +import { getRfqAttachmentsAction } from "../service" +import { downloadFile, quickPreview, smartFileAction, formatFileSize, getFileInfo } from "@/lib/file-download" + +// 첨부파일 타입 +interface RfqAttachment { + attachmentId: number + attachmentType: string + serialNo: string + description: string | null + currentRevision: string + fileName: string + originalFileName: string + filePath: string + fileSize: number | null + fileType: string | null + createdByName: string | null + createdAt: Date | null + updatedAt: Date | null + revisionComment?: string | null +} + +interface RfqAttachmentsDialogProps { + isOpen: boolean + onClose: () => void + rfqData: RfqsLastView +} + +export function RfqAttachmentsDialog({ isOpen, onClose, rfqData }: RfqAttachmentsDialogProps) { + const [attachments, setAttachments] = React.useState([]) + const [isLoading, setIsLoading] = React.useState(false) + const [downloadingFiles, setDownloadingFiles] = React.useState>(new Set()) + + // 첨부파일 목록 로드 + React.useEffect(() => { + if (!isOpen || !rfqData.id) return + + const loadAttachments = async () => { + setIsLoading(true) + try { + const result = await getRfqAttachmentsAction(rfqData.id) + + if (result.success) { + setAttachments(result.data) + } else { + toast.error(result.error || "첨부파일을 불러오는데 실패했습니다") + setAttachments([]) + } + } catch (error) { + console.error("첨부파일 로드 오류:", error) + toast.error("첨부파일을 불러오는데 실패했습니다") + setAttachments([]) + } finally { + setIsLoading(false) + } + } + + loadAttachments() + }, [isOpen, rfqData.id]) + + // 파일 다운로드 핸들러 + const handleDownload = async (attachment: RfqAttachment) => { + const attachmentId = attachment.attachmentId + setDownloadingFiles(prev => new Set([...prev, attachmentId])) + + try { + const result = await downloadFile( + attachment.filePath, + attachment.originalFileName, + { + action: 'download', + showToast: true, + showSuccessToast: true, + onSuccess: (fileName, fileSize) => { + console.log(`다운로드 완료: ${fileName} (${formatFileSize(fileSize || 0)})`) + }, + onError: (error) => { + console.error(`다운로드 실패: ${error}`) + } + } + ) + + if (!result.success) { + console.error("다운로드 결과:", result) + } + } catch (error) { + console.error("파일 다운로드 오류:", error) + toast.error("파일 다운로드에 실패했습니다") + } finally { + setDownloadingFiles(prev => { + const newSet = new Set(prev) + newSet.delete(attachmentId) + return newSet + }) + } + } + + // 파일 미리보기 핸들러 + const handlePreview = async (attachment: RfqAttachment) => { + const fileInfo = getFileInfo(attachment.originalFileName) + + if (!fileInfo.canPreview) { + toast.info("이 파일 형식은 미리보기를 지원하지 않습니다. 다운로드를 진행합니다.") + return handleDownload(attachment) + } + + try { + const result = await quickPreview(attachment.filePath, attachment.originalFileName) + + if (!result.success) { + console.error("미리보기 결과:", result) + } + } catch (error) { + console.error("파일 미리보기 오류:", error) + toast.error("파일 미리보기에 실패했습니다") + } + } + + // 스마트 파일 액션 (미리보기 가능하면 미리보기, 아니면 다운로드) + const handleSmartAction = async (attachment: RfqAttachment) => { + const attachmentId = attachment.attachmentId + const fileInfo = getFileInfo(attachment.originalFileName) + + if (fileInfo.canPreview) { + return handlePreview(attachment) + } else { + return handleDownload(attachment) + } + } + + // 첨부파일 타입별 색상 + const getAttachmentTypeBadgeVariant = (type: string) => { + switch (type.toLowerCase()) { + case "견적요청서": return "default" + case "기술사양서": return "secondary" + case "도면": return "outline" + default: return "outline" + } + } + + return ( + + + + 견적 첨부파일 + + {rfqData.rfqCode} - {rfqData.rfqTitle || rfqData.itemName || "견적"} + {attachments.length > 0 && ` (${attachments.length}개 파일)`} + + + + + {isLoading ? ( +
+ {[...Array(3)].map((_, i) => ( +
+ +
+ + +
+
+ + +
+
+ ))} +
+ ) : ( + + + + 타입 + 파일명 + 설명 + 리비전 + 크기 + 생성자 + 생성일 + 액션 + + + + {attachments.length === 0 ? ( + + +
+ + 첨부된 파일이 없습니다. +
+
+
+ ) : ( + attachments.map((attachment) => { + const fileInfo = getFileInfo(attachment.originalFileName) + const isDownloading = downloadingFiles.has(attachment.attachmentId) + + return ( + + + + {attachment.attachmentType} + + + +
+ {fileInfo.icon} +
+ + {attachment.originalFileName} + + {attachment.fileName !== attachment.originalFileName && ( + + {attachment.fileName} + + )} +
+
+
+ + + {attachment.description || "-"} + + {attachment.revisionComment && ( +
+ {attachment.revisionComment} +
+ )} +
+ + + {attachment.currentRevision} + + + + {attachment.fileSize ? formatFileSize(attachment.fileSize) : "-"} + + + {attachment.createdByName || "-"} + + + {attachment.createdAt ? format(new Date(attachment.createdAt), "MM-dd HH:mm") : "-"} + + +
+ {/* 미리보기 버튼 (미리보기 가능한 파일만) */} + {fileInfo.canPreview && ( + + )} + + {/* 다운로드 버튼 */} + + + {/* 스마트 액션 버튼 (메인 액션) */} + +
+
+
+ ) + }) + )} +
+
+ )} +
+ + {/* 하단 정보 */} + {attachments.length > 0 && !isLoading && ( +
+
+ + 총 {attachments.length}개 파일 + {attachments.some(a => a.fileSize) && + ` · 전체 크기: ${formatFileSize( + attachments.reduce((sum, a) => sum + (a.fileSize || 0), 0) + )}` + } + + + {fileInfo.icon} 미리보기 가능 | 📥 다운로드 + +
+
+ )} +
+
+ ) +} \ No newline at end of file -- cgit v1.2.3