diff options
Diffstat (limited to 'lib/dolce/dialogs/add-detail-drawing-dialog.tsx')
| -rw-r--r-- | lib/dolce/dialogs/add-detail-drawing-dialog.tsx | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/lib/dolce/dialogs/add-detail-drawing-dialog.tsx b/lib/dolce/dialogs/add-detail-drawing-dialog.tsx new file mode 100644 index 00000000..290a226b --- /dev/null +++ b/lib/dolce/dialogs/add-detail-drawing-dialog.tsx @@ -0,0 +1,376 @@ +"use client"; + +import { useState, useCallback } from "react"; +import { useDropzone } from "react-dropzone"; +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 { Upload, X, FileIcon, Info } from "lucide-react"; +import { toast } from "sonner"; +import { UnifiedDwgReceiptItem, editDetailDwgReceipt, uploadFilesToDetailDrawing } from "../actions"; +import { v4 as uuidv4 } from "uuid"; + +interface AddDetailDrawingDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + drawing: UnifiedDwgReceiptItem | null; + vendorCode: string; + userId: string; + userName: string; + userEmail: string; + onComplete: () => void; + drawingKind: "B3" | "B4"; // 추가 +} + +// B3 벤더의 선택 옵션 +const B3_DRAWING_USAGE_OPTIONS = [ + { value: "APP", label: "APPROVAL (승인용)" }, + { value: "WOR", label: "WORKING (작업용)" }, +]; + +const B3_REGISTER_KIND_OPTIONS: Record<string, Array<{ value: string; label: string; revisionRule: string }>> = { + APP: [ + { value: "APPR", label: "승인용 도면 (Full)", revisionRule: "예: A, B, C 또는 R00, R01, R02" }, + { value: "APPR-P", label: "승인용 도면 (Partial)", revisionRule: "예: A, B, C 또는 R00, R01, R02" }, + ], + WOR: [ + { value: "WORK", label: "작업용 입수도면 (Full)", revisionRule: "예: A, B, C 또는 R00, R01, R02" }, + { value: "WORK-P", label: "작업용 입수도면 (Partial)", revisionRule: "예: A, B, C 또는 R00, R01, R02" }, + ], +}; + +// B4 벤더(GTT)의 선택 옵션 +const B4_DRAWING_USAGE_OPTIONS = [ + { value: "REC", label: "RECEIVE (입수용)" }, +]; + +const B4_REGISTER_KIND_OPTIONS: Record<string, Array<{ value: string; label: string; revisionRule: string }>> = { + REC: [ + { value: "RECP", label: "Pre. 도면입수", revisionRule: "예: R00, R01, R02, R03" }, + { value: "RECW", label: "Working 도면입수", revisionRule: "예: R00, R01, R02, R03" }, + ], +}; + +export function AddDetailDrawingDialog({ + open, + onOpenChange, + drawing, + vendorCode, + userId, + userName, + userEmail, + onComplete, + drawingKind, +}: AddDetailDrawingDialogProps) { + const [drawingUsage, setDrawingUsage] = useState<string>(""); + const [registerKind, setRegisterKind] = useState<string>(""); + const [revision, setRevision] = useState<string>(""); + const [files, setFiles] = useState<File[]>([]); + const [isSubmitting, setIsSubmitting] = useState(false); + + // 파일 드롭 핸들러 + const onDrop = useCallback((acceptedFiles: File[]) => { + setFiles((prev) => [...prev, ...acceptedFiles]); + }, []); + + const { getRootProps, getInputProps, isDragActive } = useDropzone({ + onDrop, + multiple: true, + }); + + // 파일 제거 + const removeFile = (index: number) => { + setFiles((prev) => prev.filter((_, i) => i !== index)); + }; + + // 폼 초기화 + const resetForm = () => { + setDrawingUsage(""); + setRegisterKind(""); + setRevision(""); + setFiles([]); + }; + + // 제출 + const handleSubmit = async () => { + if (!drawing) return; + + // 유효성 검사 + if (!drawingUsage) { + toast.error("도면용도를 선택하세요"); + return; + } + if (!registerKind) { + toast.error("등록종류를 선택하세요"); + return; + } + if (!revision.trim()) { + toast.error("Revision을 입력하세요"); + return; + } + if (files.length === 0) { + toast.error("최소 1개 이상의 파일을 첨부해야 합니다"); + return; + } + + try { + setIsSubmitting(true); + + // 파일 업로드 ID 생성 + const uploadId = uuidv4(); + + // 상세도면 추가 + const result = await editDetailDwgReceipt({ + dwgList: [ + { + Mode: "ADD", + Status: "Draft", + 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: "", + UploadId: uploadId, + RegCompanyCode: vendorCode, + }, + ], + userId, + userNm: userName, + vendorCode, + email: userEmail, + }); + + if (result > 0) { + // 파일 업로드 처리 (상세도면 추가 후) + if (files.length > 0) { + toast.info(`${files.length}개 파일 업로드를 진행합니다...`); + + const formData = new FormData(); + formData.append("uploadId", uploadId); + formData.append("userId", userId); + formData.append("fileCount", String(files.length)); + + files.forEach((file, index) => { + formData.append(`file_${index}`, file); + }); + + const uploadResult = await uploadFilesToDetailDrawing(formData); + + if (uploadResult.success) { + toast.success(`상세도면 추가 및 ${uploadResult.uploadedCount}개 파일 업로드 완료`); + } else { + toast.warning(`상세도면은 추가되었으나 파일 업로드 실패: ${uploadResult.error}`); + } + } else { + toast.success("상세도면이 추가되었습니다"); + } + + resetForm(); + onComplete(); + } else { + toast.error("상세도면 추가에 실패했습니다"); + } + } catch (error) { + console.error("상세도면 추가 실패:", error); + toast.error("상세도면 추가 중 오류가 발생했습니다"); + } finally { + setIsSubmitting(false); + } + }; + + const handleCancel = () => { + resetForm(); + onOpenChange(false); + }; + + // DrawingUsage가 변경되면 RegisterKind 초기화 + const handleDrawingUsageChange = (value: string) => { + setDrawingUsage(value); + setRegisterKind(""); + }; + + // 현재 선택 가능한 DrawingUsage 및 RegisterKind 옵션 + const drawingUsageOptions = drawingKind === "B4" ? B4_DRAWING_USAGE_OPTIONS : B3_DRAWING_USAGE_OPTIONS; + const registerKindOptionsMap = drawingKind === "B4" ? B4_REGISTER_KIND_OPTIONS : B3_REGISTER_KIND_OPTIONS; + + const registerKindOptions = drawingUsage + ? registerKindOptionsMap[drawingUsage] || [] + : []; + + // 선택된 RegisterKind의 Revision Rule + const revisionRule = registerKindOptions.find((opt) => opt.value === registerKind)?.revisionRule || ""; + + return ( + <Dialog open={open} onOpenChange={onOpenChange}> + <DialogContent className="max-w-2xl"> + <DialogHeader> + <DialogTitle>상세도면 추가</DialogTitle> + </DialogHeader> + + <div className="space-y-6"> + {/* 도면 정보 표시 */} + {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> + )} + + {/* 도면용도 선택 */} + <div className="space-y-2"> + <Label>도면용도 (Drawing Usage)</Label> + <Select value={drawingUsage} onValueChange={handleDrawingUsageChange}> + <SelectTrigger> + <SelectValue placeholder="도면용도를 선택하세요" /> + </SelectTrigger> + <SelectContent> + {drawingUsageOptions.map((option) => ( + <SelectItem key={option.value} value={option.value}> + {option.label} + </SelectItem> + ))} + </SelectContent> + </Select> + </div> + + {/* 등록종류 선택 */} + <div className="space-y-2"> + <Label>등록종류 (Register Kind)</Label> + <Select + value={registerKind} + onValueChange={setRegisterKind} + disabled={!drawingUsage} + > + <SelectTrigger> + <SelectValue placeholder="등록종류를 선택하세요" /> + </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"> + Revision 입력 형식: {revisionRule} + </p> + )} + </div> + + {/* Revision 입력 */} + <div className="space-y-2"> + <Label>Revision</Label> + <Input + value={revision} + onChange={(e) => setRevision(e.target.value)} + placeholder="예: A, B, R00, R01" + disabled={!registerKind} + /> + </div> + + {/* 파일 업로드 */} + <div className="space-y-2"> + <Label>첨부파일 (필수) *</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"> + 파일을 드래그하거나 클릭하여 선택 + </p> + <p className="text-xs text-muted-foreground"> + 여러 파일을 한 번에 업로드할 수 있습니다 + </p> + </div> + </div> + ) : ( + <div className="space-y-2"> + <p className="text-sm font-medium"> + {files.length}개 파일 선택됨 + </p> + <p className="text-xs text-muted-foreground"> + 추가로 파일을 드래그하거나 클릭하여 더 추가할 수 있습니다 + </p> + </div> + )} + </div> + + {/* 선택된 파일 목록 */} + {files.length > 0 && ( + <div className="space-y-2 mt-4"> + {files.map((file, index) => ( + <div + key={index} + className="flex items-center gap-2 p-2 border rounded-lg" + > + <FileIcon className="h-4 w-4 text-muted-foreground" /> + <span className="flex-1 text-sm truncate">{file.name}</span> + <span className="text-xs text-muted-foreground"> + {(file.size / 1024).toFixed(2)} KB + </span> + <Button + variant="ghost" + size="icon" + onClick={() => removeFile(index)} + > + <X className="h-4 w-4" /> + </Button> + </div> + ))} + </div> + )} + </div> + </div> + + <DialogFooter> + <Button variant="outline" onClick={handleCancel} disabled={isSubmitting}> + 취소 + </Button> + <Button onClick={handleSubmit} disabled={isSubmitting}> + {isSubmitting ? "처리 중..." : "추가"} + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + ); +} + |
