summaryrefslogtreecommitdiff
path: root/lib/dolce-v2/dialogs/add-and-modify-detail-drawing-dialog-v2.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dolce-v2/dialogs/add-and-modify-detail-drawing-dialog-v2.tsx')
-rw-r--r--lib/dolce-v2/dialogs/add-and-modify-detail-drawing-dialog-v2.tsx568
1 files changed, 568 insertions, 0 deletions
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..7577d133
--- /dev/null
+++ b/lib/dolce-v2/dialogs/add-and-modify-detail-drawing-dialog-v2.tsx
@@ -0,0 +1,568 @@
+"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<string>("");
+ const [registerKind, setRegisterKind] = useState<string>("");
+ const [revision, setRevision] = useState<string>("");
+ const [revisionError, setRevisionError] = useState<string>("");
+ const [comment, setComment] = useState<string>("");
+ 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 = () => {
+ 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() !== "" &&
+ (drawingUsage === "CMT" || (revision.trim() !== "" && !revisionError)) &&
+ files.length > 0
+ : registerKind.trim() !== "" &&
+ (drawingUsage === "CMT" || (revision.trim() !== "" && !revisionError));
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="max-w-2xl">
+ <DialogHeader>
+ <DialogTitle>
+ {mode === "edit" ? t("editDetailDialog.title") : t("addDetailDialog.title")}
+ </DialogTitle>
+ </DialogHeader>
+
+ <div className="space-y-6">
+ {/* 도면 정보 표시 */}
+ {mode === "add" && drawing && (
+ <Alert>
+ <Info className="h-4 w-4" />
+ <AlertDescription>
+ <div className="font-medium">{drawing.DrawingNo}</div>
+ <div className="text-sm text-muted-foreground">{drawing.DrawingName}</div>
+ </AlertDescription>
+ </Alert>
+ )}
+
+ {mode === "edit" && detailDrawing && (
+ <Alert>
+ <Info className="h-4 w-4" />
+ <AlertDescription>
+ <div className="font-medium">{detailDrawing.DrawingNo} - Rev. {detailDrawing.DrawingRevNo}</div>
+ <div className="text-sm text-muted-foreground">{detailDrawing.DrawingName}</div>
+ </AlertDescription>
+ </Alert>
+ )}
+
+ {/* 도면용도 선택 (Add 모드에서만 표시) */}
+ {mode === "add" && (
+ <div className="space-y-2">
+ <Label>{t("addDetailDialog.drawingUsageLabel")}</Label>
+ <Select value={drawingUsage} onValueChange={handleDrawingUsageChange}>
+ <SelectTrigger>
+ <SelectValue placeholder={t("addDetailDialog.drawingUsagePlaceholder")} />
+ </SelectTrigger>
+ <SelectContent>
+ {drawingUsageOptions.map((option) => (
+ <SelectItem key={option.value} value={option.value}>
+ {option.label}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+ )}
+
+ {/* 등록종류 선택 */}
+ <div className="space-y-2">
+ <Label>{t("addDetailDialog.registerKindLabel")}</Label>
+ <Select
+ value={registerKind}
+ onValueChange={setRegisterKind}
+ disabled={mode === "add" && !drawingUsage}
+ >
+ <SelectTrigger>
+ <SelectValue placeholder={t("addDetailDialog.registerKindPlaceholder")} />
+ </SelectTrigger>
+ <SelectContent>
+ {registerKindOptions.map((option) => (
+ <SelectItem key={option.value} value={option.value}>
+ {option.label}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ {revisionRule && (
+ <p className="text-sm text-muted-foreground">
+ {t("addDetailDialog.revisionFormatPrefix")}{revisionRule}
+ </p>
+ )}
+ </div>
+
+ {/* Revision 입력 */}
+ {drawingUsage !== "CMT" && (
+ <div className="space-y-2">
+ <Label>{t("addDetailDialog.revisionLabel")}</Label>
+ <Input
+ value={revision}
+ onChange={(e) => handleRevisionChange(e.target.value)}
+ placeholder={t("addDetailDialog.revisionPlaceholder")}
+ disabled={!registerKind}
+ className={revisionError ? "border-red-500 focus-visible:ring-red-500" : ""}
+ />
+ {revisionError && (
+ <p className="text-sm text-red-500 flex items-center gap-1">
+ {revisionError}
+ </p>
+ )}
+ {!revisionError && revision && (
+ <p className="text-sm text-green-600 flex items-center gap-1">
+ {t("addDetailDialog.revisionValid")}
+ </p>
+ )}
+ </div>
+ )}
+
+ {/* Comment 입력 */}
+ <div className="space-y-2">
+ <Label>{t("addDetailDialog.commentLabel")}</Label>
+ <Textarea
+ value={comment}
+ onChange={(e) => setComment(e.target.value)}
+ placeholder={t("addDetailDialog.commentPlaceholder")}
+ rows={3}
+ className="resize-none"
+ />
+ <p className="text-xs text-muted-foreground">
+ {t("addDetailDialog.commentMaxLength")}
+ </p>
+ </div>
+
+ {/* 파일 업로드 (Add 모드에서만 표시) */}
+ {mode === "add" && (
+ <div className="space-y-2">
+ <Label>{t("addDetailDialog.attachmentLabel")}</Label>
+ <div
+ {...getRootProps()}
+ className={`
+ border-2 border-dashed rounded-lg p-8 text-center cursor-pointer
+ transition-colors
+ ${isDragActive ? "border-primary bg-primary/5" : "border-muted-foreground/25"}
+ ${files.length > 0 ? "py-4" : ""}
+ `}
+ >
+ <input {...getInputProps()} />
+ {files.length === 0 ? (
+ <div className="space-y-2">
+ <Upload className="h-8 w-8 mx-auto text-muted-foreground" />
+ <div>
+ <p className="text-sm font-medium">
+ {t("addDetailDialog.dragDropText")}
+ </p>
+ <p className="text-xs text-muted-foreground">
+ {t("addDetailDialog.fileInfo")}
+ </p>
+ </div>
+ </div>
+ ) : (
+ <div className="space-y-2">
+ <p className="text-sm font-medium">
+ {t("addDetailDialog.filesSelected", { count: files.length })}
+ </p>
+ <p className="text-xs text-muted-foreground">
+ {t("addDetailDialog.addMoreFiles")}
+ </p>
+ </div>
+ )}
+ </div>
+
+ {/* 선택된 파일 목록 */}
+ {files.length > 0 && (
+ <div className="space-y-2 mt-4">
+ <div className="flex items-center justify-between mb-2">
+ <h4 className="text-sm font-medium">
+ {t("addDetailDialog.selectedFiles", { count: files.length })}
+ </h4>
+ <Button
+ variant="ghost"
+ size="sm"
+ onClick={clearFiles}
+ >
+ {t("addDetailDialog.removeAll")}
+ </Button>
+ </div>
+ <div className="max-h-60 overflow-y-auto space-y-2">
+ {files.map((file, index) => (
+ <div
+ key={index}
+ className="flex items-center gap-2 p-2 border rounded-lg bg-muted/50"
+ >
+ <FileIcon className="h-4 w-4 text-muted-foreground shrink-0" />
+ <div className="flex-1 min-w-0">
+ <p className="text-sm truncate">{file.name}</p>
+ <p className="text-xs text-muted-foreground">
+ {(file.size / 1024 / 1024).toFixed(2)} MB
+ </p>
+ </div>
+ <Button
+ variant="ghost"
+ size="sm"
+ onClick={() => removeFile(index)}
+ >
+ <X className="h-4 w-4" />
+ </Button>
+ </div>
+ ))}
+ </div>
+ </div>
+ )}
+ </div>
+ )}
+ </div>
+
+ <DialogFooter className="sm:justify-between">
+ {mode === "edit" && detailDrawing?.Status === "EVCP Saved" && (
+ <Button variant="destructive" onClick={handleDelete} disabled={isSubmitting} type="button">
+ {isSubmitting ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Trash2 className="mr-2 h-4 w-4" />}
+ {lng === "ko" ? "삭제" : "Delete"}
+ </Button>
+ )}
+ <div className="flex gap-2 justify-end w-full">
+ <Button variant="outline" onClick={handleCancel} disabled={isSubmitting}>
+ {t("addDetailDialog.cancelButton")}
+ </Button>
+ <Button onClick={handleSubmit} disabled={isSubmitting || !isFormValid}>
+ {isSubmitting
+ ? <><Loader2 className="mr-2 h-4 w-4 animate-spin" />{t("addDetailDialog.processingButton")}</>
+ : mode === "edit"
+ ? t("editDetailDialog.updateButton")
+ : t("addDetailDialog.addButton")
+ }
+ </Button>
+ </div>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ );
+}
+