From 74843fe598702a9a55f914f2d2d291368a5abb13 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Wed, 1 Oct 2025 10:31:23 +0000
Subject: (대표님) dolce 수정, spreadjs 수정 등
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../user-vendor-document-table-container.tsx       | 168 ++++++++++++++-------
 1 file changed, 116 insertions(+), 52 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 7fac34a9..775dac47 100644
--- a/components/ship-vendor-document/user-vendor-document-table-container.tsx
+++ b/components/ship-vendor-document/user-vendor-document-table-container.tsx
@@ -40,6 +40,16 @@ import { useRouter } from 'next/navigation'
 import { AddAttachmentDialog } from "./add-attachment-dialog" // ✅ import 추가
 import { EditRevisionDialog } from "./edit-revision-dialog" // ✅ 추가
 import { downloadFile } from "@/lib/file-download" // ✅ 공용 다운로드 함수 import
+import {
+  AlertDialog,
+  AlertDialogAction,
+  AlertDialogCancel,
+  AlertDialogContent,
+  AlertDialogDescription,
+  AlertDialogFooter,
+  AlertDialogHeader,
+  AlertDialogTitle,
+} from "@/components/ui/alert-dialog"
 
 /* -------------------------------------------------------------------------------------------------
  * Types & Constants
@@ -172,12 +182,12 @@ function RevisionTable({
   // ✅ 리비전 수정 가능 여부 확인 함수
   const canEditRevision = React.useCallback((revision: RevisionInfo) => {
     // 첨부파일이 없으면 수정 가능
-    if ((!revision.attachments || revision.attachments.length === 0)&&revision.uploaderType ==="vendor") {
+    if ((!revision.attachments || revision.attachments.length === 0) && revision.uploaderType === "vendor") {
       return true
     }
 
     // 모든 첨부파일의 dolceFilePath가 null이거나 빈값이어야 수정 가능
-    return revision.attachments.every(attachment => 
+    return revision.attachments.every(attachment =>
       !attachment.dolceFilePath || attachment.dolceFilePath.trim() === ''
     )
   }, [])
@@ -188,7 +198,7 @@ function RevisionTable({
       return 'no-files'
     }
 
-    const processedCount = revision.attachments.filter(attachment => 
+    const processedCount = revision.attachments.filter(attachment =>
       attachment.dolceFilePath && attachment.dolceFilePath.trim() !== ''
     ).length
 
@@ -241,7 +251,7 @@ function RevisionTable({
               {revisions.map((revision) => {
                 const canEdit = canEditRevision(revision)
                 const processStatus = getRevisionProcessStatus(revision)
-                
+
                 return (
                   <TableRow
                     key={revision.id}
@@ -264,14 +274,14 @@ function RevisionTable({
                         {revision.revision}
                         {/* ✅ 처리 상태 인디케이터 */}
                         {processStatus === 'fully-processed' && (
-                          <div 
-                            className="w-2 h-2 bg-blue-500 rounded-full" 
+                          <div
+                            className="w-2 h-2 bg-blue-500 rounded-full"
                             title="All files processed"
                           />
                         )}
                         {processStatus === 'partially-processed' && (
-                          <div 
-                            className="w-2 h-2 bg-yellow-500 rounded-full" 
+                          <div
+                            className="w-2 h-2 bg-yellow-500 rounded-full"
                             title="Some files processed"
                           />
                         )}
