Files
wace_rps/frontend/components/production/MbomHistoryDialog.tsx
T
hjjeong 8dd5f184ae 생산관리 M-BOM 변경이력 다이얼로그(PR-B4) + 그리드 컬럼 폭 다듬기
운영 productionplanning.getMbomHistory (3448~3470) 1:1 이식:
  · backend mbomService.getHistory(projectObjid) — project 단위 mbom_header 변경이력
    시간순 최신 우선, USER_INFO join 으로 변경자명 함께
  · GET /api/production/mbom/history/:projectObjid
  · frontend MbomHistoryDialog — 5컬럼 그리드(변경일시/유형/내용/변경자/M-BOM품번)
    CREATE=파란 뱃지, UPDATE=황색 뱃지, max-w-900px
  · MbomDetailDialog toolbar 에 "변경이력" 버튼 (편집 모드 아닐 때만 노출)

PR-B1 의 history 저장 결과를 사용자가 직접 확인할 수 있도록 보는 화면 마무리.

부수: production/mbom/page 그리드 컬럼 폭 정돈 + summaryStats(전체/페이지/수주합/M-BOM 비율).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:39:48 +09:00

105 lines
4.5 KiB
TypeScript

"use client";
// 생산관리 > M-BOM 관리 — 변경이력 다이얼로그 (PR-B4).
//
// 운영판 매퍼 productionplanning.getMbomHistory (3448~3470) 1:1.
// 프로젝트의 모든 mbom_header 변경이력 시간순(최신 우선) 표시.
// CREATE / UPDATE + 설명 + 변경자 + 변경일 + M-BOM 품번.
import React, { useEffect, useState } from "react";
import {
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Loader2, History } from "lucide-react";
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import { mbomApi, MbomHistoryRow } from "@/lib/api/mbom";
interface Props {
open: boolean;
onOpenChange: (open: boolean) => void;
projectObjid: string | null;
}
const CHANGE_TYPE_BADGE: Record<string, { text: string; color: string }> = {
CREATE: { text: "생성", color: "bg-blue-600" },
UPDATE: { text: "수정", color: "bg-amber-600" },
};
export function MbomHistoryDialog({ open, onOpenChange, projectObjid }: Props) {
const [rows, setRows] = useState<MbomHistoryRow[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!open || !projectObjid) { setRows([]); return; }
let alive = true;
setLoading(true);
mbomApi.getHistory(projectObjid)
.then(data => { if (alive) setRows(data); })
.catch((e: any) => toast.error(e?.response?.data?.message ?? e?.message ?? "변경이력 조회 실패"))
.finally(() => { if (alive) setLoading(false); });
return () => { alive = false; };
}, [open, projectObjid]);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[900px] w-[95vw] max-h-[85vh] flex flex-col p-0 overflow-hidden">
<DialogHeader className="bg-blue-600 px-4 py-3">
<DialogTitle className="text-white flex items-center gap-2">
<History className="w-4 h-4" />
M-BOM
<span className="text-xs font-normal opacity-80"> {rows.length.toLocaleString()}</span>
</DialogTitle>
</DialogHeader>
<div className="flex-1 min-h-0 overflow-auto">
{loading ? (
<div className="flex h-48 items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin" />
</div>
) : rows.length === 0 ? (
<div className="flex h-48 items-center justify-center text-sm text-muted-foreground">
.
</div>
) : (
<table className="text-xs border-collapse w-full">
<thead className="bg-yellow-100 dark:bg-yellow-900/30 sticky top-0">
<tr>
<th className="border px-2 py-1.5 w-[140px] text-center"></th>
<th className="border px-2 py-1.5 w-[70px] text-center"></th>
<th className="border px-2 py-1.5 text-left"> </th>
<th className="border px-2 py-1.5 w-[110px] text-center"></th>
<th className="border px-2 py-1.5 w-[180px] text-left">M-BOM </th>
</tr>
</thead>
<tbody>
{rows.map((r, idx) => {
const badge = CHANGE_TYPE_BADGE[r.change_type] ?? { text: r.change_type, color: "bg-slate-500" };
return (
<tr key={`${r.objid}_${idx}`} className="hover:bg-muted/30">
<td className="border px-2 py-1 text-center whitespace-nowrap tabular-nums">{r.change_date}</td>
<td className="border px-2 py-1 text-center">
<span className={cn("inline-block rounded px-1.5 py-0.5 text-[10px] font-semibold text-white", badge.color)}>
{badge.text}
</span>
</td>
<td className="border px-2 py-1">{r.change_description ?? ""}</td>
<td className="border px-2 py-1 text-center">{r.change_user_name ?? r.change_user ?? ""}</td>
<td className="border px-2 py-1 font-mono text-[11px]">{r.mbom_part_no ?? ""}</td>
</tr>
);
})}
</tbody>
</table>
)}
</div>
<DialogFooter className="border-t bg-muted/20 px-4 py-3 sm:justify-center">
<Button variant="outline" onClick={() => onOpenChange(false)}></Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}