Merge branch 'feature/v2-renewal' into jskim-node
Made-with: Cursor
This commit is contained in:
commit
5cff85d260
|
|
@ -93,6 +93,19 @@ export async function loadBomVersion(req: Request, res: Response) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function activateBomVersion(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const { bomId, versionId } = req.params;
|
||||||
|
const { tableName } = req.body || {};
|
||||||
|
|
||||||
|
const result = await bomService.activateBomVersion(bomId, versionId, tableName);
|
||||||
|
res.json({ success: true, data: result });
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.error("BOM 버전 사용 확정 실패", { error: error.message });
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function deleteBomVersion(req: Request, res: Response) {
|
export async function deleteBomVersion(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const { bomId, versionId } = req.params;
|
const { bomId, versionId } = req.params;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ router.post("/:bomId/history", bomController.addBomHistory);
|
||||||
router.get("/:bomId/versions", bomController.getBomVersions);
|
router.get("/:bomId/versions", bomController.getBomVersions);
|
||||||
router.post("/:bomId/versions", bomController.createBomVersion);
|
router.post("/:bomId/versions", bomController.createBomVersion);
|
||||||
router.post("/:bomId/versions/:versionId/load", bomController.loadBomVersion);
|
router.post("/:bomId/versions/:versionId/load", bomController.loadBomVersion);
|
||||||
|
router.post("/:bomId/versions/:versionId/activate", bomController.activateBomVersion);
|
||||||
router.delete("/:bomId/versions/:versionId", bomController.deleteBomVersion);
|
router.delete("/:bomId/versions/:versionId", bomController.deleteBomVersion);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,9 @@ export async function createBomVersion(
|
||||||
companyCode,
|
companyCode,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// BOM 헤더의 version 필드도 업데이트
|
||||||
|
await client.query(`UPDATE bom SET version = $1 WHERE id = $2`, [versionName, bomId]);
|
||||||
|
|
||||||
logger.info("BOM 버전 생성", { bomId, versionName, companyCode, vTable, dTable });
|
logger.info("BOM 버전 생성", { bomId, versionName, companyCode, vTable, dTable });
|
||||||
return result.rows[0];
|
return result.rows[0];
|
||||||
});
|
});
|
||||||
|
|
@ -140,6 +143,7 @@ export async function loadBomVersion(
|
||||||
await client.query(`DELETE FROM ${snapshotDetailTable} WHERE bom_id = $1`, [bomId]);
|
await client.query(`DELETE FROM ${snapshotDetailTable} WHERE bom_id = $1`, [bomId]);
|
||||||
|
|
||||||
const b = snapshot.bom;
|
const b = snapshot.bom;
|
||||||
|
const loadedVersionName = verRow.rows[0].version_name;
|
||||||
await client.query(
|
await client.query(
|
||||||
`UPDATE bom SET base_qty = $1, unit = $2, revision = $3, remark = $4 WHERE id = $5`,
|
`UPDATE bom SET base_qty = $1, unit = $2, revision = $3, remark = $4 WHERE id = $5`,
|
||||||
[b.base_qty || null, b.unit || null, b.revision || null, b.remark || null, bomId],
|
[b.base_qty || null, b.unit || null, b.revision || null, b.remark || null, bomId],
|
||||||
|
|
@ -169,12 +173,50 @@ export async function loadBomVersion(
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("BOM 버전 불러오기 완료", { bomId, versionId, vTable, snapshotDetailTable });
|
logger.info("BOM 버전 불러오기 완료", { bomId, versionId, vTable, snapshotDetailTable });
|
||||||
return { restored: true, versionName: verRow.rows[0].version_name };
|
return { restored: true, versionName: loadedVersionName };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function activateBomVersion(bomId: string, versionId: string, tableName?: string) {
|
||||||
|
const table = safeTableName(tableName || "", "bom_version");
|
||||||
|
|
||||||
|
return transaction(async (client) => {
|
||||||
|
const verRow = await client.query(
|
||||||
|
`SELECT version_name FROM ${table} WHERE id = $1 AND bom_id = $2`,
|
||||||
|
[versionId, bomId],
|
||||||
|
);
|
||||||
|
if (verRow.rows.length === 0) throw new Error("버전을 찾을 수 없습니다");
|
||||||
|
|
||||||
|
// 기존 active -> inactive
|
||||||
|
await client.query(
|
||||||
|
`UPDATE ${table} SET status = 'inactive' WHERE bom_id = $1 AND status = 'active'`,
|
||||||
|
[bomId],
|
||||||
|
);
|
||||||
|
// 선택한 버전 -> active
|
||||||
|
await client.query(
|
||||||
|
`UPDATE ${table} SET status = 'active' WHERE id = $1`,
|
||||||
|
[versionId],
|
||||||
|
);
|
||||||
|
// BOM 헤더 version도 갱신
|
||||||
|
const versionName = verRow.rows[0].version_name;
|
||||||
|
await client.query(
|
||||||
|
`UPDATE bom SET version = $1 WHERE id = $2`,
|
||||||
|
[versionName, bomId],
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info("BOM 버전 사용 확정", { bomId, versionId, versionName });
|
||||||
|
return { activated: true, versionName };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteBomVersion(bomId: string, versionId: string, tableName?: string) {
|
export async function deleteBomVersion(bomId: string, versionId: string, tableName?: string) {
|
||||||
const table = safeTableName(tableName || "", "bom_version");
|
const table = safeTableName(tableName || "", "bom_version");
|
||||||
|
// active 상태 버전은 삭제 불가
|
||||||
|
const checkSql = `SELECT status FROM ${table} WHERE id = $1 AND bom_id = $2`;
|
||||||
|
const checkResult = await query(checkSql, [versionId, bomId]);
|
||||||
|
if (checkResult.length > 0 && checkResult[0].status === "active") {
|
||||||
|
throw new Error("사용중인 버전은 삭제할 수 없습니다");
|
||||||
|
}
|
||||||
const sql = `DELETE FROM ${table} WHERE id = $1 AND bom_id = $2 RETURNING id`;
|
const sql = `DELETE FROM ${table} WHERE id = $1 AND bom_id = $2 RETURNING id`;
|
||||||
const result = await query(sql, [versionId, bomId]);
|
const result = await query(sql, [versionId, bomId]);
|
||||||
return result.length > 0;
|
return result.length > 0;
|
||||||
|
|
|
||||||
|
|
@ -357,19 +357,92 @@ export function BomTreeComponent({
|
||||||
if (isDesignMode) {
|
if (isDesignMode) {
|
||||||
const configuredColumns = (config.columns || []).filter((c: TreeColumnDef) => !c.hidden);
|
const configuredColumns = (config.columns || []).filter((c: TreeColumnDef) => !c.hidden);
|
||||||
|
|
||||||
|
const previewSampleValue = (col: TreeColumnDef, rowIdx: number): React.ReactNode => {
|
||||||
|
if (col.key === "level") return rowIdx === 0 ? "0" : "1";
|
||||||
|
if (col.key.includes("type") || col.key.includes("division")) {
|
||||||
|
const badge = rowIdx === 0
|
||||||
|
? { bg: "bg-blue-50 text-blue-500 ring-blue-200", label: "제품" }
|
||||||
|
: { bg: "bg-amber-50 text-amber-500 ring-amber-200", label: "반제품" };
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col overflow-hidden rounded-lg border bg-white shadow-sm">
|
<span className={cn("rounded-md px-1.5 py-0.5 text-[10px] font-medium ring-1 ring-inset", badge.bg)}>
|
||||||
<div className="flex items-center gap-2 border-b bg-gray-50/80 px-4 py-2.5">
|
{badge.label}
|
||||||
<Package className="h-4 w-4 text-primary" />
|
|
||||||
<span className="text-sm font-semibold">BOM 트리 뷰</span>
|
|
||||||
<span className="rounded-md bg-gray-100 px-1.5 py-0.5 text-[10px] text-gray-500">{detailTable}</span>
|
|
||||||
{config.dataSource?.sourceTable && (
|
|
||||||
<span className="rounded-md bg-blue-50 px-1.5 py-0.5 text-[10px] text-blue-500">
|
|
||||||
{config.dataSource.sourceTable}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
);
|
||||||
|
}
|
||||||
|
if (col.key.includes("quantity") || col.key.includes("qty")) return rowIdx === 0 ? "30" : "3";
|
||||||
|
if (col.key.includes("unit")) return "EA";
|
||||||
|
if (col.key.includes("process")) {
|
||||||
|
const badge = rowIdx === 0
|
||||||
|
? { bg: "bg-emerald-50 text-emerald-600 ring-emerald-200", label: "제조" }
|
||||||
|
: { bg: "bg-purple-50 text-purple-600 ring-purple-200", label: "외주" };
|
||||||
|
return (
|
||||||
|
<span className={cn("rounded-md px-1.5 py-0.5 text-[10px] font-medium ring-1 ring-inset", badge.bg)}>
|
||||||
|
{badge.label}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return `예시${rowIdx + 1}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full flex-col bg-white">
|
||||||
|
{/* 헤더 (실제 화면과 동일 구조) */}
|
||||||
|
<div className="border-b px-5 py-3">
|
||||||
|
<div className="flex items-center gap-2.5">
|
||||||
|
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-blue-50">
|
||||||
|
<Package className="h-4 w-4 text-blue-500" />
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<h3 className="text-sm font-semibold text-gray-400">BOM 상세정보</h3>
|
||||||
|
<span className="inline-flex items-center rounded-md bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-500 ring-1 ring-inset ring-blue-200">
|
||||||
|
제품
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-0.5 flex gap-3 text-[10px] text-gray-300">
|
||||||
|
<span>품목코드 <b className="text-gray-400">SAMPLE-001</b></span>
|
||||||
|
<span>기준수량 <b className="text-gray-400">1</b></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 툴바 (실제 화면과 동일 구조) */}
|
||||||
|
<div className="flex items-center border-b bg-gray-50/50 px-5 py-1.5">
|
||||||
|
<span className="text-xs font-medium text-gray-500">BOM 구성</span>
|
||||||
|
<span className="ml-2 rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-semibold text-primary">2</span>
|
||||||
|
<div className="ml-auto flex items-center gap-1">
|
||||||
|
{showHistory && (
|
||||||
|
<Button variant="outline" size="sm" disabled className="h-6 gap-1 px-2 text-[10px]">
|
||||||
|
<History className="h-3 w-3" />
|
||||||
|
이력
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{showVersion && (
|
||||||
|
<Button variant="outline" size="sm" disabled className="h-6 gap-1 px-2 text-[10px]">
|
||||||
|
<GitBranch className="h-3 w-3" />
|
||||||
|
버전
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<div className="mx-1 h-4 w-px bg-gray-200" />
|
||||||
|
<div className="flex overflow-hidden rounded-md border">
|
||||||
|
<span className="h-6 bg-primary px-2 text-[10px] font-medium leading-6 text-primary-foreground">트리</span>
|
||||||
|
<span className="h-6 border-l bg-white px-2 text-[10px] font-medium leading-6 text-gray-500">레벨</span>
|
||||||
|
</div>
|
||||||
|
{features.showExpandAll !== false && (
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<Button variant="ghost" size="sm" disabled className="h-6 gap-1 px-2 text-[10px] text-gray-400">
|
||||||
|
<Expand className="h-3 w-3" /> 정전개
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" size="sm" disabled className="h-6 gap-1 px-2 text-[10px] text-gray-400">
|
||||||
|
<Shrink className="h-3 w-3" /> 역전개
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 테이블 */}
|
||||||
{configuredColumns.length === 0 ? (
|
{configuredColumns.length === 0 ? (
|
||||||
<div className="flex flex-1 flex-col items-center justify-center gap-2 p-6">
|
<div className="flex flex-1 flex-col items-center justify-center gap-2 p-6">
|
||||||
<AlertCircle className="h-8 w-8 text-gray-200" />
|
<AlertCircle className="h-8 w-8 text-gray-200" />
|
||||||
|
|
@ -377,46 +450,58 @@ export function BomTreeComponent({
|
||||||
<p className="text-[11px] text-gray-300">설정 패널 > 컬럼 탭에서 표시할 컬럼을 선택하세요</p>
|
<p className="text-[11px] text-gray-300">설정 패널 > 컬럼 탭에서 표시할 컬럼을 선택하세요</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex-1 overflow-hidden">
|
<div className="flex-1 overflow-auto">
|
||||||
<table className="w-full text-xs">
|
<table className="w-full border-collapse text-xs">
|
||||||
<thead>
|
<thead className="sticky top-0 z-10">
|
||||||
<tr className="border-b bg-gray-50">
|
<tr className="border-b bg-gray-50">
|
||||||
<th className="w-10 border-r border-gray-100 px-2 py-2"></th>
|
<th className="px-2 py-2.5 text-center" style={{ width: "52px" }}></th>
|
||||||
{configuredColumns.map((col: TreeColumnDef) => (
|
{configuredColumns.map((col: TreeColumnDef) => {
|
||||||
|
const centered = ["quantity", "loss_rate", "base_qty", "revision", "seq_no", "unit", "level"].includes(col.key)
|
||||||
|
|| col.key.includes("qty") || col.key.includes("quantity");
|
||||||
|
return (
|
||||||
<th
|
<th
|
||||||
key={col.key}
|
key={col.key}
|
||||||
className="border-r border-gray-100 px-3 py-2 text-left text-[11px] font-semibold text-gray-600"
|
className={cn(
|
||||||
style={{ width: col.width }}
|
"px-3 py-2.5 text-[11px] font-semibold text-gray-500",
|
||||||
|
centered ? "text-center" : "text-left",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{col.title || col.key}
|
{col.title || col.key}
|
||||||
</th>
|
</th>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="text-gray-400">
|
<tbody>
|
||||||
<tr className="border-b bg-white">
|
{/* 0레벨 루트 */}
|
||||||
<td className="border-r border-gray-50 px-2 py-2">
|
<tr className="border-b hover:bg-gray-50/50">
|
||||||
<ChevronDown className="h-3.5 w-3.5 text-gray-300" />
|
<td className="px-2 py-2 text-center">
|
||||||
|
<ChevronDown className="inline h-3.5 w-3.5 text-gray-400" />
|
||||||
</td>
|
</td>
|
||||||
{configuredColumns.map((col: TreeColumnDef, i: number) => (
|
{configuredColumns.map((col: TreeColumnDef) => {
|
||||||
<td key={col.key} className="border-r border-gray-50 px-3 py-2">
|
const centered = ["quantity", "loss_rate", "base_qty", "revision", "seq_no", "unit", "level"].includes(col.key)
|
||||||
{col.key === "level" ? "0" : col.key.includes("type") ? (
|
|| col.key.includes("qty") || col.key.includes("quantity");
|
||||||
<span className="rounded-md bg-blue-50 px-1.5 py-0.5 text-[10px] text-blue-500 ring-1 ring-inset ring-blue-200">제품</span>
|
return (
|
||||||
) : col.key.includes("quantity") || col.key.includes("qty") ? "30" : `예시${i + 1}`}
|
<td key={col.key} className={cn("px-3 py-2 text-gray-600", centered && "text-center")}>
|
||||||
|
{previewSampleValue(col, 0)}
|
||||||
</td>
|
</td>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="border-b bg-gray-50/30">
|
{/* 1레벨 자식 */}
|
||||||
<td className="border-r border-gray-50 px-2 py-2 pl-7">
|
<tr className="border-b hover:bg-gray-50/50">
|
||||||
<span className="inline-block h-1 w-1 rounded-full bg-gray-300" />
|
<td className="px-2 py-2 text-center" style={{ paddingLeft: `${INDENT_PX + 8}px` }}>
|
||||||
|
<Layers className="inline h-3.5 w-3.5 text-gray-300" />
|
||||||
</td>
|
</td>
|
||||||
{configuredColumns.map((col: TreeColumnDef, i: number) => (
|
{configuredColumns.map((col: TreeColumnDef) => {
|
||||||
<td key={col.key} className="border-r border-gray-50 px-3 py-2 text-gray-300">
|
const centered = ["quantity", "loss_rate", "base_qty", "revision", "seq_no", "unit", "level"].includes(col.key)
|
||||||
{col.key === "level" ? "1" : col.key.includes("type") ? (
|
|| col.key.includes("qty") || col.key.includes("quantity");
|
||||||
<span className="rounded-md bg-amber-50 px-1.5 py-0.5 text-[10px] text-amber-500 ring-1 ring-inset ring-amber-200">반제품</span>
|
return (
|
||||||
) : col.key.includes("quantity") || col.key.includes("qty") ? "3" : `예시${i + 1}`}
|
<td key={col.key} className={cn("px-3 py-2 text-gray-400", centered && "text-center")}>
|
||||||
|
{previewSampleValue(col, 1)}
|
||||||
</td>
|
</td>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
@ -839,6 +924,7 @@ export function BomTreeComponent({
|
||||||
detailTable={detailTable}
|
detailTable={detailTable}
|
||||||
onVersionLoaded={() => {
|
onVersionLoaded={() => {
|
||||||
if (selectedBomId) loadBomDetails(selectedBomId, selectedHeaderData);
|
if (selectedBomId) loadBomDetails(selectedBomId, selectedHeaderData);
|
||||||
|
window.dispatchEvent(new CustomEvent("refreshTable"));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Loader2, Plus, Trash2, Download } from "lucide-react";
|
import { Loader2, Plus, Trash2, Download, ShieldCheck } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { apiClient } from "@/lib/api/client";
|
import { apiClient } from "@/lib/api/client";
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ export function BomVersionModal({ open, onOpenChange, bomId, tableName = "bom_ve
|
||||||
const res = await apiClient.post(`/bom/${bomId}/versions/${versionId}/load`, { tableName, detailTable });
|
const res = await apiClient.post(`/bom/${bomId}/versions/${versionId}/load`, { tableName, detailTable });
|
||||||
if (res.data?.success) {
|
if (res.data?.success) {
|
||||||
onVersionLoaded?.();
|
onVersionLoaded?.();
|
||||||
onOpenChange(false);
|
loadVersions();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[BomVersion] 불러오기 실패:", error);
|
console.error("[BomVersion] 불러오기 실패:", error);
|
||||||
|
|
@ -90,6 +90,22 @@ export function BomVersionModal({ open, onOpenChange, bomId, tableName = "bom_ve
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleActivateVersion = async (versionId: string) => {
|
||||||
|
if (!bomId || !confirm("이 버전을 사용 확정하시겠습니까?\n기존 사용중 버전은 사용중지로 변경됩니다.")) return;
|
||||||
|
setActionId(versionId);
|
||||||
|
try {
|
||||||
|
const res = await apiClient.post(`/bom/${bomId}/versions/${versionId}/activate`, { tableName });
|
||||||
|
if (res.data?.success) {
|
||||||
|
loadVersions();
|
||||||
|
window.dispatchEvent(new CustomEvent("refreshTable"));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[BomVersion] 사용 확정 실패:", error);
|
||||||
|
} finally {
|
||||||
|
setActionId(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleDeleteVersion = async (versionId: string) => {
|
const handleDeleteVersion = async (versionId: string) => {
|
||||||
if (!bomId || !confirm("이 버전을 삭제하시겠습니까?")) return;
|
if (!bomId || !confirm("이 버전을 삭제하시겠습니까?")) return;
|
||||||
setActionId(versionId);
|
setActionId(versionId);
|
||||||
|
|
@ -179,7 +195,22 @@ export function BomVersionModal({ open, onOpenChange, bomId, tableName = "bom_ve
|
||||||
{isActing ? <Loader2 className="h-3 w-3 animate-spin" /> : <Download className="h-3 w-3" />}
|
{isActing ? <Loader2 className="h-3 w-3 animate-spin" /> : <Download className="h-3 w-3" />}
|
||||||
불러오기
|
불러오기
|
||||||
</Button>
|
</Button>
|
||||||
{ver.status !== "active" && (
|
{ver.status === "active" ? (
|
||||||
|
<span className="flex h-7 items-center rounded-md bg-emerald-50 px-2 text-[10px] font-medium text-emerald-600 ring-1 ring-inset ring-emerald-200">
|
||||||
|
사용중
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleActivateVersion(ver.id)}
|
||||||
|
disabled={isActing}
|
||||||
|
className="h-7 gap-1 px-2 text-[10px] border-emerald-300 text-emerald-600 hover:bg-emerald-50"
|
||||||
|
>
|
||||||
|
<ShieldCheck className="h-3 w-3" />
|
||||||
|
사용 확정
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
@ -190,6 +221,7 @@ export function BomVersionModal({ open, onOpenChange, bomId, tableName = "bom_ve
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" />
|
||||||
삭제
|
삭제
|
||||||
</Button>
|
</Button>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue