ERP-node/frontend/lib/registry/components/v2-bom-tree/BomDetailEditModal.tsx

213 lines
6.9 KiB
TypeScript
Raw Normal View History

"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 { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Loader2 } from "lucide-react";
import { apiClient } from "@/lib/api/client";
interface BomDetailEditModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
node: Record<string, any> | null;
isRootNode?: boolean;
tableName: string;
onSaved?: () => void;
}
export function BomDetailEditModal({
open,
onOpenChange,
node,
isRootNode = false,
tableName,
onSaved,
}: BomDetailEditModalProps) {
const [formData, setFormData] = useState<Record<string, any>>({});
const [saving, setSaving] = useState(false);
useEffect(() => {
if (node && open) {
if (isRootNode) {
setFormData({
base_qty: node.base_qty || "",
unit: node.unit || "",
remark: node.remark || "",
});
} else {
setFormData({
quantity: node.quantity || "",
unit: node.unit || node.detail_unit || "",
process_type: node.process_type || "",
base_qty: node.base_qty || "",
loss_rate: node.loss_rate || "",
remark: node.remark || "",
});
}
}
}, [node, open, isRootNode]);
const handleChange = (field: string, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleSave = async () => {
if (!node) return;
setSaving(true);
try {
const targetTable = isRootNode ? "bom" : tableName;
const realId = isRootNode ? node.id?.replace("__root_", "") : node.id;
await apiClient.put(`/table-management/tables/${targetTable}/${realId}`, formData);
onSaved?.();
onOpenChange(false);
} catch (error) {
console.error("[BomDetailEdit] 저장 실패:", error);
} finally {
setSaving(false);
}
};
if (!node) return null;
const itemCode = isRootNode
? node.child_item_code || node.item_code || node.bom_number || "-"
: node.child_item_code || "-";
const itemName = isRootNode
? node.child_item_name || node.item_name || "-"
: node.child_item_name || "-";
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[95vw] sm:max-w-[500px]">
<DialogHeader>
<DialogTitle className="text-base sm:text-lg">
{isRootNode ? "BOM 헤더 수정" : "품목 수정"}
</DialogTitle>
<DialogDescription className="text-xs sm:text-sm">
{isRootNode
? "BOM 기본 정보를 수정합니다"
: "선택한 품목의 BOM 구성 정보를 수정합니다"}
</DialogDescription>
</DialogHeader>
<div className="space-y-3 sm:space-y-4">
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="text-xs sm:text-sm"></Label>
<Input value={itemCode} disabled className="mt-1 h-8 text-xs sm:h-10 sm:text-sm" />
</div>
<div>
<Label className="text-xs sm:text-sm"></Label>
<Input value={itemName} disabled className="mt-1 h-8 text-xs sm:h-10 sm:text-sm" />
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="text-xs sm:text-sm">
{isRootNode ? "기준수량" : "구성수량"} *
</Label>
<Input
type="number"
value={isRootNode ? formData.base_qty : formData.quantity}
onChange={(e) => handleChange(isRootNode ? "base_qty" : "quantity", e.target.value)}
className="mt-1 h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div>
<Label className="text-xs sm:text-sm"></Label>
<Input
value={formData.unit}
onChange={(e) => handleChange("unit", e.target.value)}
className="mt-1 h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
</div>
{!isRootNode && (
<>
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="text-xs sm:text-sm"></Label>
<Input
value={formData.process_type}
onChange={(e) => handleChange("process_type", e.target.value)}
placeholder="예: 조립공정"
className="mt-1 h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div>
<Label className="text-xs sm:text-sm"> (%)</Label>
<Input
type="number"
value={formData.loss_rate}
onChange={(e) => handleChange("loss_rate", e.target.value)}
className="mt-1 h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="text-xs sm:text-sm"></Label>
<Input
value={node.child_specification || node.specification || "-"}
disabled
className="mt-1 h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div>
<Label className="text-xs sm:text-sm"></Label>
<Input
value={node.child_material || node.material || "-"}
disabled
className="mt-1 h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
</div>
</>
)}
<div>
<Label className="text-xs sm:text-sm"></Label>
<Textarea
value={formData.remark}
onChange={(e) => handleChange("remark", e.target.value)}
placeholder="비고 사항을 입력하세요"
className="mt-1 min-h-[60px] text-xs sm:text-sm"
/>
</div>
</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>
<Button
onClick={handleSave}
disabled={saving}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}