"use client"; // 개발관리 > E-BOM 등록 (M3) — wace structureList.jsp 1:1 // 그리드: part_bom_report 9셀 // 액션: 조회 / 삭제 / 상태변경 (E-BOM등록 Excel Import는 별 PR) // 참조: docs/migration/development/02-ebom.md import React, { useCallback, useEffect, useMemo, useState } from "react"; import { Button } from "@/components/ui/button"; import { Trash2, Settings, FileSpreadsheet } from "lucide-react"; import { toast } from "sonner"; import { DataGrid, DataGridColumn } from "@/components/common/DataGrid"; import { CommCodeSelect } from "@/components/common/CommCodeSelect"; import { SmartSelect, SmartSelectOption } from "@/components/common/SmartSelect"; import { PageHeader } from "@/components/common/PageHeader"; import { CompactFilterBar, CompactFilterField } from "@/components/common/CompactFilterBar"; import { devBomApi, BomReportListFilter, BomReportRow } from "@/lib/api/devBom"; import { BomReportStatusDialog } from "@/components/development/BomReportStatusDialog"; import { DevPartSelect } from "@/components/development/DevPartSelect"; import { BomReportExcelImportDialog } from "@/components/development/BomReportExcelImportDialog"; import { BomReportTreeDialog } from "@/components/development/BomReportTreeDialog"; import { exportToExcel } from "@/lib/utils/excelExport"; const PRODUCT_GROUP = "0000001"; // 제품구분 (vexplor 공용) const STATUS_OPTIONS: SmartSelectOption[] = [ { code: "create", label: "등록중" }, { code: "changeDesign", label: "설계변경미배포" }, { code: "deploy", label: "배포완료" }, ]; const BASE_GRID_COLUMNS: DataGridColumn[] = [ { key: "product_name", label: "제품구분", width: "w-[160px]", align: "center", frozen: true }, { key: "part_no", label: "품번", width: "w-[210px]" }, { key: "part_name", label: "품명", minWidth: "min-w-[220px]" }, // wace fnc_getFolderIcon 1:1 — 폴더 아이콘 클릭 시 BOM 구조 다이얼로그 { key: "bom_cnt", label: "E-BOM", width: "w-[115px]", align: "center", renderType: "folder" }, { key: "dept_user_name", label: "등록자", width: "w-[140px]", align: "center" }, { key: "reg_date", label: "등록일", width: "w-[125px]", align: "center" }, { key: "deploy_date", label: "확정일", width: "w-[125px]", align: "center" }, { key: "revision", label: "Version", width: "w-[115px]", align: "center" }, { key: "status_title", label: "상태", width: "w-[120px]", align: "center" }, ]; const EMPTY_FILTER: BomReportListFilter = { product_cd: "", status: "", search_part_no: "", search_part_name: "", page: 1, page_size: 50, }; export default function EbomRegistPage() { const [rows, setRows] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); const [filter, setFilter] = useState(EMPTY_FILTER); const [checkedIds, setCheckedIds] = useState([]); const [statusOpen, setStatusOpen] = useState(false); const [statusObjid, setStatusObjid] = useState(null); const [excelOpen, setExcelOpen] = useState(false); const [treeOpen, setTreeOpen] = useState(false); const [treeReport, setTreeReport] = useState(null); const fetchList = useCallback(async (override?: Partial) => { setLoading(true); try { const f = { ...filter, ...override }; const res = await devBomApi.list(f); setRows(res.rows ?? []); setTotal(res.total ?? 0); setCheckedIds([]); } catch (e: any) { toast.error(e?.response?.data?.message ?? e?.message ?? "조회 실패"); } finally { setLoading(false); } }, [filter]); useEffect(() => { fetchList(); /* eslint-disable-next-line */ }, []); const handleDelete = async () => { if (checkedIds.length === 0) return toast.error("선택된 행이 없습니다."); if (!confirm(`${checkedIds.length}건을 삭제하시겠습니까? (자식 BOM 트리도 함께 삭제됨)`)) return; try { const res = await devBomApi.remove(checkedIds); toast.success(res?.message ?? "삭제되었습니다."); fetchList(); } catch (e: any) { toast.error(e?.response?.data?.message ?? e?.message ?? "삭제 실패"); } }; const handleStatusChange = () => { if (checkedIds.length !== 1) return toast.error("상태 변경할 행 1개를 선택하세요."); setStatusObjid(checkedIds[0]); setStatusOpen(true); }; // 품번 셀 클릭 → BOM 트리 다이얼로그 (wace fn_openSetStructure 1:1) const openTree = useCallback((row: BomReportRow) => { setTreeReport(row); setTreeOpen(true); }, []); const columns: DataGridColumn[] = useMemo( () => BASE_GRID_COLUMNS.map((c) => c.key === "bom_cnt" ? { ...c, onClick: (row: any) => openTree(row as BomReportRow) } : c, ), [openTree], ); // DataGrid 는 row.id 를 키로 사용 — backend 응답은 row.objid (lowercase) 이므로 매핑 const gridRows = useMemo(() => rows.map((r) => ({ ...r, id: r.objid })), [rows]); // ─── 하단 통계 ────────────────────────────────────────────── // BOM 건수(총·페이지) / 상태별 분포 (간이 — 상태 라벨별 카운트) const bomSummary = useMemo(() => { const pageCount = gridRows.length; const byStatus = new Map(); gridRows.forEach((r: any) => { const k = String(r.status_title ?? r.status ?? "-"); byStatus.set(k, (byStatus.get(k) ?? 0) + 1); }); const intFmt = (n: number) => n.toLocaleString(); const stats: Array<{ label: string; value: string; suffix?: string }> = [ { label: "전체 건수", value: intFmt(total), suffix: "건" }, { label: "페이지 건수", value: intFmt(pageCount), suffix: "건" }, ]; // 상태별 카운트(많이 노출되는 라벨만) Array.from(byStatus.entries()).slice(0, 4).forEach(([k, v]) => { stats.push({ label: `상태(${k})`, value: intFmt(v), suffix: "건" }); }); return stats; }, [gridRows, total]); return (
fetchList()} onReset={() => { setFilter(EMPTY_FILTER); fetchList(EMPTY_FILTER); }} actions={ <> } /> 총 {total.toLocaleString()}건}> setFilter({ ...filter, product_cd: v })} /> setFilter({ ...filter, status: v })} placeholder="전체" /> setFilter((prev) => ({ ...prev, search_part_no: v, search_part_name: row?.part_name ?? prev.search_part_name, }))} /> setFilter((prev) => ({ ...prev, search_part_name: v, search_part_no: row?.part_no ?? prev.search_part_no, }))} /> { setFilter(f => ({ ...f, page: p })); fetchList({ page: p }); }} onPageSizeChange={(n) => { setFilter(f => ({ ...f, page: 1, page_size: n })); fetchList({ page: 1, page_size: n }); }} summaryStats={bomSummary} systemColumnKeys={["dept_user_name", "reg_date", "deploy_date", "revision", "status_title"]} onRefresh={() => fetchList()} onDownload={() => { if (gridRows.length === 0) { toast.info("내보낼 데이터가 없습니다."); return; } const exportRows = gridRows.map((r: any) => { const out: Record = {}; BASE_GRID_COLUMNS.forEach((col) => { out[col.label] = r[col.key] ?? ""; }); return out; }); exportToExcel(exportRows, "E-BOM_등록.xlsx", "E-BOM_등록"); }} showChart />
); }