개발관리>PART 상세 — wace partMngDetailPopUp.jsp 1:1 재작성 + colgroup 비율 정정

PartDetailDialog 재작성 (운영판 부적합 → 정정):
- 기존: 임의 섹션 분리("기본정보/크기·형상/분류·단위/Y-N 플래그") + 운영판 없는 필드
  (두께/너비/높이/외경/내경/길이/단위/수량/후가공/공급업체) 노출
- 정정: PartFormDialog 와 동일 레이아웃을 readonly 박스(Ro)로 표시
  · 운영판 22필드 그대로 (운영판 없는 9필드 제거)
  · 부속 행 추가 (운영 detail 만 표시) : EO No / EO Date / EO구분(CHANGE_TYPE) / EO사유(CHANGE_OPTION)
  · CAD Data 3행 : 3D / 2D(Drawing) / 2D(PDF) — 첨부 카운트 표시 (실제 파일 다운로드는 DEV-7 별 PR)
  · 헤더: 파란색 바 "품목 상세" (운영판 헤더 1:1)
  · 코드값 → 라벨 매핑 : ODRFG (구매/생산/Phantom), LOT_FG (미사용/사용),
                          USE_YN (미사용/사용), QC_FG (무검사/검사), SETITEM/REQ (부/여)

colgroup 비율 정정 (Form + Detail 공통):
- 기존: 4컬럼 단순 (라벨 110px / input / 라벨 110px / input) → 첫 input 25% 좁음
- 정정: 운영판 JSP colgroup 1:1 (12% / 12% / 25% / 12% / *) + 매 행 첫 Td 에 colSpan={2}
  · 결과 input1 ≈ 37%, input2 ≈ 39% — 양쪽 input 비율 거의 동일 (운영판 일치)
  · 규격/비고 행 colSpan={4} (운영 JSP colspan=4)
  · CAD Data 행 colSpan={3} (작은 라벨 1칸 + 컨텐츠 3칸)
  · Th 의 고정 너비 w-[110px] 제거 — colgroup 이 폭 결정
  · table-fixed 추가로 colgroup 비율 강제 적용

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hjjeong
2026-05-12 18:32:23 +09:00
parent 82a253ec6a
commit 5662fd2910
2 changed files with 176 additions and 125 deletions
@@ -1,24 +1,35 @@
"use client"; "use client";
// 개발관리 > PART 상세 조회 다이얼로그 (read-only). // 개발관리 > PART 상세 다이얼로그 — wace partMng/partMngDetailPopUp.jsp 1:1
// 행 더블클릭 진입. "수정" 버튼 클릭 시 PartFormDialog(mode='edit')로 전환은 //
// 호출 페이지가 dispatch (open=false → 부모가 form dialog 오픈). // 운영판은 form 과 동일 화면을 disabled 로 표시 후 "수정" 클릭 시 활성화.
// RPS 에서는 PartFormDialog 와 분리 유지 (호환). 본 다이얼로그는 Form 레이아웃 readonly +
// 부속 정보 행 추가 (EO_NO / EO_DATE / EO구분(CHANGE_TYPE) / EO사유(CHANGE_OPTION)) +
// CAD Data 영역 (3D / 2D(Drawing) / 2D(PDF)) 표시.
//
// "수정" 버튼: 부모가 본 다이얼로그를 닫고 PartFormDialog(mode='edit') 오픈하도록 onEdit 콜백 호출.
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { import {
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label"; import { Loader2, Pencil, FileText } from "lucide-react";
import { Loader2, Pencil } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { devPartApi, PartRow } from "@/lib/api/devPart"; import { devPartApi, PartRow } from "@/lib/api/devPart";
import { cn } from "@/lib/utils";
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 { interface Props {
open: boolean; open: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
objid: string | null; objid: string | null;
/** "수정" 버튼 클릭 시 호출 — 부모는 본 다이얼로그 닫고 PartFormDialog(mode='edit')를 띄움 */ /** "수정" 버튼 클릭 시 호출 — 부모는 본 다이얼로그 닫고 PartFormDialog(mode='edit') 오픈 */
onEdit?: (objid: string) => void; onEdit?: (objid: string) => void;
} }
@@ -44,9 +55,9 @@ export function PartDetailDialog({ open, onOpenChange, objid, onEdit }: Props) {
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-5xl"> <DialogContent className="max-w-[1100px] w-[95vw] max-h-[92vh] flex flex-col p-0 overflow-hidden">
<DialogHeader> <DialogHeader className="bg-blue-600 px-4 py-3">
<DialogTitle>PART </DialogTitle> <DialogTitle className="text-white"> </DialogTitle>
</DialogHeader> </DialogHeader>
{loading || !row ? ( {loading || !row ? (
@@ -54,89 +65,107 @@ export function PartDetailDialog({ open, onOpenChange, objid, onEdit }: Props) {
<Loader2 className="h-6 w-6 animate-spin" /> <Loader2 className="h-6 w-6 animate-spin" />
</div> </div>
) : ( ) : (
<div className="max-h-[70vh] space-y-4 overflow-y-auto px-1 py-2 text-sm"> <div className="flex-1 overflow-y-auto px-4 py-3">
<Section title="기본정보"> {/* 운영판 colgroup 1:1 (12% / 12% / 25% / 12% / *) */}
<Row> <table className="w-full border-collapse text-sm table-fixed">
<V label="품번" value={row.part_no} /> <colgroup>
<V label="품명" value={row.part_name} /> <col style={{ width: "12%" }} />
<V label="범주" value={row.part_type_title} /> <col style={{ width: "12%" }} />
</Row> <col style={{ width: "25%" }} />
<Row> <col style={{ width: "12%" }} />
<V label="단위" value={row.unit_title} /> <col />
<V label="수량" value={row.qty} align="right" /> </colgroup>
<V label="규격" value={row.spec} /> <tbody>
</Row> <Tr>
<Row> <Th></Th><Td colSpan={2}><Ro>{row.part_no}</Ro></Td>
<V label="재료" value={row.material} /> <Th></Th><Td><Ro>{row.part_name}</Ro></Td>
<V label="메이커" value={row.maker} /> </Tr>
<V label="비고" value={row.remark} /> <Tr>
</Row> <Th></Th><Td colSpan={2}><Ro>{row.material}</Ro></Td>
<Row> <Th></Th><Td><Ro>{row.heat_treatment_hardness}</Ro></Td>
<V label="공급업체" value={row.supply_name} /> </Tr>
<V label="등록자" value={row.writer} /> <Tr>
<V label="등록일" value={row.part_regdate_title} /> <Th></Th><Td colSpan={2}><Ro>{row.heat_treatment_method}</Ro></Td>
</Row> <Th></Th><Td><Ro>{row.surface_treatment}</Ro></Td>
<Row> </Tr>
<V label="REVISION" value={row.revision} /> <Tr>
<V label="EO_NO" value={row.eo_no} /> <Th></Th><Td colSpan={2}><Ro>{row.maker}</Ro></Td>
<V label="STATUS" value={row.status} /> <Th> </Th><Td><Ro>{row.part_type_title}</Ro></Td>
</Row> </Tr>
</Section> <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>
<Section title="크기 / 형상"> {/* 부속 — wace detail 만 표시 */}
<Row> <Tr>
<V label="두께" value={row.thickness} align="right" /> <Th>EO No</Th><Td colSpan={2}><Ro>{row.eo_no}</Ro></Td>
<V label="너비(W)" value={row.width} align="right" /> <Th>EO Date</Th><Td><Ro>{row.eo_date}</Ro></Td>
<V label="높이(H)" value={row.height} align="right" /> </Tr>
</Row> <Tr>
<Row> <Th>EO구분</Th><Td colSpan={2}><Ro>{row.change_type}</Ro></Td>
<V label="외경" value={row.out_diameter} align="right" /> <Th>EO사유</Th><Td><Ro>{row.change_option_name ?? row.change_option}</Ro></Td>
<V label="내경" value={row.in_diameter} align="right" /> </Tr>
<V label="길이(L)" value={row.length} align="right" />
</Row>
</Section>
<Section title="분류 / 단위"> {/* CAD Data */}
<Row> <tr>
<V label="계정구분" value={row.acctfg_nm} /> <th className="border bg-muted/30 px-3 py-2 text-left align-top font-medium" rowSpan={3}>
<V label="조달구분" value={row.odrfg_nm} /> CAD Data
<V label="환산수량" value={row.unitchng_nb != null ? String(row.unitchng_nb) : ""} align="right" /> </th>
</Row> <th className="border bg-muted/30 px-3 py-2 text-left font-medium">3D</th>
<Row> <td className="border px-3 py-2" colSpan={3}>
<V label="재고단위" value={row.unit_dc_nm} /> <CadCount label="3D" count={Number(row.cu01_cnt ?? 0)} />
<V label="관리단위" value={row.unitmang_dc_nm} /> </td>
<V label="개당길이 / 개당수량" </tr>
value={[row.unit_length, row.unit_qty].filter(Boolean).join(" / ")} align="right" /> <tr>
</Row> <th className="border bg-muted/30 px-3 py-2 text-left font-medium">2D(Drawing)</th>
<Row> <td className="border px-3 py-2" colSpan={3}>
<V label="열처리경도" value={row.heat_treatment_hardness} /> <CadCount label="2D(Drawing)" count={Number(row.cu02_cnt ?? 0)} />
<V label="열처리방법" value={row.heat_treatment_method} /> </td>
<V label="표면처리" value={row.surface_treatment} /> </tr>
</Row> <tr>
<Row> <th className="border bg-muted/30 px-3 py-2 text-left font-medium">2D(PDF)</th>
<V label="후가공" value={row.post_processing} /> <td className="border px-3 py-2" colSpan={3}>
<V label="첨부 (3D/2D/PDF)" <CadCount label="2D(PDF)" count={Number(row.cu03_cnt ?? 0)} />
value={`${row.cu01_cnt ?? 0} / ${row.cu02_cnt ?? 0} / ${row.cu03_cnt ?? 0}`} align="center" /> </td>
<V label="" value="" /> </tr>
</Row> </tbody>
</Section> </table>
<Section title="Y/N 플래그"> <div className="mt-2 text-[11px] text-muted-foreground">
<Row> CAD Data / DEV-7 () PR .
<V label="LOT구분" value={row.lot_fg_nm} align="center" /> </div>
<V label="사용여부" value={row.use_yn_nm} align="center" />
<V label="검사여부" value={row.qc_fg_nm} align="center" />
</Row>
<Row>
<V label="SET품여부" value={row.setitem_fg_nm} align="center" />
<V label="의뢰여부" value={row.req_fg_nm} align="center" />
<V label="" value="" />
</Row>
</Section>
</div> </div>
)} )}
<DialogFooter> <DialogFooter className="border-t bg-muted/20 px-4 py-3 sm:justify-center">
{row && onEdit && ( {row && onEdit && (
<Button onClick={() => onEdit(row.objid)}> <Button onClick={() => onEdit(row.objid)}>
<Pencil className="h-4 w-4" /><span className="ml-1"></span> <Pencil className="h-4 w-4" /><span className="ml-1"></span>
@@ -149,29 +178,48 @@ export function PartDetailDialog({ open, onOpenChange, objid, onEdit }: Props) {
); );
} }
// ─── 보조 ───────────────────────────────────────────────── // ─── 보조 컴포넌트 ──────────────────────────────────────────
function Section({ title, children }: { title: string; children: React.ReactNode }) { function Tr({ children }: { children: React.ReactNode }) {
return <tr>{children}</tr>;
}
function Th({ children }: { children: React.ReactNode }) {
return ( return (
<div className="rounded-md border bg-card p-3"> <th className="border bg-muted/30 px-3 py-2 text-left align-middle font-medium">
<div className="mb-2 text-sm font-semibold text-foreground">{title}</div> {children}
<div className="space-y-2">{children}</div> </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> </div>
); );
} }
function Row({ children }: { children: React.ReactNode }) { function CadCount({ label, count }: { label: string; count: number }) {
return <div className="grid grid-cols-3 gap-3">{children}</div>; if (count > 0) {
} return (
<div className="flex items-center gap-2 text-xs">
function V({ label, value, align }: { label: string; value: any; align?: "left" | "center" | "right" }) { <FileText className="h-4 w-4 text-blue-600" />
const cls = align === "right" ? "text-right" : align === "center" ? "text-center" : ""; <span>{label} {count.toLocaleString()}</span>
return (
<div>
{label && <Label className="mb-1 block text-xs text-muted-foreground">{label}</Label>}
<div className={`min-h-9 rounded-md border bg-muted/30 px-2 py-2 ${cls}`}>
{value != null && value !== "" ? value : <span className="text-muted-foreground"></span>}
</div> </div>
);
}
return (
<div className="border-2 border-dashed border-muted-foreground/30 rounded text-muted-foreground py-3 text-center text-xs">
{label}
</div> </div>
); );
} }
@@ -226,18 +226,21 @@ export function PartFormDialog({ open, onOpenChange, mode, editObjid, onSaved }:
</div> </div>
) : ( ) : (
<div className="flex-1 overflow-y-auto px-4 py-3"> <div className="flex-1 overflow-y-auto px-4 py-3">
<table className="w-full border-collapse text-sm"> {/* 운영판 colgroup 1:1 (12% / 12% / 25% / 12% / *)
각 행 첫 input 은 colSpan=2 로 양쪽 input 비율 맞춤 */}
<table className="w-full border-collapse text-sm table-fixed">
<colgroup> <colgroup>
<col className="w-[110px]" /> <col style={{ width: "12%" }} />
<col /> <col style={{ width: "12%" }} />
<col className="w-[110px]" /> <col style={{ width: "25%" }} />
<col style={{ width: "12%" }} />
<col /> <col />
</colgroup> </colgroup>
<tbody> <tbody>
{/* ① */} {/* ① */}
<Tr> <Tr>
<Th></Th> <Th></Th>
<Td><Input value={form.part_no} disabled={isEdit} <Td colSpan={2}><Input value={form.part_no} disabled={isEdit}
onChange={(e) => setField("part_no", e.target.value)} /></Td> onChange={(e) => setField("part_no", e.target.value)} /></Td>
<Th></Th> <Th></Th>
<Td><Input value={form.part_name} <Td><Input value={form.part_name}
@@ -247,7 +250,7 @@ export function PartFormDialog({ open, onOpenChange, mode, editObjid, onSaved }:
{/* ② */} {/* ② */}
<Tr> <Tr>
<Th></Th> <Th></Th>
<Td><Input value={form.material} <Td colSpan={2}><Input value={form.material}
onChange={(e) => setField("material", e.target.value)} /></Td> onChange={(e) => setField("material", e.target.value)} /></Td>
<Th></Th> <Th></Th>
<Td><Input value={form.heat_treatment_hardness} <Td><Input value={form.heat_treatment_hardness}
@@ -257,7 +260,7 @@ export function PartFormDialog({ open, onOpenChange, mode, editObjid, onSaved }:
{/* ③ */} {/* ③ */}
<Tr> <Tr>
<Th></Th> <Th></Th>
<Td><Input value={form.heat_treatment_method} <Td colSpan={2}><Input value={form.heat_treatment_method}
onChange={(e) => setField("heat_treatment_method", e.target.value)} /></Td> onChange={(e) => setField("heat_treatment_method", e.target.value)} /></Td>
<Th></Th> <Th></Th>
<Td><Input value={form.surface_treatment} <Td><Input value={form.surface_treatment}
@@ -267,7 +270,7 @@ export function PartFormDialog({ open, onOpenChange, mode, editObjid, onSaved }:
{/* ④ */} {/* ④ */}
<Tr> <Tr>
<Th></Th> <Th></Th>
<Td><Input value={form.maker} <Td colSpan={2}><Input value={form.maker}
onChange={(e) => setField("maker", e.target.value)} /></Td> onChange={(e) => setField("maker", e.target.value)} /></Td>
<Th></Th> <Th></Th>
<Td> <Td>
@@ -277,17 +280,17 @@ export function PartFormDialog({ open, onOpenChange, mode, editObjid, onSaved }:
</Td> </Td>
</Tr> </Tr>
{/* ⑤ 규격 (colspan=3) */} {/* ⑤ 규격 (colspan=4 — 운영판 colspan=4 1:1) */}
<Tr> <Tr>
<Th></Th> <Th></Th>
<Td colSpan={3}><Input value={form.spec} maxLength={100} <Td colSpan={4}><Input value={form.spec} maxLength={100}
onChange={(e) => setField("spec", e.target.value)} /></Td> onChange={(e) => setField("spec", e.target.value)} /></Td>
</Tr> </Tr>
{/* ⑥ */} {/* ⑥ */}
<Tr> <Tr>
<Th><Req /></Th> <Th><Req /></Th>
<Td> <Td colSpan={2}>
<CommCodeSelect groupId={GROUP_ACCTFG} withAll={false} <CommCodeSelect groupId={GROUP_ACCTFG} withAll={false}
value={form.acctfg} value={form.acctfg}
onValueChange={(v) => setField("acctfg", v)} /> onValueChange={(v) => setField("acctfg", v)} />
@@ -300,7 +303,7 @@ export function PartFormDialog({ open, onOpenChange, mode, editObjid, onSaved }:
{/* ⑦ */} {/* ⑦ */}
<Tr> <Tr>
<Th><Req /></Th> <Th><Req /></Th>
<Td> <Td colSpan={2}>
<CommCodeSelect groupId={GROUP_UNIT_DC} withAll={false} <CommCodeSelect groupId={GROUP_UNIT_DC} withAll={false}
value={form.unit_dc} value={form.unit_dc}
onValueChange={(v) => setField("unit_dc", v)} /> onValueChange={(v) => setField("unit_dc", v)} />
@@ -316,7 +319,7 @@ export function PartFormDialog({ open, onOpenChange, mode, editObjid, onSaved }:
{/* ⑧ */} {/* ⑧ */}
<Tr> <Tr>
<Th><Req /></Th> <Th><Req /></Th>
<Td><Input value={form.unitchng_nb} className="text-right" <Td colSpan={2}><Input value={form.unitchng_nb} className="text-right"
inputMode="decimal" inputMode="decimal"
onChange={(e) => setField("unitchng_nb", e.target.value.replace(/[^0-9.]/g, ""))} /></Td> onChange={(e) => setField("unitchng_nb", e.target.value.replace(/[^0-9.]/g, ""))} /></Td>
<Th>LOT구분<Req /></Th> <Th>LOT구분<Req /></Th>
@@ -327,7 +330,7 @@ export function PartFormDialog({ open, onOpenChange, mode, editObjid, onSaved }:
{/* ⑨ */} {/* ⑨ */}
<Tr> <Tr>
<Th><Req /></Th> <Th><Req /></Th>
<Td><BasicSelect value={form.use_yn} options={OPT_USE_YN} <Td colSpan={2}><BasicSelect value={form.use_yn} options={OPT_USE_YN}
onChange={(v) => setField("use_yn", v)} /></Td> onChange={(v) => setField("use_yn", v)} /></Td>
<Th><Req /></Th> <Th><Req /></Th>
<Td><BasicSelect value={form.qc_fg} options={OPT_QC_FG} <Td><BasicSelect value={form.qc_fg} options={OPT_QC_FG}
@@ -337,7 +340,7 @@ export function PartFormDialog({ open, onOpenChange, mode, editObjid, onSaved }:
{/* ⑩ */} {/* ⑩ */}
<Tr> <Tr>
<Th>SET품여부<Req /></Th> <Th>SET품여부<Req /></Th>
<Td><BasicSelect value={form.setitem_fg} options={OPT_YESNO} <Td colSpan={2}><BasicSelect value={form.setitem_fg} options={OPT_YESNO}
onChange={(v) => setField("setitem_fg", v)} /></Td> onChange={(v) => setField("setitem_fg", v)} /></Td>
<Th><Req /></Th> <Th><Req /></Th>
<Td><BasicSelect value={form.req_fg} options={OPT_YESNO} <Td><BasicSelect value={form.req_fg} options={OPT_YESNO}
@@ -347,7 +350,7 @@ export function PartFormDialog({ open, onOpenChange, mode, editObjid, onSaved }:
{/* ⑪ */} {/* ⑪ */}
<Tr> <Tr>
<Th></Th> <Th></Th>
<Td><Input value={form.unit_length} className="text-right" <Td colSpan={2}><Input value={form.unit_length} className="text-right"
inputMode="decimal" maxLength={20} inputMode="decimal" maxLength={20}
onChange={(e) => setField("unit_length", e.target.value.replace(/[^0-9.]/g, ""))} /></Td> onChange={(e) => setField("unit_length", e.target.value.replace(/[^0-9.]/g, ""))} /></Td>
<Th></Th> <Th></Th>
@@ -356,10 +359,10 @@ export function PartFormDialog({ open, onOpenChange, mode, editObjid, onSaved }:
onChange={(e) => setField("unit_qty", e.target.value.replace(/[^0-9.]/g, ""))} /></Td> onChange={(e) => setField("unit_qty", e.target.value.replace(/[^0-9.]/g, ""))} /></Td>
</Tr> </Tr>
{/* ⑫ 비고 (colspan=3) */} {/* ⑫ 비고 (colspan=4) */}
<Tr> <Tr>
<Th></Th> <Th></Th>
<Td colSpan={3}><Input value={form.remark} <Td colSpan={4}><Input value={form.remark}
onChange={(e) => setField("remark", e.target.value)} /></Td> onChange={(e) => setField("remark", e.target.value)} /></Td>
</Tr> </Tr>
@@ -368,20 +371,20 @@ export function PartFormDialog({ open, onOpenChange, mode, editObjid, onSaved }:
<th className="border bg-muted/30 px-3 py-2 text-left align-top font-medium" rowSpan={3}> <th className="border bg-muted/30 px-3 py-2 text-left align-top font-medium" rowSpan={3}>
CAD Data CAD Data
</th> </th>
<th className="border bg-muted/30 px-3 py-2 text-left font-medium w-[110px]">3D</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={2}> <td className="border px-3 py-2" colSpan={3}>
<DropPlaceholder label="3D" /> <DropPlaceholder label="3D" />
</td> </td>
</tr> </tr>
<tr> <tr>
<th className="border bg-muted/30 px-3 py-2 text-left font-medium">2D(Drawing)</th> <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={2}> <td className="border px-3 py-2" colSpan={3}>
<DropPlaceholder label="2D(Drawing)" /> <DropPlaceholder label="2D(Drawing)" />
</td> </td>
</tr> </tr>
<tr> <tr>
<th className="border bg-muted/30 px-3 py-2 text-left font-medium">2D(PDF)</th> <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={2}> <td className="border px-3 py-2" colSpan={3}>
<DropPlaceholder label="2D(PDF)" /> <DropPlaceholder label="2D(PDF)" />
</td> </td>
</tr> </tr>
@@ -415,7 +418,7 @@ function Tr({ children }: { children: React.ReactNode }) {
} }
function Th({ children }: { children: React.ReactNode }) { function Th({ children }: { children: React.ReactNode }) {
return ( return (
<th className="border bg-muted/30 px-3 py-2 text-left align-middle font-medium w-[110px]"> <th className="border bg-muted/30 px-3 py-2 text-left align-middle font-medium">
{children} {children}
</th> </th>
); );