- Integrated BOM routes into the backend for managing BOM history and versions. - Enhanced the V2BomTreeConfigPanel to include options for history and version table management. - Updated the BomTreeComponent to support viewing BOM data in both tree and level formats, with modals for editing BOM details, viewing history, and managing versions. - Improved user interaction with new buttons for accessing BOM history and version management directly from the BOM tree view.
148 lines
5.4 KiB
TypeScript
148 lines
5.4 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
} from "@/components/ui/dialog";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Loader2 } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import apiClient from "@/lib/api/client";
|
|
|
|
interface BomHistoryItem {
|
|
id: string;
|
|
revision: string;
|
|
version: string;
|
|
change_type: string;
|
|
change_description: string;
|
|
changed_by: string;
|
|
changed_date: string;
|
|
}
|
|
|
|
interface BomHistoryModalProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
bomId: string | null;
|
|
tableName?: string;
|
|
}
|
|
|
|
const CHANGE_TYPE_STYLE: Record<string, string> = {
|
|
"등록": "bg-blue-50 text-blue-600 ring-blue-200",
|
|
"수정": "bg-amber-50 text-amber-600 ring-amber-200",
|
|
"추가": "bg-emerald-50 text-emerald-600 ring-emerald-200",
|
|
"삭제": "bg-red-50 text-red-600 ring-red-200",
|
|
};
|
|
|
|
export function BomHistoryModal({ open, onOpenChange, bomId, tableName = "bom_history" }: BomHistoryModalProps) {
|
|
const [history, setHistory] = useState<BomHistoryItem[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (open && bomId) {
|
|
loadHistory();
|
|
}
|
|
}, [open, bomId]);
|
|
|
|
const loadHistory = async () => {
|
|
if (!bomId) return;
|
|
setLoading(true);
|
|
try {
|
|
const res = await apiClient.get(`/bom/${bomId}/history`, { params: { tableName } });
|
|
if (res.data?.success) {
|
|
setHistory(res.data.data || []);
|
|
}
|
|
} catch (error) {
|
|
console.error("[BomHistory] 로드 실패:", error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const formatDate = (dateStr: string) => {
|
|
if (!dateStr) return "-";
|
|
try {
|
|
return new Date(dateStr).toLocaleString("ko-KR", {
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
});
|
|
} catch {
|
|
return dateStr;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-[95vw] sm:max-w-[650px]">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-base sm:text-lg">이력 관리</DialogTitle>
|
|
<DialogDescription className="text-xs sm:text-sm">
|
|
BOM 변경 이력을 확인합니다
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="max-h-[400px] overflow-auto">
|
|
{loading ? (
|
|
<div className="flex h-32 items-center justify-center">
|
|
<Loader2 className="h-5 w-5 animate-spin text-primary" />
|
|
</div>
|
|
) : history.length === 0 ? (
|
|
<div className="flex h-32 items-center justify-center">
|
|
<p className="text-sm text-gray-400">이력이 없습니다</p>
|
|
</div>
|
|
) : (
|
|
<table className="w-full text-xs">
|
|
<thead className="sticky top-0 bg-gray-50">
|
|
<tr className="border-b">
|
|
<th className="px-3 py-2.5 text-center text-[11px] font-semibold text-gray-500" style={{ width: "50px" }}>차수</th>
|
|
<th className="px-3 py-2.5 text-center text-[11px] font-semibold text-gray-500" style={{ width: "50px" }}>버전</th>
|
|
<th className="px-3 py-2.5 text-center text-[11px] font-semibold text-gray-500" style={{ width: "70px" }}>변경구분</th>
|
|
<th className="px-3 py-2.5 text-left text-[11px] font-semibold text-gray-500">변경내용</th>
|
|
<th className="px-3 py-2.5 text-center text-[11px] font-semibold text-gray-500" style={{ width: "70px" }}>변경자</th>
|
|
<th className="px-3 py-2.5 text-center text-[11px] font-semibold text-gray-500" style={{ width: "130px" }}>변경일시</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{history.map((item, idx) => (
|
|
<tr key={item.id} className={cn("border-b border-gray-100", idx % 2 === 0 ? "bg-white" : "bg-gray-50/30")}>
|
|
<td className="px-3 py-2.5 text-center tabular-nums">{item.revision || "-"}</td>
|
|
<td className="px-3 py-2.5 text-center tabular-nums">{item.version || "-"}</td>
|
|
<td className="px-3 py-2.5 text-center">
|
|
<span className={cn(
|
|
"inline-flex items-center rounded-md px-1.5 py-0.5 text-[10px] font-medium ring-1 ring-inset",
|
|
CHANGE_TYPE_STYLE[item.change_type] || "bg-gray-50 text-gray-500 ring-gray-200",
|
|
)}>
|
|
{item.change_type}
|
|
</span>
|
|
</td>
|
|
<td className="px-3 py-2.5 text-gray-700">{item.change_description || "-"}</td>
|
|
<td className="px-3 py-2.5 text-center text-gray-600">{item.changed_by || "-"}</td>
|
|
<td className="px-3 py-2.5 text-center text-gray-400">{formatDate(item.changed_date)}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
)}
|
|
</div>
|
|
|
|
<DialogFooter className="gap-2 sm:gap-0">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => onOpenChange(false)}
|
|
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
|
>
|
|
닫기
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|