119f0f3f2e
- 공통 컴포넌트: frontend/components/common/AttachFileDropZone.tsx
· wace fnc_setFileDropZone + fn_fileCallback2 + fileDelete 1:1
· /api/files (upload·list·delete·download) attach_file_info 기반
· readOnly 옵션 (Detail 다이얼로그용), accept 옵션, dragenter+dropEffect=copy
· 도메인 무관 — ERP/ECR/생산실적 등 어디서나 재사용
- 프론트 채번 유틸: frontend/lib/utils/objidUtil.ts
· backend objidUtil 1:1 (UUID v4 → Java String.hashCode int32)
· 신규 등록 시 다이얼로그 진입 시점에 part_mng.objid 선채번
(wace partMngFormPopUp resultMap.OBJID 패턴)
- PartFormDialog (M1 신규/수정): CAD Data placeholder 제거,
AttachFileDropZone 3종(3D_CAD / 2D_DRAWING_CAD / 2D_PDF_CAD) 활성.
신규 모드는 createObjId 로 선채번 후 part_objid 로 백엔드 전달.
- PartDetailDialog: CadCount 제거, AttachFileDropZone readOnly 로 교체
(목록·다운로드만, 드롭존/삭제 숨김).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
223 lines
9.6 KiB
TypeScript
223 lines
9.6 KiB
TypeScript
"use client";
|
|
|
|
// 개발관리 > PART 상세 다이얼로그 — wace partMng/partMngDetailPopUp.jsp 1:1
|
|
//
|
|
// 운영판은 form 과 동일 화면을 disabled 로 표시 후 "수정" 클릭 시 활성화.
|
|
// RPS 에서는 PartFormDialog 와 분리 유지 (호환). 본 다이얼로그는 Form 레이아웃 readonly +
|
|
// 부속 정보 행 추가 (EO_NO / EO_DATE / EO구분(CHANGE_TYPE) / EO사유(CHANGE_OPTION)) +
|
|
// CAD Data 영역 (3D / 2D(Drawing) / 2D(PDF)) — AttachFileDropZone readonly (목록·다운로드).
|
|
//
|
|
// "수정" 버튼: 부모가 본 다이얼로그를 닫고 PartFormDialog(mode='edit') 오픈하도록 onEdit 콜백 호출.
|
|
|
|
import React, { useEffect, useState } from "react";
|
|
import {
|
|
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
|
|
} from "@/components/ui/dialog";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Loader2, Pencil } from "lucide-react";
|
|
import { toast } from "sonner";
|
|
import { devPartApi, PartRow } from "@/lib/api/devPart";
|
|
import { cn } from "@/lib/utils";
|
|
import { AttachFileDropZone } from "@/components/common/AttachFileDropZone";
|
|
|
|
const LABEL_ODRFG: Record<string, string> = { "0": "구매", "1": "생산", "8": "Phantom" };
|
|
const LABEL_LOT_FG: Record<string, string> = { "0": "미사용", "1": "사용" };
|
|
const LABEL_USE_YN: Record<string, string> = { "0": "미사용", "1": "사용" };
|
|
const LABEL_QC_FG: Record<string, string> = { "0": "무검사", "1": "검사" };
|
|
const LABEL_YESNO: Record<string, string> = { "0": "부", "1": "여" };
|
|
|
|
interface Props {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
objid: string | null;
|
|
/** "수정" 버튼 클릭 시 호출 — 부모는 본 다이얼로그 닫고 PartFormDialog(mode='edit') 오픈 */
|
|
onEdit?: (objid: string) => void;
|
|
}
|
|
|
|
export function PartDetailDialog({ open, onOpenChange, objid, onEdit }: Props) {
|
|
const [row, setRow] = useState<PartRow | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!open || !objid) return;
|
|
let alive = true;
|
|
setLoading(true);
|
|
devPartApi.detail(objid)
|
|
.then((data) => { if (alive) setRow(data); })
|
|
.catch((e: any) => {
|
|
toast.error(e?.response?.data?.message ?? e?.message ?? "조회 실패");
|
|
onOpenChange(false);
|
|
})
|
|
.finally(() => { if (alive) setLoading(false); });
|
|
return () => { alive = false; };
|
|
}, [open, objid, onOpenChange]);
|
|
|
|
if (!open) return null;
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-[1100px] w-[95vw] max-h-[92vh] flex flex-col p-0 overflow-hidden">
|
|
<DialogHeader className="bg-blue-600 px-4 py-3">
|
|
<DialogTitle className="text-white">품목 상세</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
{loading || !row ? (
|
|
<div className="flex h-64 items-center justify-center">
|
|
<Loader2 className="h-6 w-6 animate-spin" />
|
|
</div>
|
|
) : (
|
|
<div className="flex-1 overflow-y-auto px-4 py-3">
|
|
{/* 운영판 colgroup 1:1 (12% / 12% / 25% / 12% / *) */}
|
|
<table className="w-full border-collapse text-sm table-fixed">
|
|
<colgroup>
|
|
<col style={{ width: "12%" }} />
|
|
<col style={{ width: "12%" }} />
|
|
<col style={{ width: "25%" }} />
|
|
<col style={{ width: "12%" }} />
|
|
<col />
|
|
</colgroup>
|
|
<tbody>
|
|
<Tr>
|
|
<Th>품번</Th><Td colSpan={2}><Ro>{row.part_no}</Ro></Td>
|
|
<Th>품명</Th><Td><Ro>{row.part_name}</Ro></Td>
|
|
</Tr>
|
|
<Tr>
|
|
<Th>재료</Th><Td colSpan={2}><Ro>{row.material}</Ro></Td>
|
|
<Th>열처리경도</Th><Td><Ro>{row.heat_treatment_hardness}</Ro></Td>
|
|
</Tr>
|
|
<Tr>
|
|
<Th>열처리방법</Th><Td colSpan={2}><Ro>{row.heat_treatment_method}</Ro></Td>
|
|
<Th>표면처리</Th><Td><Ro>{row.surface_treatment}</Ro></Td>
|
|
</Tr>
|
|
<Tr>
|
|
<Th>메이커</Th><Td colSpan={2}><Ro>{row.maker}</Ro></Td>
|
|
<Th>범주 이름</Th><Td><Ro>{row.part_type_title}</Ro></Td>
|
|
</Tr>
|
|
<Tr>
|
|
<Th>규격</Th><Td colSpan={4}><Ro>{row.spec}</Ro></Td>
|
|
</Tr>
|
|
<Tr>
|
|
<Th>계정구분</Th><Td colSpan={2}><Ro>{row.acctfg_nm}</Ro></Td>
|
|
<Th>조달구분</Th><Td><Ro>{LABEL_ODRFG[row.odrfg ?? ""] ?? row.odrfg_nm ?? ""}</Ro></Td>
|
|
</Tr>
|
|
<Tr>
|
|
<Th>재고단위</Th><Td colSpan={2}><Ro>{row.unit_dc_nm}</Ro></Td>
|
|
<Th>관리단위</Th><Td><Ro>{row.unitmang_dc_nm}</Ro></Td>
|
|
</Tr>
|
|
<Tr>
|
|
<Th>환산수량</Th>
|
|
<Td colSpan={2}><Ro align="right">{row.unitchng_nb != null && row.unitchng_nb !== "" ? String(row.unitchng_nb) : ""}</Ro></Td>
|
|
<Th>LOT구분</Th><Td><Ro>{LABEL_LOT_FG[row.lot_fg ?? ""] ?? row.lot_fg_nm ?? ""}</Ro></Td>
|
|
</Tr>
|
|
<Tr>
|
|
<Th>사용여부</Th><Td colSpan={2}><Ro>{LABEL_USE_YN[row.use_yn ?? ""] ?? row.use_yn_nm ?? ""}</Ro></Td>
|
|
<Th>검사여부</Th><Td><Ro>{LABEL_QC_FG[row.qc_fg ?? ""] ?? row.qc_fg_nm ?? ""}</Ro></Td>
|
|
</Tr>
|
|
<Tr>
|
|
<Th>SET품여부</Th><Td colSpan={2}><Ro>{LABEL_YESNO[row.setitem_fg ?? ""] ?? row.setitem_fg_nm ?? ""}</Ro></Td>
|
|
<Th>의뢰여부</Th><Td><Ro>{LABEL_YESNO[row.req_fg ?? ""] ?? row.req_fg_nm ?? ""}</Ro></Td>
|
|
</Tr>
|
|
<Tr>
|
|
<Th>개당길이</Th><Td colSpan={2}><Ro align="right">{row.unit_length}</Ro></Td>
|
|
<Th>개당소요량</Th><Td><Ro align="right">{row.unit_qty}</Ro></Td>
|
|
</Tr>
|
|
<Tr>
|
|
<Th>비고</Th><Td colSpan={4}><Ro>{row.remark}</Ro></Td>
|
|
</Tr>
|
|
|
|
{/* 부속 — wace detail 만 표시 */}
|
|
<Tr>
|
|
<Th>EO No</Th><Td colSpan={2}><Ro>{row.eo_no}</Ro></Td>
|
|
<Th>EO Date</Th><Td><Ro>{row.eo_date}</Ro></Td>
|
|
</Tr>
|
|
<Tr>
|
|
<Th>EO구분</Th><Td colSpan={2}><Ro>{row.change_type}</Ro></Td>
|
|
<Th>EO사유</Th><Td><Ro>{row.change_option_name ?? row.change_option}</Ro></Td>
|
|
</Tr>
|
|
|
|
{/* CAD Data — readonly (목록·다운로드만) */}
|
|
<tr>
|
|
<th className="border bg-muted/30 px-3 py-2 text-left align-top font-medium" rowSpan={3}>
|
|
CAD Data
|
|
</th>
|
|
<th className="border bg-muted/30 px-3 py-2 text-left font-medium">3D</th>
|
|
<td className="border px-3 py-2" colSpan={3}>
|
|
<AttachFileDropZone
|
|
targetObjid={row.objid}
|
|
docType="3D_CAD"
|
|
docTypeName="3D CAD 첨부파일"
|
|
readOnly
|
|
/>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th className="border bg-muted/30 px-3 py-2 text-left font-medium">2D(Drawing)</th>
|
|
<td className="border px-3 py-2" colSpan={3}>
|
|
<AttachFileDropZone
|
|
targetObjid={row.objid}
|
|
docType="2D_DRAWING_CAD"
|
|
docTypeName="2D(Drawing) CAD 첨부파일"
|
|
readOnly
|
|
/>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th className="border bg-muted/30 px-3 py-2 text-left font-medium">2D(PDF)</th>
|
|
<td className="border px-3 py-2" colSpan={3}>
|
|
<AttachFileDropZone
|
|
targetObjid={row.objid}
|
|
docType="2D_PDF_CAD"
|
|
docTypeName="2D(PDF) CAD 첨부파일"
|
|
readOnly
|
|
/>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
|
|
<DialogFooter className="border-t bg-muted/20 px-4 py-3 sm:justify-center">
|
|
{row && onEdit && (
|
|
<Button onClick={() => onEdit(row.objid)}>
|
|
<Pencil className="h-4 w-4" /><span className="ml-1">수정</span>
|
|
</Button>
|
|
)}
|
|
<Button variant="outline" onClick={() => onOpenChange(false)}>닫기</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
// ─── 보조 컴포넌트 ──────────────────────────────────────────
|
|
|
|
function Tr({ children }: { children: React.ReactNode }) {
|
|
return <tr>{children}</tr>;
|
|
}
|
|
function Th({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<th className="border bg-muted/30 px-3 py-2 text-left align-middle font-medium">
|
|
{children}
|
|
</th>
|
|
);
|
|
}
|
|
function Td({ children, colSpan }: { children: React.ReactNode; colSpan?: number }) {
|
|
return <td className="border px-3 py-1.5" colSpan={colSpan}>{children}</td>;
|
|
}
|
|
|
|
// 운영판 disabled input 의 readonly 박스
|
|
function Ro({ children, align }: { children: React.ReactNode; align?: "left" | "center" | "right" }) {
|
|
const cls = align === "right" ? "text-right" : align === "center" ? "text-center" : "text-left";
|
|
const empty = children == null || children === "";
|
|
return (
|
|
<div className={cn(
|
|
"min-h-9 rounded border bg-muted/30 px-2 py-1.5",
|
|
cls,
|
|
)}>
|
|
{empty ? <span className="text-muted-foreground">—</span> : children}
|
|
</div>
|
|
);
|
|
}
|
|
|