@@ -333,7 +343,7 @@ function RevisionTable({
                         {/* ✅ 처리된 파일 수 표시 */}
                         {processStatus === 'partially-processed' && (
                           <span className="text-xs text-muted-foreground">
-                            ({revision.attachments.filter(att => 
+                            ({revision.attachments.filter(att =>
                               att.dolceFilePath && att.dolceFilePath.trim() !== ''
                             ).length} processed)
                           </span>
@@ -354,21 +364,20 @@ function RevisionTable({
                             <Eye className="h-4 w-4" />
                           </Button>
                         )}
-                        
+
                         {/* ✅ 수정 버튼 */}
                         <Button
                           variant="ghost"
                           size="sm"
                           onClick={() => onEditRevision(revision)}
-                          className={`h-8 px-2 ${
-                            canEdit 
-                              ? 'text-blue-600 hover:text-blue-700 hover:bg-blue-50' 
+                          className={`h-8 px-2 ${canEdit
+                              ? 'text-blue-600 hover:text-blue-700 hover:bg-blue-50'
                               : 'text-muted-foreground cursor-not-allowed'
-                          }`}
+                            }`}
                           disabled={!canEdit}
                           title={
-                            canEdit 
-                              ? 'Edit revision' 
+                            canEdit
+                              ? 'Edit revision'
                               : 'Cannot edit - some files have been processed'
                           }
                         >
@@ -390,17 +399,23 @@ function RevisionTable({
 function AttachmentTable({
   attachments,
   onDownloadFile,
-  onDeleteFile, // ✅ 삭제 함수 prop 추가
+  onDeleteFile,
 }: {
   attachments: AttachmentInfo[]
   onDownloadFile: (attachment: AttachmentInfo) => void
-  onDeleteFile: (attachment: AttachmentInfo) => Promise<void> // ✅ 삭제 함수 추가
+  onDeleteFile: (attachment: AttachmentInfo) => Promise<void>
 }) {
   const { selectedRevisionId, allData, setAllData } = React.useContext(DocumentSelectionContext)
   const [addAttachmentDialogOpen, setAddAttachmentDialogOpen] = React.useState(false)
-  const [deletingFileId, setDeletingFileId] = React.useState<number | null>(null) // ✅ 삭제 중인 파일 ID
+  const [deletingFileId, setDeletingFileId] = React.useState<number | null>(null)
   const router = useRouter()
 
+  // ✅ AlertDialog 상태 추가
+  const [deleteConfirmOpen, setDeleteConfirmOpen] = React.useState(false)
+  const [fileToDelete, setFileToDelete] = React.useState<AttachmentInfo | null>(null)
+  const [errorAlertOpen, setErrorAlertOpen] = React.useState(false)
+  const [errorMessage, setErrorMessage] = React.useState('')
+
   // 선택된 리비전 정보 가져오기
   const selectedRevisionInfo = React.useMemo(() => {
     if (!selectedRevisionId || !allData) return null
@@ -425,34 +440,48 @@ function AttachmentTable({
 
   // ✅ 삭제 가능 여부 확인 함수
   const canDeleteFile = React.useCallback((attachment: AttachmentInfo) => {
+    // rejected 상태의 리비전에 속한 첨부파일은 무조건 삭제 가능
+    if (selectedRevisionInfo &&
+      selectedRevisionInfo.revisionStatus &&
+      selectedRevisionInfo.revisionStatus.toLowerCase() === 'rejected') {
+      return true
+    }
+
+    // 그 외의 경우는 기존 로직대로: dolceFilePath가 없거나 빈값인 경우만 삭제 가능
     return !attachment.dolceFilePath || attachment.dolceFilePath.trim() === ''
-  }, [])
+  }, [selectedRevisionInfo])
 
-  // ✅ 파일 삭제 핸들러
-  const handleDeleteFile = React.useCallback(async (attachment: AttachmentInfo) => {
+  // ✅ 삭제 요청 핸들러 (확인 다이얼로그 표시)
+  const handleDeleteRequest = React.useCallback((attachment: AttachmentInfo) => {
     if (!canDeleteFile(attachment)) {
-      alert('This file cannot be deleted because it has been processed by the system.')
+      setErrorMessage('This file cannot be deleted because it has been processed by the system.')
+      setErrorAlertOpen(true)
       return
     }
 
-    const confirmDelete = window.confirm(
-      `Are you sure you want to delete "${attachment.fileName}"?\nThis action cannot be undone.`
-    )
-    
-    if (!confirmDelete) return
+    setFileToDelete(attachment)
+    setDeleteConfirmOpen(true)
+  }, [canDeleteFile])
+
+  // ✅ 실제 삭제 수행 핸들러
+  const handleConfirmDelete = React.useCallback(async () => {
+    if (!fileToDelete) return
 
     try {
-      setDeletingFileId(attachment.id)
-      await onDeleteFile(attachment)
+      setDeletingFileId(fileToDelete.id)
+      setDeleteConfirmOpen(false)
+      await onDeleteFile(fileToDelete)
     } catch (error) {
       console.error('Delete file error:', error)
-      alert(`Failed to delete file: ${error instanceof Error ? error.message : 'Unknown error'}`)
+      setErrorMessage(`Failed to delete file: ${error instanceof Error ? error.message : 'Unknown error'}`)
+      setErrorAlertOpen(true)
     } finally {
       setDeletingFileId(null)
+      setFileToDelete(null)
     }
-  }, [canDeleteFile, onDeleteFile])
+  }, [fileToDelete, onDeleteFile])
 
-  // 첨부파일 업로드 성공 핸들러
+  // 첨부파일 업로드 성공 핸들러 (기존 코드 유지)
   const handleAttachmentUploadSuccess = React.useCallback((uploadResult?: any) => {
     if (!selectedRevisionId || !allData || !uploadResult?.data) {
       console.log('🔄 Full refresh')
@@ -467,7 +496,7 @@ function AttachmentTable({
         revisionId: selectedRevisionId,
         fileName: file.fileName,
         filePath: file.filePath,
-        dolceFilePath: null, // ✅ 새 파일은 dolceFilePath가 없음
+        dolceFilePath: null,
         fileSize: file.fileSize,
         fileType: file.fileType || null,
         createdAt: new Date(),
@@ -484,7 +513,6 @@ function AttachmentTable({
           for (const stage of stages) {
             const revisionIndex = stage.revisions.findIndex(r => r.id === selectedRevisionId)
             if (revisionIndex !== -1) {
-              // 해당 리비전의 첨부파일 배열에 새 파일들 추가
               stage.revisions[revisionIndex] = {
                 ...stage.revisions[revisionIndex],
                 attachments: [...stage.revisions[revisionIndex].attachments, ...newAttachments]
@@ -501,7 +529,6 @@ function AttachmentTable({
       setAllData(updatedData)
       console.log('✅ AttachmentTable update complete')
 
-      // 메인 테이블도 업데이트 (약간의 지연 후)
       setTimeout(() => {
         router.refresh()
       }, 1500)
@@ -518,7 +545,6 @@ function AttachmentTable({
         <CardHeader>
           <div className="flex items-center justify-between">
             <CardTitle className="text-lg">Attachments</CardTitle>
-            {/* + 버튼 */}
             {selectedRevisionId && selectedRevisionInfo && (
               <Button
                 onClick={handleAddAttachment}
@@ -551,7 +577,6 @@ function AttachmentTable({
                           ? 'Please select a revision'
                           : 'No attached files'}
                       </span>
-                      {/* 리비전이 선택된 경우 추가 버튼 표시 */}
                       {selectedRevisionId && selectedRevisionInfo && (
                         <Button
                           onClick={handleAddAttachment}
@@ -581,7 +606,6 @@ function AttachmentTable({
                               : `${(file.fileSize / 1024).toFixed(1)}KB`
                             : '-'}
                         </div>
-                        {/* ✅ dolceFilePath 상태 표시 */}
                         {file.dolceFilePath && file.dolceFilePath.trim() !== '' && (
                           <div className="text-xs text-blue-600 font-medium">
                             Processed
@@ -591,7 +615,6 @@ function AttachmentTable({
                     </TableCell>
                     <TableCell>
                       <div className="flex items-center gap-1">
-                        {/* 다운로드 버튼 */}
                         <Button
                           variant="ghost"
                           size="sm"
@@ -601,21 +624,21 @@ function AttachmentTable({
                         >
                           <Download className="h-4 w-4" />
                         </Button>
-                        
-                        {/* ✅ 삭제 버튼 */}
+
                         <Button
                           variant="ghost"
                           size="sm"
-                          onClick={() => handleDeleteFile(file)}
-                          className={`h-8 px-2 ${
-                            canDeleteFile(file) 
-                              ? 'text-red-600 hover:text-red-700 hover:bg-red-50' 
+                          onClick={() => handleDeleteRequest(file)}
+                          className={`h-8 px-2 ${canDeleteFile(file)
+                              ? 'text-red-600 hover:text-red-700 hover:bg-red-50'
                               : 'text-muted-foreground cursor-not-allowed'
-                          }`}
+                            }`}
                           disabled={!canDeleteFile(file) || deletingFileId === file.id}
                           title={
-                            canDeleteFile(file) 
-                              ? 'Delete file' 
+                            canDeleteFile(file)
+                              ? selectedRevisionInfo?.revisionStatus?.toLowerCase() === 'rejected'
+                                ? 'Delete file (rejected revision)'
+                                : 'Delete file'
                               : 'Cannot delete processed file'
                           }
                         >
@@ -635,6 +658,47 @@ function AttachmentTable({
         </CardContent>
       </Card>
 
+      {/* ✅ 삭제 확인 다이얼로그 */}
+      <AlertDialog open={deleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
+        <AlertDialogContent>
+          <AlertDialogHeader>
+            <AlertDialogTitle>Delete File</AlertDialogTitle>
+            <AlertDialogDescription>
+              Are you sure you want to delete "{fileToDelete?.fileName}"?
+              This action cannot be undone.
+            </AlertDialogDescription>
+          </AlertDialogHeader>
+          <AlertDialogFooter>
+            <AlertDialogCancel onClick={() => setFileToDelete(null)}>
+              Cancel
+            </AlertDialogCancel>
+            <AlertDialogAction
+              onClick={handleConfirmDelete}
+              className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
+            >
+              Delete
+            </AlertDialogAction>
+          </AlertDialogFooter>
+        </AlertDialogContent>
+      </AlertDialog>
+
+      {/* ✅ 에러 메시지 다이얼로그 */}
+      <AlertDialog open={errorAlertOpen} onOpenChange={setErrorAlertOpen}>
+        <AlertDialogContent>
+          <AlertDialogHeader>
+            <AlertDialogTitle>Error</AlertDialogTitle>
+            <AlertDialogDescription>
+              {errorMessage}
+            </AlertDialogDescription>
+          </AlertDialogHeader>
+          <AlertDialogFooter>
+            <AlertDialogAction onClick={() => setErrorMessage('')}>
+              OK
+            </AlertDialogAction>
+          </AlertDialogFooter>
+        </AlertDialogContent>
+      </AlertDialog>
+
       {/* AddAttachmentDialog */}
       {selectedRevisionInfo && (
         <AddAttachmentDialog
@@ -666,7 +730,7 @@ function SubTables() {
   const isCancelled = React.useRef(false)
 
   const [newRevisionDialogOpen, setNewRevisionDialogOpen] = React.useState(false)
-  
+
   // ✅ 리비전 수정 다이얼로그 상태
   const [editRevisionDialogOpen, setEditRevisionDialogOpen] = React.useState(false)
   const [editingRevision, setEditingRevision] = React.useState<RevisionInfo | null>(null)
@@ -770,7 +834,7 @@ function SubTables() {
     try {
       // 파일 경로 처리
       let downloadPath = attachment.filePath
-      
+
       // 공용 다운로드 함수 사용 (보안 검증, 파일 체크 모두 포함)
       const result = await downloadFile(downloadPath, attachment.fileName, {
         action: 'download',
@@ -784,7 +848,7 @@ function SubTables() {
 
     } catch (error) {
       console.error('File download error:', error)
-      
+
       // fallback: API 엔드포인트를 통한 다운로드 시도
       try {
         const queryParam = attachment.id
-- 
cgit v1.2.3

