ea6606da0c
backend (M1+M2):
- devPartService: listTemp/listRelease/getByObjid/create/update/deploy/removeMany
- partMngBaseSimple SELECT + 추가 15컬럼(acctfg/odrfg/unit_dc/unitmang_dc/lot_fg 등) 라벨/CASE
- deploy 트랜잭션 3단계 (isLastInit → part_mng_history INSERT → partMngDeploy + EO_NO 채번)
- EO_NO 분기: is_longd='1'→EOB{yy}-{seq} / else EO{yy}-{seq}
- objidUtil: wace CommonUtils.createObjId() 1:1 (bigint objid 채번)
- DDL: 9 신규 테이블 + part_mng 15컬럼 ALTER (운영판 1:1 추출)
frontend (M1+M2):
- part-regist (M1) / part-search (M2): 23셀 그리드 + 검색폼 + 액션
- PartFormDialog: 등록/수정 통합 (mode prop, 4 섹션)
- PartDetailDialog: 읽기 전용 + "수정" dispatch
- AdminPageRenderer dynamic 임포트 2건 + menu_info URL spec 정렬
본 PR 제외 (별 PR): 도면 다중 업로드, ERP 업로드, Excel Import, BOM_PART_QTY R/W
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
178 lines
7.1 KiB
TypeScript
178 lines
7.1 KiB
TypeScript
"use client";
|
|
|
|
// 개발관리 > PART 상세 조회 다이얼로그 (read-only).
|
|
// 행 더블클릭 진입. "수정" 버튼 클릭 시 PartFormDialog(mode='edit')로 전환은
|
|
// 호출 페이지가 dispatch (open=false → 부모가 form dialog 오픈).
|
|
|
|
import React, { useEffect, useState } 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 { Loader2, Pencil } from "lucide-react";
|
|
import { toast } from "sonner";
|
|
import { devPartApi, PartRow } from "@/lib/api/devPart";
|
|
|
|
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-5xl">
|
|
<DialogHeader>
|
|
<DialogTitle>PART 상세 정보</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
{loading || !row ? (
|
|
<div className="flex h-64 items-center justify-center">
|
|
<Loader2 className="h-6 w-6 animate-spin" />
|
|
</div>
|
|
) : (
|
|
<div className="max-h-[70vh] space-y-4 overflow-y-auto px-1 py-2 text-sm">
|
|
<Section title="기본정보">
|
|
<Row>
|
|
<V label="품번" value={row.part_no} />
|
|
<V label="품명" value={row.part_name} />
|
|
<V label="범주" value={row.part_type_title} />
|
|
</Row>
|
|
<Row>
|
|
<V label="단위" value={row.unit_title} />
|
|
<V label="수량" value={row.qty} align="right" />
|
|
<V label="규격" value={row.spec} />
|
|
</Row>
|
|
<Row>
|
|
<V label="재료" value={row.material} />
|
|
<V label="메이커" value={row.maker} />
|
|
<V label="비고" value={row.remark} />
|
|
</Row>
|
|
<Row>
|
|
<V label="공급업체" value={row.supply_name} />
|
|
<V label="등록자" value={row.writer} />
|
|
<V label="등록일" value={row.part_regdate_title} />
|
|
</Row>
|
|
<Row>
|
|
<V label="REVISION" value={row.revision} />
|
|
<V label="EO_NO" value={row.eo_no} />
|
|
<V label="STATUS" value={row.status} />
|
|
</Row>
|
|
</Section>
|
|
|
|
<Section title="크기 / 형상">
|
|
<Row>
|
|
<V label="두께" value={row.thickness} align="right" />
|
|
<V label="너비(W)" value={row.width} align="right" />
|
|
<V label="높이(H)" value={row.height} align="right" />
|
|
</Row>
|
|
<Row>
|
|
<V label="외경" value={row.out_diameter} align="right" />
|
|
<V label="내경" value={row.in_diameter} align="right" />
|
|
<V label="길이(L)" value={row.length} align="right" />
|
|
</Row>
|
|
</Section>
|
|
|
|
<Section title="분류 / 단위">
|
|
<Row>
|
|
<V label="계정구분" value={row.acctfg_nm} />
|
|
<V label="조달구분" value={row.odrfg_nm} />
|
|
<V label="환산수량" value={row.unitchng_nb != null ? String(row.unitchng_nb) : ""} align="right" />
|
|
</Row>
|
|
<Row>
|
|
<V label="재고단위" value={row.unit_dc_nm} />
|
|
<V label="관리단위" value={row.unitmang_dc_nm} />
|
|
<V label="개당길이 / 개당수량"
|
|
value={[row.unit_length, row.unit_qty].filter(Boolean).join(" / ")} align="right" />
|
|
</Row>
|
|
<Row>
|
|
<V label="열처리경도" value={row.heat_treatment_hardness} />
|
|
<V label="열처리방법" value={row.heat_treatment_method} />
|
|
<V label="표면처리" value={row.surface_treatment} />
|
|
</Row>
|
|
<Row>
|
|
<V label="후가공" value={row.post_processing} />
|
|
<V label="첨부 (3D/2D/PDF)"
|
|
value={`${row.cu01_cnt ?? 0} / ${row.cu02_cnt ?? 0} / ${row.cu03_cnt ?? 0}`} align="center" />
|
|
<V label="" value="" />
|
|
</Row>
|
|
</Section>
|
|
|
|
<Section title="Y/N 플래그">
|
|
<Row>
|
|
<V label="LOT구분" value={row.lot_fg_nm} align="center" />
|
|
<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>
|
|
)}
|
|
|
|
<DialogFooter>
|
|
{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 Section({ title, children }: { title: string; children: React.ReactNode }) {
|
|
return (
|
|
<div className="rounded-md border bg-card p-3">
|
|
<div className="mb-2 text-sm font-semibold text-foreground">{title}</div>
|
|
<div className="space-y-2">{children}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Row({ children }: { children: React.ReactNode }) {
|
|
return <div className="grid grid-cols-3 gap-3">{children}</div>;
|
|
}
|
|
|
|
function V({ label, value, align }: { label: string; value: any; align?: "left" | "center" | "right" }) {
|
|
const cls = align === "right" ? "text-right" : align === "center" ? "text-center" : "";
|
|
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>
|
|
);
|
|
}
|