"use client"; // 개발관리 E-BOM 트리 다이얼로그 — wace setStructurePopupMainFS 1:1 (단일 다이얼로그로 통합) // // 운영판은 frameset(헤더/좌측 트리/우측 디테일/하단 버튼) 4-Frame 팝업이지만 RPS 는 단일 // 다이얼로그에 헤더(BOM Report 메타) + 동적 LEVEL 컬럼 트리 그리드 + 엑셀 다운로드 버튼. // // 컬럼 (운영 structureAscendingListExcel.jsp 1:1): // 동적 L1..LMaxLevel ("*" 표시) + 품번 / 품명 / 수량 / 항목수량 / 3D / 2D / PDF / // 재료 / 열처리경도 / 열처리방법 / 표면처리 / 메이커 / 범주 이름 / 비고 import React, { useEffect, useMemo, useState } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Loader2, FileSpreadsheet, Folder } from "lucide-react"; import { toast } from "sonner"; import { devBomApi, BomReportRow, BomTreeFullRow } from "@/lib/api/devBom"; import { cn } from "@/lib/utils"; interface Props { open: boolean; onOpenChange: (open: boolean) => void; bomReport: BomReportRow | null; // 헤더 정보 표시용 } export function BomReportTreeDialog({ open, onOpenChange, bomReport }: Props) { const [rows, setRows] = useState([]); const [maxLevel, setMaxLevel] = useState(0); const [loading, setLoading] = useState(false); const [exporting, setExporting] = useState(false); useEffect(() => { if (!open || !bomReport?.objid) { setRows([]); setMaxLevel(0); return; } let alive = true; setLoading(true); devBomApi.treeFull({ bom_report_objid: bomReport.objid }) .then((data) => { if (!alive) return; setRows(data.rows ?? []); setMaxLevel(Number(data.max_level) || 0); }) .catch((e: any) => { toast.error(e?.response?.data?.message ?? e?.message ?? "트리 조회 실패"); }) .finally(() => { if (alive) setLoading(false); }); return () => { alive = false; }; }, [open, bomReport?.objid]); const handleExcel = async () => { if (!bomReport?.objid) return; setExporting(true); try { const { blob, fileName } = await devBomApi.excelAscending({ bom_report_objid: bomReport.objid }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = fileName; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); toast.success(`${fileName} 다운로드`); } catch (e: any) { toast.error(e?.response?.data?.message ?? e?.message ?? "엑셀 다운로드 실패"); } finally { setExporting(false); } }; const effectiveMax = Math.max(1, maxLevel); // 동적 LEVEL 컬럼 헤더 (1..maxLevel) const levelHeaders = useMemo(() => { const h: number[] = []; for (let i = 1; i <= effectiveMax; i++) h.push(i); return h; }, [effectiveMax]); return ( BOM 구조 조회 {/* 헤더 메타 */} {bomReport && (
)}
총 {rows.length.toLocaleString()}행 · MAX_LEVEL = {maxLevel}
{loading ? (
) : ( {levelHeaders.map((i) => ( ))} {rows.length === 0 && ( )} {rows.map((r, idx) => { const lev = Number(r.lev ?? 1); return ( {levelHeaders.map((i) => ( ))} ); })}
{i}품번 품명 수량 항목수량 3D 2D PDF 재료 열처리경도 열처리방법 표면처리 메이커 범주 이름 비고
BOM 구조가 없습니다.
{i === lev ? "*" : ""} {r.pm_part_no} {r.pm_part_name} {r.qty} {r.p_qty} {r.material} {r.heat_treatment_hardness} {r.heat_treatment_method} {r.surface_treatment} {r.maker} {r.part_type_title} {r.remark}
)}
); } function FolderCell({ n }: { n: any }) { const has = Number(n ?? 0) > 0; return ( ); } function MetaRow({ label, value }: { label: string; value: any }) { return (
{label} {value != null && value !== "" ? value : "—"}
); }