From 8547034e6d82e4d1184f35af2dbff67180d89dc8 Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Wed, 26 Nov 2025 18:09:18 +0900 Subject: (김준회) dolce: 동기화 기능 추가, 로컬 다운로드, 삭제 추가, 동기화 dialog 개선 등 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../add-and-modify-detail-drawing-dialog-v2.tsx | 695 +++++++++++++++++++++ 1 file changed, 695 insertions(+) create mode 100644 lib/dolce-v2/dialogs/add-and-modify-detail-drawing-dialog-v2.tsx (limited to 'lib/dolce-v2/dialogs/add-and-modify-detail-drawing-dialog-v2.tsx') diff --git a/lib/dolce-v2/dialogs/add-and-modify-detail-drawing-dialog-v2.tsx b/lib/dolce-v2/dialogs/add-and-modify-detail-drawing-dialog-v2.tsx new file mode 100644 index 00000000..b8650b1a --- /dev/null +++ b/lib/dolce-v2/dialogs/add-and-modify-detail-drawing-dialog-v2.tsx @@ -0,0 +1,695 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Textarea } from "@/components/ui/textarea"; +import { Upload, X, FileIcon, Info, Loader2, Trash2 } from "lucide-react"; +import { toast } from "sonner"; +import { useTranslation } from "@/i18n/client"; +import { + UnifiedDwgReceiptItem, + DetailDwgReceiptItem, + editDetailDwgReceiptV2, // V2 Action + deleteLocalDetailDrawing +} from "@/lib/dolce-v2/actions"; +import { v4 as uuidv4 } from "uuid"; +import { useFileUploadWithProgress } from "@/lib/dolce/hooks/use-file-upload-with-progress"; +// import { uploadFilesWithProgress } from "../utils/upload-with-progress"; // V2에서는 사용 안함 (Action에 포함) +import { + getB3DrawingUsageOptions, + getB3RegisterKindOptions, + getB4DrawingUsageOptions, + getB4RegisterKindOptions +} from "@/lib/dolce/utils/code-translator"; + +interface AddAndModifyDetailDrawingDialogV2Props { + open: boolean; + onOpenChange: (open: boolean) => void; + drawing: UnifiedDwgReceiptItem | null; + vendorCode: string; + userId: string; + userName: string; + userEmail: string; + onComplete: () => void; + drawingKind: "B3" | "B4"; + lng: string; + mode?: "add" | "edit"; + detailDrawing?: DetailDwgReceiptItem | null; +} + +export function AddAndModifyDetailDrawingDialogV2({ + open, + onOpenChange, + drawing, + vendorCode, + userId, + userName, + userEmail, + onComplete, + drawingKind, + lng, + mode = "add", + detailDrawing = null, +}: AddAndModifyDetailDrawingDialogV2Props) { + const { t } = useTranslation(lng, "dolce"); + const [drawingUsage, setDrawingUsage] = useState(""); + const [registerKind, setRegisterKind] = useState(""); + const [revision, setRevision] = useState(""); + const [revisionError, setRevisionError] = useState(""); + const [comment, setComment] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); + + // Edit 모드일 때 초기값 설정 + useEffect(() => { + if (mode === "edit" && detailDrawing && open) { + setDrawingUsage(detailDrawing.DrawingUsage || ""); + setRegisterKind(detailDrawing.RegisterKind || ""); + setRevision(detailDrawing.DrawingRevNo || ""); + setComment(detailDrawing.RegisterDesc || ""); + } else if (mode === "add" && open) { + // Add 모드로 열릴 때는 초기화 + resetForm(); + } + }, [mode, detailDrawing, open]); + + // 옵션 생성 (다국어 지원) + const drawingUsageOptions = drawingKind === "B3" + ? getB3DrawingUsageOptions(lng) + : getB4DrawingUsageOptions(lng); + + const registerKindOptions = drawingKind === "B3" + ? getB3RegisterKindOptions(drawingUsage, lng).map(opt => ({ + ...opt, + revisionRule: lng === "ko" ? "예: A, B, C 또는 R00, R01, R02" : "e.g. A, B, C or R00, R01, R02" + })) + : getB4RegisterKindOptions(drawingUsage, lng).map(opt => ({ + ...opt, + revisionRule: lng === "ko" ? "예: R00, R01, R02, R03" : "e.g. R00, R01, R02, R03" + })); + + // 파일 업로드 훅 사용 + const { + files, + removeFile, + clearFiles, + getRootProps, + getInputProps, + isDragActive, + } = useFileUploadWithProgress(); + + // Revision 유효성 검증 함수 + const validateRevision = (value: string): string => { + if (!value.trim()) { + return t("addDetailDialog.revisionRequired"); + } + + const upperValue = value.toUpperCase().trim(); + + // A-Z 패턴 (단일 알파벳) + if (/^[A-Z]$/.test(upperValue)) { + return ""; + } + + // R00-R99 패턴 + if (/^R\d{2}$/.test(upperValue)) { + return ""; + } + + return t("addDetailDialog.revisionInvalidFormat"); + }; + + // Revision 입력 핸들러 + const handleRevisionChange = (value: string) => { + const processedValue = value.toUpperCase(); + setRevision(processedValue); + + // 값이 있을 때만 validation + if (processedValue.trim()) { + const error = validateRevision(processedValue); + setRevisionError(error); + } else { + setRevisionError(""); + } + }; + + // 폼 초기화 + const resetForm = () => { + setDrawingUsage(""); + setRegisterKind(""); + setRevision(""); + setRevisionError(""); + setComment(""); + clearFiles(); + }; + + // 제출 + const handleSubmit = async () => { + // 유효성 검사 + if (!registerKind) { + toast.error(t("addDetailDialog.selectRegisterKindError")); + return; + } + + if (drawingUsage !== "CMT") { + if (!revision.trim()) { + toast.error(t("addDetailDialog.selectRevisionError")); + setRevisionError(t("addDetailDialog.revisionRequired")); + return; + } + + // Revision 형식 검증 + const revisionValidationError = validateRevision(revision); + if (revisionValidationError) { + toast.error(revisionValidationError); + setRevisionError(revisionValidationError); + return; + } + } + + // Add 모드일 때만 파일 필수 + if (mode === "add") { + if (!drawing) return; + if (!drawingUsage) { + toast.error(t("addDetailDialog.selectDrawingUsageError")); + return; + } + if (files.length === 0) { + toast.error(t("addDetailDialog.selectFilesError")); + return; + } + } + + // Edit 모드일 때는 detailDrawing 필수 + if (mode === "edit" && !detailDrawing) { + toast.error(t("editDetailDialog.editError")); + return; + } + + try { + setIsSubmitting(true); + + // FormData 구성 + const formData = new FormData(); + formData.append("userId", userId); + formData.append("userNm", userName); + formData.append("vendorCode", vendorCode); + formData.append("email", userEmail); + + if (mode === "add" && drawing) { + const uploadId = uuidv4(); + + const dwgList = [ + { + Mode: "ADD", + Status: "Submitted", + RegisterId: 0, + ProjectNo: drawing.ProjectNo, + Discipline: drawing.Discipline, + DrawingKind: drawing.DrawingKind, + DrawingNo: drawing.DrawingNo, + DrawingName: drawing.DrawingName, + RegisterGroupId: drawing.RegisterGroupId, + RegisterSerialNo: 0, + RegisterKind: registerKind, + DrawingRevNo: drawingUsage === "CMT" ? null : revision, + Category: "TS", + Receiver: null, + Manager: "", + RegisterDesc: comment, + UploadId: uploadId, + RegCompanyCode: vendorCode, + }, + ]; + formData.append("dwgList", JSON.stringify(dwgList)); + + // 파일 추가 + formData.append("fileCount", String(files.length)); + files.forEach((file, index) => { + formData.append(`file_${index}`, file); + }); + + } else if (mode === "edit" && detailDrawing) { + const dwgList = [ + { + Mode: "MOD", + 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: registerKind, + DrawingRevNo: drawingUsage === "CMT" ? null : revision, + Category: detailDrawing.Category, + Receiver: detailDrawing.Receiver, + Manager: detailDrawing.Manager, + RegisterDesc: comment, + UploadId: detailDrawing.UploadId, + RegCompanyCode: detailDrawing.RegCompanyCode || vendorCode, + }, + ]; + formData.append("dwgList", JSON.stringify(dwgList)); + formData.append("fileCount", "0"); // 수정 시에는 메타데이터만 수정 (파일 수정은 별도) + } + + // Action 호출 + const result = await editDetailDwgReceiptV2(formData); + + if (result.success) { + toast.success(mode === "add" ? t("addDetailDialog.addSuccess") : t("editDetailDialog.editSuccess")); + resetForm(); + onComplete(); + onOpenChange(false); + } else { + throw new Error("Action failed"); + } + + } catch (error) { + console.error("상세도면 처리 실패:", error); + toast.error(mode === "add" ? t("addDetailDialog.addErrorMessage") : t("editDetailDialog.editErrorMessage")); + } finally { + setIsSubmitting(false); + } + }; + + // 삭제 핸들러 + const handleDelete = async () => { + if (!detailDrawing) return; + + if (!confirm(lng === "ko" ? "정말로 삭제하시겠습니까?" : "Are you sure you want to delete?")) return; + + try { + setIsSubmitting(true); + // uploadId만 있으면 됨 + const result = await deleteLocalDetailDrawing(detailDrawing.UploadId); + + if (result.success) { + toast.success(lng === "ko" ? "삭제되었습니다." : "Deleted successfully."); + onComplete(); + onOpenChange(false); + } else { + throw new Error(result.error); + } + } catch (error) { + console.error("삭제 실패:", error); + toast.error(lng === "ko" ? "삭제 실패" : "Delete failed"); + } finally { + setIsSubmitting(false); + } + }; + + const handleCancel = () => { + // 유효성 검사 + if (!registerKind) { + toast.error(t("addDetailDialog.selectRegisterKindError")); + return; + } + + if (drawingUsage !== "CMT") { + if (!revision.trim()) { + toast.error(t("addDetailDialog.selectRevisionError")); + setRevisionError(t("addDetailDialog.revisionRequired")); + return; + } + + // Revision 형식 검증 + const revisionValidationError = validateRevision(revision); + if (revisionValidationError) { + toast.error(revisionValidationError); + setRevisionError(revisionValidationError); + return; + } + } + + // Add 모드일 때만 파일 필수 + if (mode === "add") { + if (!drawing) return; + if (!drawingUsage) { + toast.error(t("addDetailDialog.selectDrawingUsageError")); + return; + } + if (files.length === 0) { + toast.error(t("addDetailDialog.selectFilesError")); + return; + } + } + + // Edit 모드일 때는 detailDrawing 필수 + if (mode === "edit" && !detailDrawing) { + toast.error(t("editDetailDialog.editError")); + return; + } + + try { + setIsSubmitting(true); + + // FormData 구성 + const formData = new FormData(); + formData.append("userId", userId); + formData.append("userNm", userName); + formData.append("vendorCode", vendorCode); + formData.append("email", userEmail); + + if (mode === "add" && drawing) { + const uploadId = uuidv4(); + + const dwgList = [ + { + Mode: "ADD", + Status: "Submitted", + RegisterId: 0, + ProjectNo: drawing.ProjectNo, + Discipline: drawing.Discipline, + DrawingKind: drawing.DrawingKind, + DrawingNo: drawing.DrawingNo, + DrawingName: drawing.DrawingName, + RegisterGroupId: drawing.RegisterGroupId, + RegisterSerialNo: 0, + RegisterKind: registerKind, + DrawingRevNo: drawingUsage === "CMT" ? null : revision, + Category: "TS", + Receiver: null, + Manager: "", + RegisterDesc: comment, + UploadId: uploadId, + RegCompanyCode: vendorCode, + }, + ]; + formData.append("dwgList", JSON.stringify(dwgList)); + + // 파일 추가 + formData.append("fileCount", String(files.length)); + files.forEach((file, index) => { + formData.append(`file_${index}`, file); + }); + + } else if (mode === "edit" && detailDrawing) { + const dwgList = [ + { + Mode: "MOD", + 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: registerKind, + DrawingRevNo: drawingUsage === "CMT" ? null : revision, + Category: detailDrawing.Category, + Receiver: detailDrawing.Receiver, + Manager: detailDrawing.Manager, + RegisterDesc: comment, + UploadId: detailDrawing.UploadId, + RegCompanyCode: detailDrawing.RegCompanyCode || vendorCode, + }, + ]; + formData.append("dwgList", JSON.stringify(dwgList)); + formData.append("fileCount", "0"); // 수정 시에는 메타데이터만 수정 (파일 수정은 별도) + } + + // Action 호출 + const result = await editDetailDwgReceiptV2(formData); + + if (result.success) { + toast.success(mode === "add" ? t("addDetailDialog.addSuccess") : t("editDetailDialog.editSuccess")); + resetForm(); + onComplete(); + onOpenChange(false); + } else { + throw new Error("Action failed"); + } + + } catch (error) { + console.error("상세도면 처리 실패:", error); + toast.error(mode === "add" ? t("addDetailDialog.addErrorMessage") : t("editDetailDialog.editErrorMessage")); + } finally { + setIsSubmitting(false); + } + }; + + // DrawingUsage가 변경되면 RegisterKind 초기화 + const handleDrawingUsageChange = (value: string) => { + setDrawingUsage(value); + setRegisterKind(""); + setRevision(""); + setRevisionError(""); + }; + + // 선택된 RegisterKind의 Revision Rule + const revisionRule = registerKindOptions.find((opt) => opt.value === registerKind)?.revisionRule || ""; + + // 버튼 활성화 조건 + const isFormValid = mode === "add" + ? drawingUsage.trim() !== "" && + registerKind.trim() !== "" && + (drawingUsage === "CMT" || (revision.trim() !== "" && !revisionError)) && + files.length > 0 + : registerKind.trim() !== "" && + (drawingUsage === "CMT" || (revision.trim() !== "" && !revisionError)); + + return ( + + + + + {mode === "edit" ? t("editDetailDialog.title") : t("addDetailDialog.title")} + + + +
+ {/* 도면 정보 표시 */} + {mode === "add" && drawing && ( + + + +
{drawing.DrawingNo}
+
{drawing.DrawingName}
+
+
+ )} + + {mode === "edit" && detailDrawing && ( + + + +
{detailDrawing.DrawingNo} - Rev. {detailDrawing.DrawingRevNo}
+
{detailDrawing.DrawingName}
+
+
+ )} + + {/* 도면용도 선택 (Add 모드에서만 표시) */} + {mode === "add" && ( +
+ + +
+ )} + + {/* 등록종류 선택 */} +
+ + + {revisionRule && ( +

+ {t("addDetailDialog.revisionFormatPrefix")}{revisionRule} +

+ )} +
+ + {/* Revision 입력 */} + {drawingUsage !== "CMT" && ( +
+ + handleRevisionChange(e.target.value)} + placeholder={t("addDetailDialog.revisionPlaceholder")} + disabled={!registerKind} + className={revisionError ? "border-red-500 focus-visible:ring-red-500" : ""} + /> + {revisionError && ( +

+ {revisionError} +

+ )} + {!revisionError && revision && ( +

+ {t("addDetailDialog.revisionValid")} +

+ )} +
+ )} + + {/* Comment 입력 */} +
+ +