"use client"; // 개발관리 > PART 등록/수정 다이얼로그 — wace partMng/partMngFormPopUp.jsp 1:1 // // 폼 필드 (운영판 그대로, 그 외 추가 없음): // ① 품번 | 품명 // ② 재료 | 열처리경도 // ③ 열처리방법 | 표면처리 // ④ 메이커 | 범주이름* (PART_TYPE, comm_code 0000062) // ⑤ 규격 (1행) // ⑥ 계정구분* (ACCTFG, comm_code 0900213) | 조달구분* (ODRFG, 0/1/8 하드) // ⑦ 재고단위* (UNIT_DC, comm_code 0001399) | 관리단위* (UNITMANG_DC, 동일) // ⑧ 환산수량* (UNITCHNG_NB, 숫자) | LOT구분* (0=미사용, 1=사용) // ⑨ 사용여부* (0=미사용, 1=사용) | 검사여부* (0=무검사, 1=검사) // ⑩ SET품여부* (0=부, 1=여) | 의뢰여부* (0=부, 1=여) // ⑪ 개당길이 | 개당소요량 // ⑫ 비고 (1행) // ⑬ CAD Data: 3D / 2D(Drawing) / 2D(PDF) Drag&Drop — wace fnc_setFileDropZone 3종 1:1 (DEV-7) // // 신규: POST /api/development/part (운영 폼 22컬럼) — part_objid 선채번해서 전달 // (도면이 PART INSERT 전에 attach_file_info 로 먼저 들어갈 수 있으므로 wace resultMap.OBJID 패턴) // 수정: PUT /api/development/part/:objid (wace updatePartDetail 21컬럼 1:1) import React, { useCallback, useEffect, useState } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Loader2, Save } from "lucide-react"; import { toast } from "sonner"; import { CommCodeSelect } from "@/components/common/CommCodeSelect"; import { devPartApi, PartCreateBody, PartUpdateBody, PartRow } from "@/lib/api/devPart"; import { cn } from "@/lib/utils"; import { createObjId } from "@/lib/utils/objidUtil"; import { AttachFileDropZone } from "@/components/common/AttachFileDropZone"; // comm_code group ids (vexplor_rps DB) const GROUP_PART_TYPE = "0000062"; const GROUP_UNIT_DC = "0001399"; const GROUP_ACCTFG = "0900213"; // 운영 1:1 하드코딩 옵션 const OPT_ODRFG = [{ v: "0", t: "구매" }, { v: "1", t: "생산" }, { v: "8", t: "Phantom" }]; const OPT_LOT_FG = [{ v: "0", t: "미사용" }, { v: "1", t: "사용" }]; const OPT_USE_YN = [{ v: "0", t: "미사용" }, { v: "1", t: "사용" }]; const OPT_QC_FG = [{ v: "0", t: "무검사" }, { v: "1", t: "검사" }]; const OPT_YESNO = [{ v: "0", t: "부" }, { v: "1", t: "여" }]; interface FormState { part_no: string; part_name: string; material: string; heat_treatment_hardness: string; heat_treatment_method: string; surface_treatment: string; maker: string; part_type: string; spec: string; acctfg: string; odrfg: string; unit_dc: string; unitmang_dc: string; unitchng_nb: string; lot_fg: string; use_yn: string; qc_fg: string; setitem_fg: string; req_fg: string; unit_length: string; unit_qty: string; remark: string; } const EMPTY_FORM: FormState = { part_no: "", part_name: "", material: "", heat_treatment_hardness: "", heat_treatment_method: "", surface_treatment: "", maker: "", part_type: "", spec: "", acctfg: "", odrfg: "", unit_dc: "", unitmang_dc: "", unitchng_nb: "", lot_fg: "", use_yn: "", qc_fg: "", setitem_fg: "", req_fg: "", unit_length: "", unit_qty: "", remark: "", }; interface Props { open: boolean; onOpenChange: (open: boolean) => void; mode: "create" | "edit"; editObjid?: string | null; onSaved: () => void; } export function PartFormDialog({ open, onOpenChange, mode, editObjid, onSaved }: Props) { const isEdit = mode === "edit"; const [form, setForm] = useState(EMPTY_FORM); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); // CAD Data 업로드 target_objid: // - 수정 모드: editObjid 그대로 // - 신규 모드: 다이얼로그 열릴 때 createObjId() 로 선채번 (wace partMngFormPopUp resultMap.OBJID 패턴) const [partObjid, setPartObjid] = useState(null); const setField = useCallback( (key: K, value: FormState[K]) => setForm((prev) => ({ ...prev, [key]: value })), [] ); useEffect(() => { if (!open) { setPartObjid(null); return; } if (isEdit && editObjid) { setPartObjid(editObjid); loadDetail(editObjid); } else { setForm(EMPTY_FORM); setPartObjid(createObjId()); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); const loadDetail = async (objid: string) => { setLoading(true); try { const row = await devPartApi.detail(objid); if (!row) { toast.error("PART를 찾을 수 없습니다."); onOpenChange(false); return; } setForm(rowToForm(row)); } catch (e: any) { toast.error(e?.response?.data?.message ?? e?.message ?? "조회 실패"); onOpenChange(false); } finally { setLoading(false); } }; // wace fn_save 1:1 — 모든 required 검증 const validate = (): string | null => { if (!form.part_no.trim()) return "품번은 필수입니다."; if (!form.part_name.trim()) return "품명은 필수입니다."; if (!form.part_type) return "범주이름은 필수입니다."; if (!form.acctfg) return "계정구분은 필수입니다."; if (!form.odrfg) return "조달구분은 필수입니다."; if (!form.unit_dc) return "재고단위는 필수입니다."; if (!form.unitmang_dc) return "관리단위는 필수입니다."; if (!form.unitchng_nb) return "환산수량은 필수입니다."; if (!form.lot_fg) return "LOT구분은 필수입니다."; if (!form.use_yn) return "사용여부는 필수입니다."; if (!form.qc_fg) return "검사여부는 필수입니다."; if (!form.setitem_fg) return "SET품여부는 필수입니다."; if (!form.req_fg) return "의뢰여부는 필수입니다."; return null; }; const handleSave = async () => { const err = validate(); if (err) return toast.error(err); setSaving(true); try { if (isEdit && editObjid) { const body: PartUpdateBody = { part_name: form.part_name, material: form.material, heat_treatment_hardness: form.heat_treatment_hardness, heat_treatment_method: form.heat_treatment_method, surface_treatment: form.surface_treatment, maker: form.maker, part_type: form.part_type, acctfg: form.acctfg, odrfg: form.odrfg, spec: form.spec, unit_dc: form.unit_dc, unitmang_dc: form.unitmang_dc, unitchng_nb: form.unitchng_nb, lot_fg: form.lot_fg, use_yn: form.use_yn, qc_fg: form.qc_fg, setitem_fg: form.setitem_fg, req_fg: form.req_fg, unit_length: form.unit_length, unit_qty: form.unit_qty, remark: form.remark, }; await devPartApi.update(editObjid, body); toast.success("PART가 수정되었습니다."); } else { const body: PartCreateBody = { // CAD Data 도면이 선업로드 되었을 수 있으므로 선채번된 objid 전달 (wace 1:1) part_objid: partObjid ?? undefined, part_no: form.part_no, part_name: form.part_name, part_type: form.part_type, material: form.material, heat_treatment_hardness: form.heat_treatment_hardness, heat_treatment_method: form.heat_treatment_method, surface_treatment: form.surface_treatment, maker: form.maker, spec: form.spec, acctfg: form.acctfg, odrfg: form.odrfg, unit_dc: form.unit_dc, unitmang_dc: form.unitmang_dc, unitchng_nb: form.unitchng_nb, lot_fg: form.lot_fg, use_yn: form.use_yn, qc_fg: form.qc_fg, setitem_fg: form.setitem_fg, req_fg: form.req_fg, unit_length: form.unit_length, unit_qty: form.unit_qty, remark: form.remark, }; await devPartApi.create(body); toast.success("PART가 등록되었습니다."); } onSaved(); onOpenChange(false); } catch (e: any) { toast.error(e?.response?.data?.message ?? e?.message ?? "저장 실패"); } finally { setSaving(false); } }; const titleText = isEdit ? "품목 수정" : "품목 등록"; return ( {titleText} {loading ? (
) : (
{/* 운영판 colgroup 1:1 (12% / 12% / 25% / 12% / *) 각 행 첫 input 은 colSpan=2 로 양쪽 input 비율 맞춤 */} {/* ① */} {/* ② */} {/* ③ */} {/* ④ */} {/* ⑤ 규격 (colspan=4 — 운영판 colspan=4 1:1) */} {/* ⑥ */} {/* ⑦ */} {/* ⑧ */} {/* ⑨ */} {/* ⑩ */} {/* ⑪ */} {/* ⑫ 비고 (colspan=4) */} {/* ⑬ CAD Data — wace fnc_setFileDropZone 3종 1:1 */}
품번 setField("part_no", e.target.value)} /> 품명 setField("part_name", e.target.value)} />
재료 setField("material", e.target.value)} /> 열처리경도 setField("heat_treatment_hardness", e.target.value)} />
열처리방법 setField("heat_treatment_method", e.target.value)} /> 표면처리 setField("surface_treatment", e.target.value)} />
메이커 setField("maker", e.target.value)} /> 범주이름 setField("part_type", v)} />
규격 setField("spec", e.target.value)} />
계정구분 setField("acctfg", v)} /> 조달구분 setField("odrfg", v)} />
재고단위 setField("unit_dc", v)} /> 관리단위 setField("unitmang_dc", v)} />
환산수량 setField("unitchng_nb", e.target.value.replace(/[^0-9.]/g, ""))} /> LOT구분 setField("lot_fg", v)} />
사용여부 setField("use_yn", v)} /> 검사여부 setField("qc_fg", v)} />
SET품여부 setField("setitem_fg", v)} /> 의뢰여부 setField("req_fg", v)} />
개당길이 setField("unit_length", e.target.value.replace(/[^0-9.]/g, ""))} /> 개당소요량 setField("unit_qty", e.target.value.replace(/[^0-9.]/g, ""))} />
비고 setField("remark", e.target.value)} />
CAD Data 3D
2D(Drawing)
2D(PDF)
)}
); } // ─── 보조 컴포넌트 ────────────────────────────────────────── function Tr({ children }: { children: React.ReactNode }) { return {children}; } function Th({ children }: { children: React.ReactNode }) { return ( {children} ); } function Td({ children, colSpan }: { children: React.ReactNode; colSpan?: number }) { return {children}; } function Req() { return *; } function BasicSelect({ value, options, onChange, }: { value: string; options: { v: string; t: string }[]; onChange: (v: string) => void; }) { return ( ); } // ─── PartRow → FormState ──────────────────────────────────── function rowToForm(r: PartRow): FormState { return { part_no: r.part_no ?? "", part_name: r.part_name ?? "", material: r.material ?? "", heat_treatment_hardness: r.heat_treatment_hardness ?? "", heat_treatment_method: r.heat_treatment_method ?? "", surface_treatment: r.surface_treatment ?? "", maker: r.maker ?? "", part_type: r.part_type ?? "", spec: r.spec ?? "", acctfg: r.acctfg ?? "", odrfg: r.odrfg ?? "", unit_dc: r.unit_dc ?? "", unitmang_dc: r.unitmang_dc ?? "", unitchng_nb: r.unitchng_nb != null ? String(r.unitchng_nb) : "", lot_fg: r.lot_fg ?? "", use_yn: r.use_yn ?? "", qc_fg: r.qc_fg ?? "", setitem_fg: r.setitem_fg ?? "", req_fg: r.req_fg ?? "", unit_length: r.unit_length ?? "", unit_qty: r.unit_qty ?? "", remark: r.remark ?? "", }; }