From 4586f2cd95f1cd7112cbec80399da8817df0d289 Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Tue, 25 Nov 2025 15:42:00 +0900 Subject: (김준회) dolce: i18n 적용, 상세도면 수정 적용 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../add-and-modify-detail-drawing-dialog.tsx | 576 +++++++++++++++++++++ 1 file changed, 576 insertions(+) create mode 100644 lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx (limited to 'lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx') diff --git a/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx b/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx new file mode 100644 index 00000000..87819693 --- /dev/null +++ b/lib/dolce/dialogs/add-and-modify-detail-drawing-dialog.tsx @@ -0,0 +1,576 @@ +"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 } from "lucide-react"; +import { toast } from "sonner"; +import { useTranslation } from "@/i18n/client"; +import { UnifiedDwgReceiptItem, DetailDwgReceiptItem, editDetailDwgReceipt } from "../actions"; +import { v4 as uuidv4 } from "uuid"; +import { useFileUploadWithProgress } from "../hooks/use-file-upload-with-progress"; +import { uploadFilesWithProgress } from "../utils/upload-with-progress"; +import { FileUploadProgressList } from "../components/file-upload-progress-list"; +import { + getB3DrawingUsageOptions, + getB3RegisterKindOptions, + getB4DrawingUsageOptions, + getB4RegisterKindOptions +} from "../utils/code-translator"; + +interface AddAndModifyDetailDrawingDialogProps { + 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 AddAndModifyDetailDrawingDialog({ + open, + onOpenChange, + drawing, + vendorCode, + userId, + userName, + userEmail, + onComplete, + drawingKind, + lng, + mode = "add", + detailDrawing = null, +}: AddAndModifyDetailDrawingDialogProps) { + 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 { + fileProgresses, + files, + removeFile, + clearFiles, + updateFileProgress, + 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 (!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); + + if (mode === "add" && drawing) { + // 파일 업로드 ID 생성 + const uploadId = uuidv4(); + + // 상세도면 추가 + const result = await editDetailDwgReceipt({ + 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: revision, + Category: "TS", // To SHI (벤더가 SHI에게 제출) + Receiver: null, + Manager: "", + RegisterDesc: comment, + UploadId: uploadId, + RegCompanyCode: vendorCode, + }, + ], + userId, + userNm: userName, + vendorCode, + email: userEmail, + }); + + if (result > 0) { + // 파일 업로드 처리 (상세도면 추가 후) + if (files.length > 0) { + toast.info(t("addDetailDialog.uploadingFiles", { count: files.length })); + + // 모든 파일 상태를 uploading으로 변경 + files.forEach((_, index) => { + updateFileProgress(index, 0, "uploading"); + }); + + const uploadResult = await uploadFilesWithProgress({ + uploadId, + userId, + files, + callbacks: { + onProgress: (fileIndex, progress) => { + updateFileProgress(fileIndex, progress, "uploading"); + }, + onFileComplete: (fileIndex) => { + updateFileProgress(fileIndex, 100, "completed"); + }, + onFileError: (fileIndex, error) => { + updateFileProgress(fileIndex, 0, "error", error); + }, + }, + }); + + if (uploadResult.success) { + toast.success(t("addDetailDialog.addSuccessWithUpload", { count: uploadResult.uploadedCount })); + } else { + toast.warning(t("addDetailDialog.addSuccessPartialUpload", { error: uploadResult.error })); + } + } else { + toast.success(t("addDetailDialog.addSuccess")); + } + + // API 호출 성공 시 무조건 다이얼로그 닫기 (파일 업로드 성공 여부와 무관) + resetForm(); + onComplete(); + onOpenChange(false); + } else { + toast.error(t("addDetailDialog.addError")); + } + } else if (mode === "edit" && detailDrawing) { + // 상세도면 수정 + const result = await editDetailDwgReceipt({ + 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: revision, + Category: detailDrawing.Category, + Receiver: detailDrawing.Receiver, + Manager: detailDrawing.Manager, + RegisterDesc: comment, + UploadId: detailDrawing.UploadId, + RegCompanyCode: detailDrawing.RegCompanyCode || vendorCode, + }, + ], + userId, + userNm: userName, + vendorCode, + email: userEmail, + }); + + if (result > 0) { + toast.success(t("editDetailDialog.editSuccess")); + resetForm(); + onComplete(); + onOpenChange(false); + } else { + toast.error(t("editDetailDialog.editError")); + } + } + } catch (error) { + console.error("상세도면 처리 실패:", error); + toast.error(mode === "add" ? t("addDetailDialog.addErrorMessage") : t("editDetailDialog.editErrorMessage")); + } finally { + setIsSubmitting(false); + } + }; + + const handleCancel = () => { + resetForm(); + onOpenChange(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() !== "" && + revision.trim() !== "" && + !revisionError && + files.length > 0 + : registerKind.trim() !== "" && + 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 입력 */} +
+ + 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 입력 */} +
+ +