From 24e0b8c83f7d68156e5a63ba85a541c04036f00b Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Fri, 28 Nov 2025 14:13:46 +0900 Subject: (김준회) dolce: API 추가건 대응 처리(상세도면 및 파일 삭제 기능-Standby 상태) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../document-list-ship/dolce-upload-page-v2.tsx | 120 ++++++++++++++++-- .../(partners)/document-list-ship/page.tsx | 5 - db/schema/dolce/dolce.ts | 4 +- db/schema/index.ts | 4 +- i18n/locales/en/dolce.json | 3 +- i18n/locales/ko/dolce.json | 3 +- lib/dolce/actions.ts | 24 +++- .../add-and-modify-detail-drawing-dialog.tsx | 139 ++++++++++++++++++--- lib/dolce/table/file-list-columns.tsx | 4 +- lib/dolce/table/gtt-drawing-list-columns.tsx | 2 +- 10 files changed, 264 insertions(+), 44 deletions(-) diff --git a/app/[lng]/partners/(partners)/document-list-ship/dolce-upload-page-v2.tsx b/app/[lng]/partners/(partners)/document-list-ship/dolce-upload-page-v2.tsx index f7ddfde6..555b921c 100644 --- a/app/[lng]/partners/(partners)/document-list-ship/dolce-upload-page-v2.tsx +++ b/app/[lng]/partners/(partners)/document-list-ship/dolce-upload-page-v2.tsx @@ -15,6 +15,12 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { InfoIcon, RefreshCw, Search, Upload, Plus, Loader2 } from "lucide-react"; import { toast } from "sonner"; import { useTranslation } from "@/i18n/client"; @@ -27,6 +33,7 @@ import { fetchVendorProjects, fetchDetailDwgReceiptList, fetchFileInfoList, + deleteFileInfo, } from "@/lib/dolce/actions"; import { DrawingListTableV2 } from "@/lib/dolce/table/drawing-list-table-v2"; import { drawingListColumns } from "@/lib/dolce/table/drawing-list-columns"; @@ -39,6 +46,16 @@ import { B4BulkUploadDialogV3 } from "@/lib/dolce/dialogs/b4-bulk-upload-dialog- // import { B4BulkUploadDialog } from "@/lib/dolce/dialogs/b4-bulk-upload-dialog"; import { AddAndModifyDetailDrawingDialog } from "@/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog"; import { UploadFilesToDetailDialog } from "@/lib/dolce/dialogs/upload-files-to-detail-dialog"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; interface DolceUploadPageV2Props { searchParams: { [key: string]: string | string[] | undefined }; @@ -89,6 +106,10 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro const [dialogMode, setDialogMode] = useState<"add" | "edit">("add"); const [editingDetail, setEditingDetail] = useState(null); const [uploadFilesDialogOpen, setUploadFilesDialogOpen] = useState(false); + + // 파일 삭제 상태 + const [fileToDelete, setFileToDelete] = useState(null); + const [isDeletingFile, setIsDeletingFile] = useState(false); // 초기 데이터 로드 const loadInitialData = useCallback(async () => { @@ -317,6 +338,33 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro setAddDialogOpen(true); }; + // 파일 삭제 클릭 핸들러 + const handleDeleteFileClick = (file: FileInfoItem) => { + setFileToDelete(file); + }; + + // 파일 삭제 확인 핸들러 + const handleConfirmDeleteFile = async () => { + if (!fileToDelete) return; + + try { + setIsDeletingFile(true); + await deleteFileInfo({ + fileId: fileToDelete.FileId, + uploadId: fileToDelete.UploadId, + }); + + toast.success("File deleted successfully"); + loadFiles(); + } catch (error) { + console.error("파일 삭제 실패:", error); + toast.error("Failed to delete file"); + } finally { + setIsDeletingFile(false); + setFileToDelete(null); + } + }; + // 필터된 도면 목록 (클라이언트 사이드 필터링) const filteredDrawings = useMemo(() => { let result = drawings.filter((drawing) => { @@ -382,7 +430,13 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro (vendorInfo.drawingKind === "B4" && selectedDrawing && 'DrawingMoveGbn' in selectedDrawing && selectedDrawing.DrawingMoveGbn === "도면입수") ); - const fileColumns = createFileListColumns({ onDownload: handleDownload, lng }); + const canDeleteFile = selectedDetail?.Status === "Standby"; + + const fileColumns = createFileListColumns({ + onDownload: handleDownload, + onDelete: canDeleteFile ? handleDeleteFileClick : undefined, + lng + }); if (isLoading) { return ( @@ -575,9 +629,9 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro {/* 하단: 상세도면리스트 + 파일리스트 - 항상 렌더링 */} -
+
{/* 좌측: 상세도면 리스트 */} - + {t("detailDialog.detailListTitle")} @@ -639,7 +693,7 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro {/* 우측: 첨부파일 리스트 */} - + {t("detailDialog.fileListTitle")} @@ -659,14 +713,29 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro {selectedDetail && canAddDetailDrawing && ( - + + + +
+ +
+
+ {selectedDetail.Status !== "Standby" && ( + +

{t("detailDialog.uploadRestrictedStandby")}

+
+ )} +
+
)}
@@ -741,6 +810,33 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro lng={lng} /> )} + + {/* 파일 삭제 확인 다이얼로그 */} + !open && setFileToDelete(null)}> + + + Delete File + + Are you sure you want to delete this file? This action cannot be undone. + {fileToDelete && ( +
+ {fileToDelete.FileName} +
+ )} +
+
+ + Cancel + + {isDeletingFile ? "Deleting..." : "Delete"} + + +
+
); } diff --git a/app/[lng]/partners/(partners)/document-list-ship/page.tsx b/app/[lng]/partners/(partners)/document-list-ship/page.tsx index 9ce7c6c6..46800a77 100644 --- a/app/[lng]/partners/(partners)/document-list-ship/page.tsx +++ b/app/[lng]/partners/(partners)/document-list-ship/page.tsx @@ -4,11 +4,6 @@ import { Card, CardContent, CardHeader } from "@/components/ui/card"; import DolceUploadPageV2 from "./dolce-upload-page-v2"; import { Shell } from "@/components/shell"; -export const metadata = { - title: "조선 벤더문서 업로드(DOLCE) V2", - description: "조선 설계문서 업로드 및 관리 - 분할 레이아웃", -}; - // ============================================================================ // 로딩 스켈레톤 // ============================================================================ diff --git a/db/schema/dolce/dolce.ts b/db/schema/dolce/dolce.ts index 378d29d2..6552d61b 100644 --- a/db/schema/dolce/dolce.ts +++ b/db/schema/dolce/dolce.ts @@ -1,4 +1,6 @@ -import { pgSchema, varchar, timestamp, jsonb, text, index, serial, boolean, uuid, integer } from "drizzle-orm/pg-core"; +// 유지하지만, 미사용 중. dolce 에 바로 보내기로 합의 + +import { pgSchema, varchar, timestamp, jsonb, text, index, boolean, uuid, integer } from "drizzle-orm/pg-core"; export const dolceSchema = pgSchema("dolce"); diff --git a/db/schema/index.ts b/db/schema/index.ts index df4ca424..cd54e032 100644 --- a/db/schema/index.ts +++ b/db/schema/index.ts @@ -84,6 +84,4 @@ export * from './avl/avl'; export * from './avl/vendor-pool'; // === Email Logs 스키마 === export * from './emailLogs'; -export * from './emailWhitelist'; -// Dolce 로컬 저장용 스키마 -export * from './dolce/dolce'; \ No newline at end of file +export * from './emailWhitelist'; \ No newline at end of file diff --git a/i18n/locales/en/dolce.json b/i18n/locales/en/dolce.json index adb35efe..c198086d 100644 --- a/i18n/locales/en/dolce.json +++ b/i18n/locales/en/dolce.json @@ -69,7 +69,8 @@ "downloadSuccess": "File download completed", "downloadError": "Failed to download file", "detailLoadError": "Failed to load detail drawings", - "fileLoadError": "Failed to load file list" + "fileLoadError": "Failed to load file list", + "uploadRestrictedStandby": "File attachment is only available for 'Standby' status." }, "detailDrawing": { "columns": { diff --git a/i18n/locales/ko/dolce.json b/i18n/locales/ko/dolce.json index e9f7f862..4390a416 100644 --- a/i18n/locales/ko/dolce.json +++ b/i18n/locales/ko/dolce.json @@ -69,7 +69,8 @@ "downloadSuccess": "파일 다운로드가 완료되었습니다", "downloadError": "파일 다운로드에 실패했습니다", "detailLoadError": "상세도면 로드에 실패했습니다", - "fileLoadError": "파일 목록 로드에 실패했습니다" + "fileLoadError": "파일 목록 로드에 실패했습니다", + "uploadRestrictedStandby": "파일 업로드는 'Standby' 상태인 상세도면에만 가능합니다." }, "detailDrawing": { "columns": { diff --git a/lib/dolce/actions.ts b/lib/dolce/actions.ts index 5590ce8c..501c6cb0 100644 --- a/lib/dolce/actions.ts +++ b/lib/dolce/actions.ts @@ -138,7 +138,7 @@ export interface FileInfoItem { } export interface DetailDwgEditRequest { - Mode: "ADD" | "MOD"; + Mode: "ADD" | "MOD" | "DEL"; Status: string; RegisterId: number; ProjectNo: string; @@ -471,6 +471,28 @@ export async function fetchVendorProjects(): Promise< } } +/** + * 6. 파일 삭제 (FileInfoDeleteEdit) + */ +export async function deleteFileInfo(params: { + fileId: string; + uploadId: string; +}): Promise { + try { + const response = await dolceApiCall<{ + FileInfoDeleteEditResult: number; + }>("FileInfoDeleteEdit", { + FileId: params.fileId, + UploadId: params.uploadId, + }); + + return response.FileInfoDeleteEditResult; + } catch (error) { + console.error("파일 삭제 실패:", error); + throw error; + } +} + // ============================================================================ // B4 일괄 업로드 관련 // ============================================================================ diff --git a/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx b/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx index 0253228b..54ec79a7 100644 --- a/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx +++ b/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { Dialog, DialogContent, @@ -20,7 +20,7 @@ import { } from "@/components/ui/select"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Textarea } from "@/components/ui/textarea"; -import { Upload, X, FileIcon, Info } from "lucide-react"; +import { Upload, X, FileIcon, Info, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { useTranslation } from "@/i18n/client"; import { UnifiedDwgReceiptItem, DetailDwgReceiptItem, editDetailDwgReceipt } from "../actions"; @@ -34,6 +34,16 @@ import { getB4DrawingUsageOptions, getB4RegisterKindOptions } from "../utils/code-translator"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter as AlertDialogFooterComponent, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; interface AddAndModifyDetailDrawingDialogProps { open: boolean; @@ -73,6 +83,10 @@ export function AddAndModifyDetailDrawingDialog({ const [isSubmitting, setIsSubmitting] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false); + + // 삭제 관련 상태 + const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); // Edit 모드일 때 초기값 설정 useEffect(() => { @@ -150,7 +164,7 @@ export function AddAndModifyDetailDrawingDialog({ }; // 폼 초기화 - const resetForm = () => { + const resetForm = useCallback(() => { setDrawingUsage(""); setRegisterKind(""); setRevision(""); @@ -158,7 +172,7 @@ export function AddAndModifyDetailDrawingDialog({ setComment(""); clearFiles(); setShowConfirmation(false); - }; + }, [clearFiles]); // 제출 (확인 단계 포함) const handleSubmit = async () => { @@ -346,6 +360,59 @@ export function AddAndModifyDetailDrawingDialog({ } }; + // 상세도면 삭제 핸들러 + const handleDelete = async () => { + if (!detailDrawing) return; + + try { + setIsDeleting(true); + + const result = await editDetailDwgReceipt({ + dwgList: [ + { + Mode: "DEL", + Status: detailDrawing.Status, + RegisterId: detailDrawing.RegisterId, + ProjectNo: detailDrawing.ProjectNo, + Discipline: detailDrawing.Discipline, + DrawingKind: detailDrawing.DrawingKind, + DrawingNo: detailDrawing.DrawingNo, + DrawingName: detailDrawing.DrawingName, + RegisterGroupId: detailDrawing.RegisterGroupId, + RegisterSerialNo: detailDrawing.RegisterSerialNo, + RegisterKind: detailDrawing.RegisterKind, // 기존 값 유지 + DrawingRevNo: detailDrawing.DrawingRevNo, // 기존 값 유지 + Category: detailDrawing.Category, + Receiver: detailDrawing.Receiver, + Manager: detailDrawing.Manager, + RegisterDesc: detailDrawing.RegisterDesc, + UploadId: detailDrawing.UploadId, + RegCompanyCode: detailDrawing.RegCompanyCode || vendorCode, + }, + ], + userId, + userNm: userName, + vendorCode, + email: userEmail, + }); + + if (result > 0) { + toast.success("Detail drawing deleted successfully"); + setShowDeleteConfirmation(false); + resetForm(); + onComplete(); + onOpenChange(false); + } else { + toast.error("Failed to delete detail drawing"); + } + } catch (error) { + console.error("상세도면 삭제 실패:", error); + toast.error("An error occurred while deleting"); + } finally { + setIsDeleting(false); + } + }; + // DrawingUsage가 변경되면 RegisterKind 초기화 const handleDrawingUsageChange = (value: string) => { setDrawingUsage(value); @@ -631,19 +698,59 @@ export function AddAndModifyDetailDrawingDialog({ )} - - - + + {mode === "edit" && !showConfirmation && ( + + )} +
+ + +
+ + {/* Delete Confirmation Dialog */} + + + + Delete Detail Drawing + + Are you sure you want to delete this detail drawing? This action cannot be undone. + {detailDrawing && ( + + {detailDrawing.DrawingNo} (Rev. {detailDrawing.DrawingRevNo}) + + )} + + + + Cancel + + {isDeleting ? "Deleting..." : "Delete"} + + + + ); diff --git a/lib/dolce/table/file-list-columns.tsx b/lib/dolce/table/file-list-columns.tsx index e21f4382..30349063 100644 --- a/lib/dolce/table/file-list-columns.tsx +++ b/lib/dolce/table/file-list-columns.tsx @@ -35,7 +35,6 @@ export const createFileListColumns = ({ header: "Action", minSize: 50, cell: ({ row }) => { - const isLocal = row.original.FileServerId === "LOCAL"; const isDownloading = downloadingFileId === row.original.FileId; return ( @@ -55,7 +54,7 @@ export const createFileListColumns = ({ )} - {isLocal && onDelete && ( + {onDelete && (