summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-28 14:13:46 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-28 14:13:46 +0900
commit24e0b8c83f7d68156e5a63ba85a541c04036f00b (patch)
treedeac3a6385b959ced9abed7de134938a609cd68f
parent9cda8482660a87fd98c9ee43f507d75ff75b4e23 (diff)
(김준회) dolce: API 추가건 대응 처리(상세도면 및 파일 삭제 기능-Standby 상태)
-rw-r--r--app/[lng]/partners/(partners)/document-list-ship/dolce-upload-page-v2.tsx120
-rw-r--r--app/[lng]/partners/(partners)/document-list-ship/page.tsx5
-rw-r--r--db/schema/dolce/dolce.ts4
-rw-r--r--db/schema/index.ts4
-rw-r--r--i18n/locales/en/dolce.json3
-rw-r--r--i18n/locales/ko/dolce.json3
-rw-r--r--lib/dolce/actions.ts24
-rw-r--r--lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx139
-rw-r--r--lib/dolce/table/file-list-columns.tsx4
-rw-r--r--lib/dolce/table/gtt-drawing-list-columns.tsx2
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<DetailDwgReceiptItem | null>(null);
const [uploadFilesDialogOpen, setUploadFilesDialogOpen] = useState(false);
+
+ // 파일 삭제 상태
+ const [fileToDelete, setFileToDelete] = useState<FileInfoItem | null>(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
</Card>
{/* 하단: 상세도면리스트 + 파일리스트 - 항상 렌더링 */}
- <div className="grid grid-cols-1 lg:grid-cols-[60fr_40fr] gap-4 flex-shrink-0 min-h-[45vh]">
+ <div className="grid grid-cols-1 2xl:grid-cols-[60fr_40fr] gap-4 flex-shrink-0 min-h-[45vh]">
{/* 좌측: 상세도면 리스트 */}
- <Card className="flex flex-col min-h-0 h-full lg:h-auto">
+ <Card className="flex flex-col min-h-0 h-full 2xl:h-auto min-w-0">
<CardHeader className="flex-row items-center justify-between py-3 flex-shrink-0">
<CardTitle className="text-base">
{t("detailDialog.detailListTitle")}
@@ -639,7 +693,7 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro
</Card>
{/* 우측: 첨부파일 리스트 */}
- <Card className="flex flex-col min-h-0 h-full lg:h-auto">
+ <Card className="flex flex-col min-h-0 h-full 2xl:h-auto min-w-0">
<CardHeader className="flex-row items-center justify-between py-3 flex-shrink-0">
<CardTitle className="text-base">
{t("detailDialog.fileListTitle")}
@@ -659,14 +713,29 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro
<RefreshCw className={`h-4 w-4 ${isLoadingFiles ? "animate-spin" : ""}`} />
</Button>
{selectedDetail && canAddDetailDrawing && (
- <Button
- variant="default"
- size="sm"
- onClick={() => setUploadFilesDialogOpen(true)}
- >
- <Upload className="h-4 w-4 mr-2" />
- {t("detailDialog.uploadFilesButton")}
- </Button>
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <div className="inline-block">
+ <Button
+ variant="default"
+ size="sm"
+ onClick={() => setUploadFilesDialogOpen(true)}
+ // Status check: only allow upload if status is 'Standby'
+ disabled={selectedDetail.Status !== "Standby"}
+ >
+ <Upload className="h-4 w-4 mr-2" />
+ {t("detailDialog.uploadFilesButton")}
+ </Button>
+ </div>
+ </TooltipTrigger>
+ {selectedDetail.Status !== "Standby" && (
+ <TooltipContent>
+ <p>{t("detailDialog.uploadRestrictedStandby")}</p>
+ </TooltipContent>
+ )}
+ </Tooltip>
+ </TooltipProvider>
)}
</div>
</CardHeader>
@@ -741,6 +810,33 @@ export default function DolceUploadPageV2({ searchParams }: DolceUploadPageV2Pro
lng={lng}
/>
)}
+
+ {/* 파일 삭제 확인 다이얼로그 */}
+ <AlertDialog open={!!fileToDelete} onOpenChange={(open) => !open && setFileToDelete(null)}>
+ <AlertDialogContent>
+ <AlertDialogHeader>
+ <AlertDialogTitle>Delete File</AlertDialogTitle>
+ <AlertDialogDescription>
+ Are you sure you want to delete this file? This action cannot be undone.
+ {fileToDelete && (
+ <div className="mt-2 p-2 bg-muted rounded-md text-sm font-medium">
+ {fileToDelete.FileName}
+ </div>
+ )}
+ </AlertDialogDescription>
+ </AlertDialogHeader>
+ <AlertDialogFooter>
+ <AlertDialogCancel disabled={isDeletingFile}>Cancel</AlertDialogCancel>
+ <AlertDialogAction
+ onClick={handleConfirmDeleteFile}
+ disabled={isDeletingFile}
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
+ >
+ {isDeletingFile ? "Deleting..." : "Delete"}
+ </AlertDialogAction>
+ </AlertDialogFooter>
+ </AlertDialogContent>
+ </AlertDialog>
</div>
);
}
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<number> {
+ 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({
</div>
)}
- <DialogFooter>
- <Button variant="outline" onClick={handleCancel} disabled={isSubmitting}>
- {showConfirmation ? t("addDetailDialog.backButton", "뒤로") : t("addDetailDialog.cancelButton")}
- </Button>
- <Button onClick={handleSubmit} disabled={isSubmitting || !isFormValid}>
- {isSubmitting
- ? t("addDetailDialog.processingButton")
- : showConfirmation
- ? t("addDetailDialog.confirmSubmit", "제출")
- : t("addDetailDialog.nextButton", "다음")
- }
- </Button>
+ <DialogFooter className={mode === "edit" && !showConfirmation ? "sm:justify-between" : ""}>
+ {mode === "edit" && !showConfirmation && (
+ <Button
+ type="button"
+ variant="destructive"
+ onClick={() => setShowDeleteConfirmation(true)}
+ disabled={isSubmitting}
+ >
+ <Trash2 className="h-4 w-4 mr-2" />
+ {t("editDetailDialog.deleteButton", "Delete")}
+ </Button>
+ )}
+ <div className="flex gap-2 justify-end sm:w-auto w-full">
+ <Button variant="outline" onClick={handleCancel} disabled={isSubmitting}>
+ {showConfirmation ? t("addDetailDialog.backButton", "뒤로") : t("addDetailDialog.cancelButton")}
+ </Button>
+ <Button onClick={handleSubmit} disabled={isSubmitting || !isFormValid}>
+ {isSubmitting
+ ? t("addDetailDialog.processingButton")
+ : showConfirmation
+ ? t("addDetailDialog.confirmSubmit", "제출")
+ : t("addDetailDialog.nextButton", "다음")
+ }
+ </Button>
+ </div>
</DialogFooter>
+
+ {/* Delete Confirmation Dialog */}
+ <AlertDialog open={showDeleteConfirmation} onOpenChange={setShowDeleteConfirmation}>
+ <AlertDialogContent>
+ <AlertDialogHeader>
+ <AlertDialogTitle>Delete Detail Drawing</AlertDialogTitle>
+ <AlertDialogDescription>
+ Are you sure you want to delete this detail drawing? This action cannot be undone.
+ {detailDrawing && (
+ <span className="block mt-2 font-medium text-foreground">
+ {detailDrawing.DrawingNo} (Rev. {detailDrawing.DrawingRevNo})
+ </span>
+ )}
+ </AlertDialogDescription>
+ </AlertDialogHeader>
+ <AlertDialogFooterComponent>
+ <AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
+ <AlertDialogAction
+ onClick={handleDelete}
+ disabled={isDeleting}
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
+ >
+ {isDeleting ? "Deleting..." : "Delete"}
+ </AlertDialogAction>
+ </AlertDialogFooterComponent>
+ </AlertDialogContent>
+ </AlertDialog>
</DialogContent>
</Dialog>
);
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 = ({
<Download className="h-4 w-4 mr-2" />
)}
</Button>
- {isLocal && onDelete && (
+ {onDelete && (
<Button
variant="destructive"
size="sm"
@@ -104,4 +103,3 @@ export const createFileListColumns = ({
},
];
-
diff --git a/lib/dolce/table/gtt-drawing-list-columns.tsx b/lib/dolce/table/gtt-drawing-list-columns.tsx
index a72e221d..94d4d7d1 100644
--- a/lib/dolce/table/gtt-drawing-list-columns.tsx
+++ b/lib/dolce/table/gtt-drawing-list-columns.tsx
@@ -168,7 +168,7 @@ export function createGttDrawingListColumns({
{
accessorKey: "CreateDt",
header: t("drawingList.columns.createDt"),
- minSize: 200,
+ minSize: 280,
cell: ({ row }) => {
const date = row.getValue("CreateDt") as string;
return <div className="text-sm text-muted-foreground">{formatDolceDateTime(date)}</div>;