From 8642ee064ddf96f1db2b948b4cc8bbbd6cfee820 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 12 Nov 2025 10:42:36 +0000 Subject: (최겸) 구매 일반계약, 입찰 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../procurement-item/procurement-item-selector.tsx | 176 +++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 components/common/selectors/procurement-item/procurement-item-selector.tsx (limited to 'components/common/selectors/procurement-item/procurement-item-selector.tsx') diff --git a/components/common/selectors/procurement-item/procurement-item-selector.tsx b/components/common/selectors/procurement-item/procurement-item-selector.tsx new file mode 100644 index 00000000..5650959c --- /dev/null +++ b/components/common/selectors/procurement-item/procurement-item-selector.tsx @@ -0,0 +1,176 @@ +"use client"; + +import React, { useState, useCallback, useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { + Command, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, +} from "@/components/ui/command"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Check, ChevronsUpDown, X, Search } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { useDebounce } from "@/hooks/use-debounce"; +import { searchProcurementItemsForSelector, ProcurementSearchItem, getProcurementItemByCode } from "./procurement-item-service"; + +interface ProcurementItemSelectorProps { + selectedProcurementItem?: ProcurementSearchItem | null; + onProcurementItemSelect?: (item: ProcurementSearchItem | null) => void; + onClear?: () => void; + placeholder?: string; + disabled?: boolean; + className?: string; +} + +export function ProcurementItemSelector({ + selectedProcurementItem, + onProcurementItemSelect, + onClear, + placeholder = "품목을 검색하세요...", + disabled = false, + className, +}: ProcurementItemSelectorProps) { + const [open, setOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(""); + const [searchResults, setSearchResults] = useState([]); + const [isLoading, setIsLoading] = useState(false); + + const debouncedSearchQuery = useDebounce(searchQuery, 300); + + // 검색 쿼리가 변경될 때마다 검색 실행 + useEffect(() => { + const performSearch = async () => { + if (debouncedSearchQuery.length < 1) { + setSearchResults([]); + return; + } + + setIsLoading(true); + try { + const results = await searchProcurementItemsForSelector(debouncedSearchQuery); + setSearchResults(results); + } catch (error) { + console.error("품목 검색 실패:", error); + setSearchResults([]); + } finally { + setIsLoading(false); + } + }; + + performSearch(); + }, [debouncedSearchQuery]); + + // 품목 선택 핸들러 + const handleSelect = useCallback((item: ProcurementSearchItem) => { + onProcurementItemSelect?.(item); + setOpen(false); + setSearchQuery(""); + setSearchResults([]); + }, [onProcurementItemSelect]); + + // 선택 해제 핸들러 + const handleClear = useCallback(() => { + onProcurementItemSelect?.(null); + onClear?.(); + setSearchQuery(""); + setSearchResults([]); + }, [onProcurementItemSelect, onClear]); + + return ( +
+ + + + + +
+
+ + setSearchQuery(e.target.value)} + className="pl-8" + autoFocus + /> +
+
+ + + + {isLoading ? ( +
+ 검색 중... +
+ ) : searchQuery.length < 1 ? ( +
+ 품목코드 또는 품목명을 입력하세요 +
+ ) : ( +
+ 검색 결과가 없습니다 +
+ )} +
+ + {searchResults.map((item) => ( + handleSelect(item)} + className="cursor-pointer" + > + +
+ {item.itemCode} + {item.itemName} +
+
+ ))} +
+
+
+
+
+ + {/* 선택 해제 버튼 */} + {selectedProcurementItem && ( + + )} +
+ ); +} -- cgit v1.2.3