diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-10-01 10:31:23 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-10-01 10:31:23 +0000 |
| commit | 74843fe598702a9a55f914f2d2d291368a5abb13 (patch) | |
| tree | a88abdaf039f51dd843e0416321f08877b17ea75 /components/ship-vendor-document/user-vendor-document-table-container.tsx | |
| parent | 33e8452331c301430191b3506825ebaf3edac93a (diff) | |
(대표님) dolce 수정, spreadjs 수정 등
Diffstat (limited to 'components/ship-vendor-document/user-vendor-document-table-container.tsx')
| -rw-r--r-- | components/ship-vendor-document/user-vendor-document-table-container.tsx | 168 |
1 files changed, 116 insertions, 52 deletions
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 |
