From 969c25b56f6d29d7ffa4bc2ce04c5fb4e5846b34 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Thu, 14 Aug 2025 11:54:47 +0000 Subject: (대표님) 정규벤더등록, 벤더문서관리, 벤더데이터입력, 첨부파일관리 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user-vendor-document-table-container.tsx | 615 ++++++++++++++++----- 1 file changed, 466 insertions(+), 149 deletions(-) (limited to 'components/ship-vendor-document/user-vendor-document-table-container.tsx') diff --git a/components/ship-vendor-document/user-vendor-document-table-container.tsx b/components/ship-vendor-document/user-vendor-document-table-container.tsx index 4e133696..61d52c28 100644 --- a/components/ship-vendor-document/user-vendor-document-table-container.tsx +++ b/components/ship-vendor-document/user-vendor-document-table-container.tsx @@ -27,7 +27,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog" -import { Building, FileText, AlertCircle, Eye, Download, Loader2, Plus } from "lucide-react" +import { Building, FileText, AlertCircle, Eye, Download, Loader2, Plus, Trash2, Edit } from "lucide-react" import { SimplifiedDocumentsTable } from "@/lib/vendor-document-list/ship/enhanced-documents-table" import { getUserVendorDocuments, @@ -38,6 +38,7 @@ import { WebViewerInstance } from "@pdftron/webviewer" import { NewRevisionDialog } from "./new-revision-dialog" import { useRouter } from 'next/navigation' import { AddAttachmentDialog } from "./add-attachment-dialog" // ✅ import 추가 +import { EditRevisionDialog } from "./edit-revision-dialog" // ✅ 추가 /* ------------------------------------------------------------------------------------------------- * Types & Constants @@ -91,6 +92,7 @@ interface AttachmentInfo { revisionId: number fileName: string filePath: string + dolceFilePath: string | null fileSize: number | null fileType: string | null createdAt: Date @@ -129,7 +131,7 @@ export const DocumentSelectionContext = React.createContext = { 'Approval Submission Full': 'AS-F', @@ -143,18 +145,20 @@ function getUsageTypeDisplay(usageType: string | null): string { 'Reference Series Full': 'RS-F', 'Reference Series Partial': 'RS-P', } - + return abbreviations[usageType] || usageType } -function RevisionTable({ - revisions, +function RevisionTable({ + revisions, onViewRevision, - onNewRevision -}: { + onNewRevision, + onEditRevision, // ✅ 수정 함수 prop 추가 +}: { revisions: RevisionInfo[] onViewRevision: (revision: RevisionInfo) => void onNewRevision: () => void + onEditRevision: (revision: RevisionInfo) => void // ✅ 수정 함수 타입 추가 }) { const { selectedRevisionId, setSelectedRevisionId } = React.useContext(DocumentSelectionContext) @@ -163,6 +167,38 @@ function RevisionTable({ setSelectedRevisionId(revisionId === selectedRevisionId ? null : revisionId) } + // ✅ 리비전 수정 가능 여부 확인 함수 + const canEditRevision = React.useCallback((revision: RevisionInfo) => { + // 첨부파일이 없으면 수정 가능 + if ((!revision.attachments || revision.attachments.length === 0)&&revision.uploaderType ==="vendor") { + return true + } + + // 모든 첨부파일의 dolceFilePath가 null이거나 빈값이어야 수정 가능 + return revision.attachments.every(attachment => + !attachment.dolceFilePath || attachment.dolceFilePath.trim() === '' + ) + }, []) + + // ✅ 리비전 상태 표시 함수 (처리된 파일이 있는지 확인) + const getRevisionProcessStatus = React.useCallback((revision: RevisionInfo) => { + if (!revision.attachments || revision.attachments.length === 0) { + return 'no-files' + } + + const processedCount = revision.attachments.filter(attachment => + attachment.dolceFilePath && attachment.dolceFilePath.trim() !== '' + ).length + + if (processedCount === 0) { + return 'not-processed' + } else if (processedCount === revision.attachments.length) { + return 'fully-processed' + } else { + return 'partially-processed' + } + }, []) + return ( @@ -182,14 +218,14 @@ function RevisionTable({
- +
Select Revision Category Usage - Type {/* ✅ usageType 컬럼 */} + Type Status Uploader Comment @@ -199,94 +235,144 @@ function RevisionTable({ - {revisions.map((revision) => ( - - - toggleSelect(revision.id)} - className="h-4 w-4 cursor-pointer" - /> - - - {revision.revision} - - - {revision.uploaderType === "vendor" ? "To SHI" : "From SHI"} - - - - {revision.usage || '-'} - - - {/* ✅ usageType 표시 */} - - - {revision.usageType ? - + {revisions.map((revision) => { + const canEdit = canEditRevision(revision) + const processStatus = getRevisionProcessStatus(revision) + + return ( + + + toggleSelect(revision.id)} + className="h-4 w-4 cursor-pointer" + /> + + +
+ {revision.revision} + {/* ✅ 처리 상태 인디케이터 */} + {processStatus === 'fully-processed' && ( +
+ )} + {processStatus === 'partially-processed' && ( +
+ )} +
+ + + {revision.uploaderType === "vendor" ? "To SHI" : "From SHI"} + + + + {revision.usage || '-'} + + + + + {revision.usageType ? ( revision.usageType - - : ( + ) : ( + - + )} + + + + + {revision.revisionStatus} + + + + {revision.uploaderName || '-'} + + + {revision.comment ? ( +
+

+ {revision.comment} +

+
+ ) : ( - )} - -
- - - {revision.revisionStatus} - - - - {revision.uploaderName || '-'} - - - {revision.comment ? ( -
-

- {revision.comment} -

+ + + + {revision.uploadedAt + ? new Date(revision.uploadedAt).toLocaleDateString() + : '-'} + + + +
+ {revision.attachments.length} + {/* ✅ 처리된 파일 수 표시 */} + {processStatus === 'partially-processed' && ( + + ({revision.attachments.filter(att => + att.dolceFilePath && att.dolceFilePath.trim() !== '' + ).length} processed) + + )}
- ) : ( - - - )} -
- - - {revision.uploadedAt - ? new Date(revision.uploadedAt).toLocaleDateString() - : '-'} - - - - {revision.attachments.length} - - - {revision.attachments.length > 0 && ( - - )} - - - ))} + + +
+ {/* 보기 버튼 */} + {revision.attachments.length > 0 && ( + + )} + + {/* ✅ 수정 버튼 */} + +
+
+ + ) + })}
@@ -295,21 +381,24 @@ function RevisionTable({ ) } -function AttachmentTable({ - attachments, - onDownloadFile -}: { +function AttachmentTable({ + attachments, + onDownloadFile, + onDeleteFile, // ✅ 삭제 함수 prop 추가 +}: { attachments: AttachmentInfo[] onDownloadFile: (attachment: AttachmentInfo) => void + onDeleteFile: (attachment: AttachmentInfo) => Promise // ✅ 삭제 함수 추가 }) { const { selectedRevisionId, allData, setAllData } = React.useContext(DocumentSelectionContext) - const [addAttachmentDialogOpen, setAddAttachmentDialogOpen] = React.useState(false) // ✅ 추가 - const router = useRouter() // ✅ 추가 + const [addAttachmentDialogOpen, setAddAttachmentDialogOpen] = React.useState(false) + const [deletingFileId, setDeletingFileId] = React.useState(null) // ✅ 삭제 중인 파일 ID + const router = useRouter() - // ✅ 선택된 리비전 정보 가져오기 + // 선택된 리비전 정보 가져오기 const selectedRevisionInfo = React.useMemo(() => { if (!selectedRevisionId || !allData) return null - + for (const doc of allData) { if (doc.allStages) { for (const stage of doc.allStages as StageInfo[]) { @@ -321,14 +410,43 @@ function AttachmentTable({ return null }, [selectedRevisionId, allData]) - // ✅ 첨부파일 추가 핸들러 + // 첨부파일 추가 핸들러 const handleAddAttachment = React.useCallback(() => { if (selectedRevisionInfo) { setAddAttachmentDialogOpen(true) } }, [selectedRevisionInfo]) - // ✅ 첨부파일 업로드 성공 핸들러 + // ✅ 삭제 가능 여부 확인 함수 + const canDeleteFile = React.useCallback((attachment: AttachmentInfo) => { + return !attachment.dolceFilePath || attachment.dolceFilePath.trim() === '' + }, []) + + // ✅ 파일 삭제 핸들러 + const handleDeleteFile = React.useCallback(async (attachment: AttachmentInfo) => { + if (!canDeleteFile(attachment)) { + alert('This file cannot be deleted because it has been processed by the system.') + return + } + + const confirmDelete = window.confirm( + `Are you sure you want to delete "${attachment.fileName}"?\nThis action cannot be undone.` + ) + + if (!confirmDelete) return + + try { + setDeletingFileId(attachment.id) + await onDeleteFile(attachment) + } catch (error) { + console.error('Delete file error:', error) + alert(`Failed to delete file: ${error instanceof Error ? error.message : 'Unknown error'}`) + } finally { + setDeletingFileId(null) + } + }, [canDeleteFile, onDeleteFile]) + + // 첨부파일 업로드 성공 핸들러 const handleAttachmentUploadSuccess = React.useCallback((uploadResult?: any) => { if (!selectedRevisionId || !allData || !uploadResult?.data) { console.log('🔄 Full refresh') @@ -343,6 +461,7 @@ function AttachmentTable({ revisionId: selectedRevisionId, fileName: file.fileName, filePath: file.filePath, + dolceFilePath: null, // ✅ 새 파일은 dolceFilePath가 없음 fileSize: file.fileSize, fileType: file.fileType || null, createdAt: new Date(), @@ -352,10 +471,10 @@ function AttachmentTable({ // allData에서 해당 리비전을 찾아서 첨부파일 추가 const updatedData = allData.map(doc => { const updatedDoc = { ...doc } - + if (updatedDoc.allStages) { const stages = [...updatedDoc.allStages as StageInfo[]] - + for (const stage of stages) { const revisionIndex = stage.revisions.findIndex(r => r.id === selectedRevisionId) if (revisionIndex !== -1) { @@ -369,7 +488,7 @@ function AttachmentTable({ } } } - + return updatedDoc }) @@ -393,7 +512,7 @@ function AttachmentTable({
Attachments - {/* ✅ + 버튼 추가 */} + {/* + 버튼 */} {selectedRevisionId && selectedRevisionInfo && (