119 lines
3.9 KiB
TypeScript
119 lines
3.9 KiB
TypeScript
/**
|
|
* 품목 검색 모달
|
|
*
|
|
* SearchableSelectionModal 공통 컴포넌트 기반
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { useCallback } from 'react';
|
|
import { SearchableSelectionModal } from '@/components/organisms/SearchableSelectionModal';
|
|
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; unit?: string }) => void;
|
|
tabLabel?: string;
|
|
/** 품목 유형 필터 (예: 'RM', 'SF', 'FG') */
|
|
itemType?: string;
|
|
/** BOM 카테고리 필터 (material, motor, controller, steel, parts, inspection) */
|
|
bomCategory?: string;
|
|
}
|
|
|
|
// 검색어 유효성: 영문, 한글, 숫자 1자 이상
|
|
const isValidSearchQuery = (query: string) => {
|
|
if (!query || !query.trim()) return false;
|
|
return /[a-zA-Z가-힣ㄱ-ㅎㅏ-ㅣ0-9]/.test(query);
|
|
};
|
|
|
|
// =============================================================================
|
|
// 컴포넌트
|
|
// =============================================================================
|
|
|
|
export function ItemSearchModal({
|
|
open,
|
|
onOpenChange,
|
|
onSelectItem,
|
|
tabLabel,
|
|
itemType,
|
|
bomCategory,
|
|
}: ItemSearchModalProps) {
|
|
const handleFetchData = useCallback(async (query: string) => {
|
|
const data = await fetchItems({
|
|
search: query || undefined,
|
|
itemType: itemType as ItemType | undefined,
|
|
bom_category: bomCategory || undefined,
|
|
per_page: 50,
|
|
});
|
|
return data;
|
|
}, [itemType, bomCategory]);
|
|
|
|
const handleSelect = useCallback((item: ItemMaster) => {
|
|
onSelectItem({
|
|
code: item.itemCode,
|
|
name: item.itemName,
|
|
specification: item.specification || undefined,
|
|
unit: item.unit || undefined,
|
|
});
|
|
}, [onSelectItem]);
|
|
|
|
return (
|
|
<SearchableSelectionModal<ItemMaster>
|
|
open={open}
|
|
onOpenChange={onOpenChange}
|
|
title={
|
|
<>
|
|
품목 검색
|
|
{tabLabel && <span className="text-sm font-normal text-gray-500 ml-2">({tabLabel})</span>}
|
|
</>
|
|
}
|
|
searchPlaceholder="품목코드 또는 품목명 검색..."
|
|
fetchData={handleFetchData}
|
|
keyExtractor={(item) => item.id?.toString() ?? item.itemCode}
|
|
validateSearch={isValidSearchQuery}
|
|
invalidSearchMessage="영문, 한글 또는 숫자 1자 이상 입력하세요"
|
|
emptyQueryMessage="품목코드 또는 품목명을 입력하세요"
|
|
loadingMessage="품목 검색 중..."
|
|
dialogClassName="sm:max-w-[500px]"
|
|
infoText={(items, isLoading) =>
|
|
!isLoading ? (
|
|
<span className="text-xs text-gray-400 text-right block">
|
|
총 {items.length}개 품목
|
|
</span>
|
|
) : null
|
|
}
|
|
mode="single"
|
|
onSelect={handleSelect}
|
|
renderItem={(item) => (
|
|
<div className="p-3 hover:bg-blue-50 transition-colors">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<span className="font-semibold text-gray-900">{item.itemCode}</span>
|
|
<span className="text-sm text-gray-600">{item.itemName}</span>
|
|
{item.hasInspectionTemplate && (
|
|
<span className="text-xs text-white bg-green-500 px-1.5 py-0.5 rounded">
|
|
수입검사
|
|
</span>
|
|
)}
|
|
</div>
|
|
{item.unit && (
|
|
<span className="text-xs text-gray-400 bg-gray-100 px-2 py-0.5 rounded">
|
|
{item.unit}
|
|
</span>
|
|
)}
|
|
</div>
|
|
{item.specification && (
|
|
<p className="text-xs text-gray-400 mt-1">{item.specification}</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
/>
|
|
);
|
|
}
|