From 14f61e24947fb92dd71ec0a7196a6e815f8e66da Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 21 Jul 2025 07:54:26 +0000 Subject: (최겸)기술영업 RFQ 담당자 초대, 요구사항 반영 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table/add-possible-item-dialog.tsx | 450 +++++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 lib/tech-vendor-possible-items/table/add-possible-item-dialog.tsx (limited to 'lib/tech-vendor-possible-items/table/add-possible-item-dialog.tsx') diff --git a/lib/tech-vendor-possible-items/table/add-possible-item-dialog.tsx b/lib/tech-vendor-possible-items/table/add-possible-item-dialog.tsx new file mode 100644 index 00000000..cdce60af --- /dev/null +++ b/lib/tech-vendor-possible-items/table/add-possible-item-dialog.tsx @@ -0,0 +1,450 @@ +"use client"; + +import * as React from "react"; +import { Search, Plus, X } from "lucide-react"; + +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Badge } from "@/components/ui/badge"; +import { useToast } from "@/hooks/use-toast"; +import { + getAllTechVendors, + createTechVendorPossibleItem, + getItemsByVendorType +} from "@/lib/tech-vendor-possible-items/service"; + +interface TechVendor { + id: number; + vendorCode: string | null; + vendorName: string; + techVendorType: string; +} + +interface ItemData { + itemCode: string; + itemList: string | null; + workType: string | null; + shipTypes?: string | null; + subItemList?: string | null; +} + +interface AddPossibleItemDialogProps { + children?: React.ReactNode; + onSuccess?: () => void; +} + +export function AddPossibleItemDialog({ + children, + onSuccess +}: AddPossibleItemDialogProps) { + const { toast } = useToast(); + const [open, setOpen] = React.useState(false); + + // 벤더 관련 상태 + const [vendors, setVendors] = React.useState([]); + const [filteredVendors, setFilteredVendors] = React.useState([]); + const [vendorSearch, setVendorSearch] = React.useState(""); + const [selectedVendor, setSelectedVendor] = React.useState(null); + + // 아이템 관련 상태 + const [items, setItems] = React.useState([]); + const [filteredItems, setFilteredItems] = React.useState([]); + const [itemSearch, setItemSearch] = React.useState(""); + const [selectedItems, setSelectedItems] = React.useState([]); + + const [isLoading, setIsLoading] = React.useState(false); + + // 벤더 목록 로드 + React.useEffect(() => { + if (open) { + loadVendors(); + } + }, [open]); + + // 벤더 검색 필터링 + React.useEffect(() => { + if (!vendorSearch) { + setFilteredVendors(vendors); + } else { + const filtered = vendors.filter(vendor => + vendor.vendorName.toLowerCase().includes(vendorSearch.toLowerCase()) || + vendor.vendorCode?.toLowerCase().includes(vendorSearch.toLowerCase()) + ); + setFilteredVendors(filtered); + } + }, [vendors, vendorSearch]); + + // 아이템 검색 필터링 + React.useEffect(() => { + if (!itemSearch) { + setFilteredItems(items); + } else { + const filtered = items.filter(item => + item.itemCode.toLowerCase().includes(itemSearch.toLowerCase()) || + item.itemList?.toLowerCase().includes(itemSearch.toLowerCase()) || + item.workType?.toLowerCase().includes(itemSearch.toLowerCase()) + ); + setFilteredItems(filtered); + } + }, [items, itemSearch]); + + const loadVendors = async () => { + try { + setIsLoading(true); + const vendorData = await getAllTechVendors(); + setVendors(vendorData); + } catch (error) { + console.error("Failed to load vendors:", error); + toast({ + title: "오류", + description: "벤더 목록을 불러오는데 실패했습니다.", + variant: "destructive", + }); + } finally { + setIsLoading(false); + } + }; + + const loadItemsByVendorType = async (vendorTypes: string) => { + try { + setIsLoading(true); + console.log("Loading items for vendor types:", vendorTypes); + const itemData = await getItemsByVendorType(vendorTypes); + console.log("Loaded items:", itemData.length, itemData); + setItems(itemData); + } catch (error) { + console.error("Failed to load items:", error); + toast({ + title: "오류", + description: "아이템 목록을 불러오는데 실패했습니다.", + variant: "destructive", + }); + } finally { + setIsLoading(false); + } + }; + + const handleVendorSelect = (vendor: TechVendor) => { + setSelectedVendor(vendor); + setSelectedItems([]); // 벤더 변경시 선택된 아이템 초기화 + loadItemsByVendorType(vendor.techVendorType); + }; + + const handleItemToggle = (item: ItemData) => { + setSelectedItems(prev => { + const isSelected = prev.some(i => i.itemCode === item.itemCode); + if (isSelected) { + return prev.filter(i => i.itemCode !== item.itemCode); + } else { + return [...prev, item]; + } + }); + }; + + const handleSubmit = async () => { + if (!selectedVendor || selectedItems.length === 0) return; + + try { + setIsLoading(true); + let successCount = 0; + let errorCount = 0; + + for (const item of selectedItems) { + const result = await createTechVendorPossibleItem({ + vendorId: selectedVendor.id, + itemCode: item.itemCode, + workType: item.workType, + shipTypes: item.shipTypes, + itemList: item.itemList, + subItemList: item.subItemList, + }); + + if (result.success) { + successCount++; + } else { + errorCount++; + } + } + + if (successCount > 0) { + toast({ + title: "성공", + description: `${successCount}개의 아이템이 추가되었습니다.${errorCount > 0 ? ` (${errorCount}개 실패)` : ""}`, + }); + + handleClose(); + onSuccess?.(); + } else { + toast({ + title: "오류", + description: "아이템 추가에 실패했습니다.", + variant: "destructive", + }); + } + } catch (error) { + console.error("Failed to add items:", error); + toast({ + title: "오류", + description: "아이템 추가 중 오류가 발생했습니다.", + variant: "destructive", + }); + } finally { + setIsLoading(false); + } + }; + + const handleClose = () => { + setOpen(false); + setTimeout(() => { + setSelectedVendor(null); + setSelectedItems([]); + setVendorSearch(""); + setItemSearch(""); + setVendors([]); + setItems([]); + setFilteredVendors([]); + setFilteredItems([]); + }, 200); + }; + + const parseVendorTypes = (vendorType: string): string[] => { + if (!vendorType) return []; + + // JSON 배열 형태인지 확인 + if (vendorType.startsWith('[') && vendorType.endsWith(']')) { + try { + const parsed = JSON.parse(vendorType); + return Array.isArray(parsed) ? parsed.filter(Boolean) : [vendorType]; + } catch { + return [vendorType]; + } + } + + // 콤마로 구분된 문자열인지 확인 + if (vendorType.includes(',')) { + return vendorType.split(',').map(t => t.trim()).filter(Boolean); + } + + // 단일 문자열 + return [vendorType.trim()].filter(Boolean); + }; + + return ( + + + {children || ( + + )} + + + + + 벤더별 아이템 추가 + + + 왼쪽에서 벤더를 선택하고, 오른쪽에서 아이템을 선택하세요. + + + +
+
+ {/* 왼쪽: 벤더 선택/표시 */} +
+ {!selectedVendor ? ( + <> +
+ +
+ setVendorSearch(e.target.value)} + className="pl-10" + /> +
+
+ +
+
+ {isLoading ? ( +
로딩 중...
+ ) : filteredVendors.length === 0 ? ( +
+ 검색 결과가 없습니다. +
+ ) : ( + filteredVendors.map((vendor) => ( +
handleVendorSelect(vendor)} + > +
{vendor.vendorName}
+
+ {vendor.vendorCode} +
+
+ {parseVendorTypes(vendor.techVendorType).map((type, index) => ( + + {type} + + ))} +
+
+ )) + )} +
+
+ + ) : ( +
+
+ + +
+
+
{selectedVendor?.vendorName}
+
+ {selectedVendor?.vendorCode} +
+
+ {selectedVendor && parseVendorTypes(selectedVendor.techVendorType).map((type, index) => ( + + {type} + + ))} +
+
+
+ )} +
+ + + + {/* 오른쪽: 아이템 선택 */} +
+ {selectedVendor ? ( + <> + + + +
+ setItemSearch(e.target.value)} + className="pl-10" + /> +
+ + + {selectedItems.length > 0 && ( +
+ +
+ {selectedItems.map((item) => ( + + {item.itemCode} + { + e.stopPropagation(); + handleItemToggle(item); + }} + /> + + ))} +
+
+ )} + +
+
+ {isLoading ? ( +
아이템 로딩 중...
+ ) : filteredItems.length === 0 && items.length === 0 ? ( +
+ 해당 벤더 타입에 대한 아이템이 없습니다. +
+ ) : filteredItems.length === 0 ? ( +
+ 검색 결과가 없습니다. +
+ ) : ( + filteredItems.map((item) => { + const isSelected = selectedItems.some(i => i.itemCode === item.itemCode); + return ( +
handleItemToggle(item)} + > +
{item.itemCode}
+
+ {item.itemList || "-"} +
+
+ 공종: {item.workType || "-"} + {item.shipTypes && 선종: {item.shipTypes}} + {item.subItemList && 서브아이템: {item.subItemList}} +
+
+ ); + }) + )} +
+
+ + ) : ( +
+ 왼쪽에서 벤더를 선택하세요. +
+ )} +
+
+
+ +
+ + +
+
+
+ ); +} \ No newline at end of file -- cgit v1.2.3