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 --- .../possible-items/add-item-dialog.tsx | 284 +++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 lib/tech-vendors/possible-items/add-item-dialog.tsx (limited to 'lib/tech-vendors/possible-items/add-item-dialog.tsx') diff --git a/lib/tech-vendors/possible-items/add-item-dialog.tsx b/lib/tech-vendors/possible-items/add-item-dialog.tsx new file mode 100644 index 00000000..ef15a5ce --- /dev/null +++ b/lib/tech-vendors/possible-items/add-item-dialog.tsx @@ -0,0 +1,284 @@ +"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 { + getItemsForTechVendor, + addTechVendorPossibleItem +} from "../service"; + +interface ItemData { + id: number; + itemCode: string | null; + itemList: string | null; + workType: string | null; + shipTypes?: string | null; + subItemList?: string | null; + createdAt: Date; + updatedAt: Date; +} + +interface AddItemDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + vendorId: number; +} + +export function AddItemDialog({ open, onOpenChange, vendorId }: AddItemDialogProps) { + // 아이템 관련 상태 + 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 && vendorId) { + loadItems(); + } + }, [open, vendorId]); + + // 아이템 검색 필터링 + 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 loadItems = async () => { + try { + setIsLoading(true); + console.log("Loading items for vendor:", vendorId); + const result = await getItemsForTechVendor(vendorId); + + if (result.error) { + throw new Error(result.error); + } + + console.log("Loaded items:", result.data.length, result.data); + // itemCode가 null이 아닌 항목만 필터링 + const validItems = result.data.filter(item => item.itemCode != null); + setItems(validItems); + } catch (error) { + console.error("Failed to load items:", error); + toast.error("아이템 목록을 불러오는데 실패했습니다."); + } finally { + setIsLoading(false); + } + }; + + const handleItemToggle = (item: ItemData) => { + if (!item.itemCode) return; // itemCode가 null인 경우 처리하지 않음 + + setSelectedItems(prev => { + // itemCode + shipTypes 조합으로 중복 체크 + const isSelected = prev.some(i => + i.itemCode === item.itemCode && i.shipTypes === item.shipTypes + ); + if (isSelected) { + return prev.filter(i => + !(i.itemCode === item.itemCode && i.shipTypes === item.shipTypes) + ); + } else { + return [...prev, item]; + } + }); + }; + + const handleSubmit = async () => { + if (selectedItems.length === 0) return; + + try { + setIsLoading(true); + let successCount = 0; + let errorCount = 0; + + for (const item of selectedItems) { + if (!item.itemCode) continue; // itemCode가 null인 경우 건너뛰기 + + const result = await addTechVendorPossibleItem({ + vendorId: vendorId, + itemCode: item.itemCode, + workType: item.workType || undefined, + shipTypes: item.shipTypes || undefined, + itemList: item.itemList || undefined, + subItemList: item.subItemList || undefined, + }); + + if (result.success) { + successCount++; + } else { + errorCount++; + console.error("Failed to add item:", item.itemCode, result.error); + } + } + + if (successCount > 0) { + toast.success( + `${successCount}개의 아이템이 추가되었습니다.${ + errorCount > 0 ? ` (${errorCount}개 실패)` : "" + }` + ); + + handleClose(); + } else { + toast.error("아이템 추가에 실패했습니다."); + } + } catch (error) { + console.error("Failed to add items:", error); + toast.error("아이템 추가 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + } + }; + + const handleClose = () => { + onOpenChange(false); + setTimeout(() => { + setSelectedItems([]); + setItemSearch(""); + setItems([]); + setFilteredItems([]); + }, 200); + }; + + return ( + + + + 아이템 추가 + + 추가할 아이템을 선택하세요. 복수 선택이 가능합니다. + + + +
+ {/* 검색 */} +
+ +
+ + setItemSearch(e.target.value)} + className="pl-10" + /> +
+
+ + {/* 선택된 아이템 표시 */} + {selectedItems.length > 0 && ( +
+ +
+ {selectedItems.map((item) => { + if (!item.itemCode) return null; + const itemKey = `${item.itemCode}${item.shipTypes ? `-${item.shipTypes}` : ''}`; + return ( + + {itemKey} + { + e.stopPropagation(); + handleItemToggle(item); + }} + /> + + ); + })} +
+
+ )} + + {/* 아이템 목록 */} +
+
+ {isLoading ? ( +
아이템 로딩 중...
+ ) : filteredItems.length === 0 && items.length === 0 ? ( +
+ 해당 벤더 타입에 대한 추가 가능한 아이템이 없습니다. +
+ ) : filteredItems.length === 0 ? ( +
+ 검색 결과가 없습니다. +
+ ) : ( + filteredItems.map((item) => { + if (!item.itemCode) return null; // itemCode가 null인 경우 렌더링하지 않음 + + // itemCode + shipTypes 조합으로 선택 여부 체크 + const isSelected = selectedItems.some(i => + i.itemCode === item.itemCode && i.shipTypes === item.shipTypes + ); + const itemKey = `${item.itemCode}${item.shipTypes ? `-${item.shipTypes}` : ''}`; + + return ( +
handleItemToggle(item)} + > +
+ {itemKey} +
+
+ {item.itemList || "-"} +
+
+ 공종: {item.workType || "-"} + {item.shipTypes && 선종: {item.shipTypes}} + {item.subItemList && 서브아이템: {item.subItemList}} +
+
+ ); + }) + )} +
+
+
+ +
+ + +
+
+
+ ); +} \ No newline at end of file -- cgit v1.2.3