/** * 품목 검색 모달 * * - 품목 코드/이름으로 검색 * - 품목 목록에서 선택 * - API 연동 */ "use client"; import { useState, useEffect, useMemo, useCallback } from "react"; import { Search, X, Loader2 } from "lucide-react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "../ui/dialog"; import { Input } from "../ui/input"; import { fetchItems } from "@/lib/api/items"; import type { ItemMaster, ItemType } from "@/types/item"; // ============================================================================= // Props // ============================================================================= interface ItemSearchModalProps { open: boolean; onOpenChange: (open: boolean) => void; onSelectItem: (item: { code: string; name: string; specification?: string }) => void; tabLabel?: string; /** 품목 유형 필터 (예: 'RM', 'SF', 'FG') */ itemType?: string; } // ============================================================================= // 컴포넌트 // ============================================================================= export function ItemSearchModal({ open, onOpenChange, onSelectItem, tabLabel, itemType, }: ItemSearchModalProps) { const [searchQuery, setSearchQuery] = useState(""); const [items, setItems] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); // 품목 목록 조회 const loadItems = useCallback(async (search?: string) => { setIsLoading(true); setError(null); try { const data = await fetchItems({ search: search || undefined, itemType: itemType as ItemType | undefined, per_page: 50, }); setItems(data); } catch (err) { console.error("[ItemSearchModal] 품목 조회 오류:", err); setError("품목 목록을 불러오는데 실패했습니다."); setItems([]); } finally { setIsLoading(false); } }, [itemType]); // 검색어 유효성 검사: 영문, 한글, 숫자 1자 이상 const isValidSearchQuery = useCallback((query: string) => { if (!query || !query.trim()) return false; return /[a-zA-Z가-힣ㄱ-ㅎㅏ-ㅣ0-9]/.test(query); }, []); // 모달 열릴 때 초기화 (자동 로드 안함) useEffect(() => { if (open) { setItems([]); setError(null); } }, [open]); // 검색어 변경 시 디바운스 검색 (유효한 검색어만) useEffect(() => { if (!open) return; // 검색어가 유효하지 않으면 결과 초기화 if (!isValidSearchQuery(searchQuery)) { setItems([]); return; } const timer = setTimeout(() => { loadItems(searchQuery); }, 300); return () => clearTimeout(timer); }, [searchQuery, open, loadItems, isValidSearchQuery]); // 검색 결과 그대로 사용 (서버에서 이미 필터링됨) const filteredItems = items; const handleSelect = (item: ItemMaster) => { onSelectItem({ code: item.itemCode, name: item.itemName, specification: item.specification || undefined, }); onOpenChange(false); setSearchQuery(""); }; const handleClose = () => { onOpenChange(false); setSearchQuery(""); }; return ( 품목 검색 {tabLabel && ({tabLabel})} {/* 검색 입력 */}
setSearchQuery(e.target.value)} className="pl-10 pr-10" /> {searchQuery && ( )}
{/* 품목 목록 */}
{isLoading ? (
품목 검색 중...
) : error ? (
{error}
) : filteredItems.length === 0 ? (
{!searchQuery ? "품목코드 또는 품목명을 입력하세요" : !isValidSearchQuery(searchQuery) ? "영문, 한글 또는 숫자 1자 이상 입력하세요" : "검색 결과가 없습니다"}
) : (
{filteredItems.map((item, index) => (
handleSelect(item)} className="p-3 hover:bg-blue-50 cursor-pointer transition-colors" >
{item.itemCode} {item.itemName} {item.hasInspectionTemplate && ( 수입검사 )}
{item.unit && ( {item.unit} )}
{item.specification && (

{item.specification}

)}
))}
)}
{/* 품목 개수 표시 */} {!isLoading && !error && (
총 {filteredItems.length}개 품목
)}
); }