ERP-node/frontend/components/admin/dashboard/widgets/yard-3d/MaterialLibrary.tsx

193 lines
6.8 KiB
TypeScript
Raw Normal View History

"use client";
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Search, Loader2 } from "lucide-react";
import { materialApi } from "@/lib/api/yardLayoutApi";
interface TempMaterial {
id: number;
material_code: string;
material_name: string;
category: string;
unit: string;
default_color: string;
description: string;
}
interface MaterialLibraryProps {
isOpen: boolean;
onClose: () => void;
onSelect: (material: TempMaterial) => void;
}
export default function MaterialLibrary({ isOpen, onClose, onSelect }: MaterialLibraryProps) {
const [materials, setMaterials] = useState<TempMaterial[]>([]);
const [categories, setCategories] = useState<string[]>([]);
const [searchText, setSearchText] = useState("");
const [selectedCategory, setSelectedCategory] = useState<string>("");
const [isLoading, setIsLoading] = useState(false);
const [selectedMaterial, setSelectedMaterial] = useState<TempMaterial | null>(null);
// 자재 목록 로드
const loadMaterials = async () => {
try {
setIsLoading(true);
const [materialsResponse, categoriesResponse] = await Promise.all([
materialApi.getTempMaterials({
search: searchText || undefined,
category: selectedCategory || undefined,
page: 1,
limit: 50,
}),
materialApi.getCategories(),
]);
if (materialsResponse.success) {
setMaterials(materialsResponse.data);
}
if (categoriesResponse.success) {
setCategories(categoriesResponse.data);
}
} catch (error) {
console.error("자재 목록 조회 실패:", error);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
if (isOpen) {
loadMaterials();
}
}, [isOpen, searchText, selectedCategory]);
// 자재 선택 및 추가
const handleSelectMaterial = () => {
if (selectedMaterial) {
onSelect(selectedMaterial);
setSelectedMaterial(null);
onClose();
}
};
// 모달 닫기
const handleClose = () => {
setSelectedMaterial(null);
setSearchText("");
setSelectedCategory("");
onClose();
};
return (
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="max-w-4xl">
<DialogHeader>
<DialogTitle> </DialogTitle>
</DialogHeader>
<div className="space-y-4">
{/* 검색 및 필터 */}
<div className="flex items-center gap-2">
<div className="relative flex-1">
2025-10-29 17:53:03 +09:00
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="자재 코드 또는 이름 검색..."
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
className="pl-9"
/>
</div>
<select
value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)}
2025-10-29 17:53:03 +09:00
className="rounded-md border border-border px-3 py-2 text-sm"
>
<option value=""> </option>
{categories.map((category) => (
<option key={category} value={category}>
{category}
</option>
))}
</select>
</div>
{/* 자재 목록 */}
{isLoading ? (
<div className="flex h-64 items-center justify-center">
2025-10-29 17:53:03 +09:00
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
) : materials.length === 0 ? (
2025-10-29 17:53:03 +09:00
<div className="flex h-64 items-center justify-center text-muted-foreground">
{searchText || selectedCategory ? "검색 결과가 없습니다" : "등록된 자재가 없습니다"}
</div>
) : (
<div className="max-h-96 overflow-auto rounded-md">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[50px]"></TableHead>
<TableHead> </TableHead>
<TableHead> </TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{materials.map((material) => (
<TableRow
key={material.id}
className={`cursor-pointer ${
2025-10-29 17:53:03 +09:00
selectedMaterial?.id === material.id ? "bg-primary/10" : "hover:bg-muted"
}`}
onClick={() => setSelectedMaterial(material)}
>
<TableCell>
<div className="h-6 w-6 rounded border" style={{ backgroundColor: material.default_color }} />
</TableCell>
<TableCell className="font-medium">{material.material_code}</TableCell>
<TableCell>{material.material_name}</TableCell>
<TableCell>{material.category}</TableCell>
<TableCell>{material.unit}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
{/* 선택된 자재 정보 */}
{selectedMaterial && (
2025-10-29 17:53:03 +09:00
<div className="rounded-lg bg-primary/10 p-4">
<div className="mb-2 text-sm font-medium text-primary"> </div>
<div className="flex items-center gap-4">
<div className="h-10 w-10 rounded border" style={{ backgroundColor: selectedMaterial.default_color }} />
<div className="flex-1">
<div className="font-medium">{selectedMaterial.material_name}</div>
2025-10-29 17:53:03 +09:00
<div className="text-sm text-foreground">{selectedMaterial.material_code}</div>
</div>
</div>
{selectedMaterial.description && (
2025-10-29 17:53:03 +09:00
<div className="mt-2 text-sm text-foreground">{selectedMaterial.description}</div>
)}
</div>
)}
</div>
<DialogFooter>
<Button variant="outline" onClick={handleClose}>
</Button>
<Button onClick={handleSelectMaterial} disabled={!selectedMaterial}>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}