"use client"; import * as React from "react"; import { Search, X } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { getItemsForVendorMapping, getConnectableVendorsForItem, connectItemWithVendors, } from "../service"; type ItemType = "SHIP" | "TOP" | "HULL"; interface ItemData { id: number; itemCode: string | null; itemList: string | null; workType: string | null; shipTypes?: string | null; subItemList?: string | null; itemType: ItemType; createdAt: Date; updatedAt: Date; } interface VendorData { id: number; vendorName: string; email: string | null; techVendorType: string; status: string; } interface ConnectItemVendorDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onConnected?: () => void; } export function ConnectItemVendorDialog({ open, onOpenChange, onConnected, }: ConnectItemVendorDialogProps) { const [items, setItems] = React.useState([]); const [filteredItems, setFilteredItems] = React.useState([]); const [itemSearch, setItemSearch] = React.useState(""); const [selectedItem, setSelectedItem] = React.useState(null); const [vendors, setVendors] = React.useState([]); const [filteredVendors, setFilteredVendors] = React.useState([]); const [vendorSearch, setVendorSearch] = React.useState(""); const [selectedVendorIds, setSelectedVendorIds] = React.useState([]); const [isLoadingItems, setIsLoadingItems] = React.useState(false); const [isLoadingVendors, setIsLoadingVendors] = React.useState(false); const [isSubmitting, setIsSubmitting] = React.useState(false); // 다이얼로그가 열릴 때 전체 아이템 목록 로드 React.useEffect(() => { if (open) { loadItems(); } }, [open]); // 아이템 검색 필터링 React.useEffect(() => { if (!itemSearch) { setFilteredItems(items); return; } const lowered = itemSearch.toLowerCase(); const filtered = items.filter((item) => [item.itemCode, item.itemList, item.workType, item.shipTypes, item.subItemList] .filter(Boolean) .some((value) => value?.toLowerCase().includes(lowered)) ); setFilteredItems(filtered); }, [items, itemSearch]); // 벤더 검색 필터링 React.useEffect(() => { if (!vendorSearch) { setFilteredVendors(vendors); return; } const lowered = vendorSearch.toLowerCase(); const filtered = vendors.filter((vendor) => [vendor.vendorName, vendor.email, vendor.techVendorType, vendor.status] .filter(Boolean) .some((value) => value?.toLowerCase().includes(lowered)) ); setFilteredVendors(filtered); }, [vendors, vendorSearch]); // 특정 아이템 선택 시 연결 가능한 벤더 목록 로드 React.useEffect(() => { if (!selectedItem) { setVendors([]); setFilteredVendors([]); setSelectedVendorIds([]); return; } loadVendors(selectedItem); }, [selectedItem]); const loadItems = async () => { setIsLoadingItems(true); try { const result = await getItemsForVendorMapping(); if (result.error) { throw new Error(result.error); } const validItems = (result.data as ItemData[]).filter((item) => item.itemCode != null); setItems(validItems); } catch (error) { console.error("Failed to load items for mapping:", error); toast.error("아이템 목록을 불러오는데 실패했습니다."); } finally { setIsLoadingItems(false); } }; const loadVendors = async (item: ItemData) => { setIsLoadingVendors(true); try { const result = await getConnectableVendorsForItem(item.id, item.itemType); if (result.error) { throw new Error(result.error); } setVendors(result.data as VendorData[]); } catch (error) { console.error("Failed to load vendors for item:", error); toast.error("연결 가능한 벤더 목록을 불러오는데 실패했습니다."); } finally { setIsLoadingVendors(false); } }; const handleItemSelect = (item: ItemData) => { if (!item.itemCode) return; setSelectedItem(item); }; const handleVendorToggle = (vendorId: number) => { setSelectedVendorIds((prev) => prev.includes(vendorId) ? prev.filter((id) => id !== vendorId) : [...prev, vendorId] ); }; const handleSubmit = async () => { if (!selectedItem || selectedVendorIds.length === 0) return; setIsSubmitting(true); try { const result = await connectItemWithVendors({ itemId: selectedItem.id, itemType: selectedItem.itemType, vendorIds: selectedVendorIds, }); if (!result.success) { throw new Error(result.error || "연결에 실패했습니다."); } const successCount = result.successCount || 0; const skippedCount = result.skipped?.length || 0; toast.success( `${successCount}개 벤더와 연결되었습니다${ skippedCount > 0 ? ` (${skippedCount}개 중복 제외)` : "" }` ); onConnected?.(); handleClose(); } catch (error) { console.error("Failed to connect item with vendors:", error); toast.error(error instanceof Error ? error.message : "연결 중 오류가 발생했습니다."); } finally { setIsSubmitting(false); } }; const handleClose = () => { onOpenChange(false); setTimeout(() => { setItemSearch(""); setVendorSearch(""); setSelectedItem(null); setSelectedVendorIds([]); setItems([]); setFilteredItems([]); setVendors([]); setFilteredVendors([]); }, 200); }; return ( 아이템 기준 벤더 연결 연결할 아이템을 먼저 선택한 후, 해당 아이템과 연결할 벤더를 선택하세요.
{/* 아이템 선택 영역 */}
setItemSearch(e.target.value)} className="pl-10" />
{selectedItem && (
{[selectedItem.itemType, selectedItem.itemCode, selectedItem.shipTypes] .filter(Boolean) .join("-")} { e.stopPropagation(); setSelectedItem(null); }} />
)}
{isLoadingItems ? (
아이템 로딩 중...
) : filteredItems.length === 0 ? (
아이템이 없습니다.
) : (
{filteredItems.map((item) => { if (!item.itemCode) return null; const isSelected = selectedItem?.id === item.id && selectedItem.itemType === item.itemType; const itemKey = `${item.itemType}-${item.id}-${item.itemCode}${item.shipTypes ? `-${item.shipTypes}` : ""}`; return (
handleItemSelect(item)} >
{[`[${item.itemType}]`, item.itemCode, item.shipTypes] .filter(Boolean) .join(" ")}
{item.itemList || "-"}
공종: {item.workType || "-"} {item.shipTypes && 선종: {item.shipTypes}} {item.subItemList && 서브아이템: {item.subItemList}}
); })}
)}
{/* 벤더 선택 영역 */}
setVendorSearch(e.target.value)} className="pl-10" disabled={!selectedItem} />
{selectedVendorIds.length > 0 && (
{vendors .filter((vendor) => selectedVendorIds.includes(vendor.id)) .map((vendor) => ( {vendor.vendorName} { e.stopPropagation(); handleVendorToggle(vendor.id); }} /> ))}
)}
{!selectedItem ? (
아이템을 먼저 선택해주세요.
) : isLoadingVendors ? (
벤더 로딩 중...
) : filteredVendors.length === 0 ? (
연결 가능한 벤더가 없습니다.
) : (
{filteredVendors.map((vendor) => { const isSelected = selectedVendorIds.includes(vendor.id); return (
handleVendorToggle(vendor.id)} >
{vendor.vendorName}
{vendor.email || "-"}
타입: {vendor.techVendorType || "-"} 상태: {vendor.status || "-"}
); })}
)}
); }