'use client'; import { useState, useEffect, useCallback } from 'react'; import { getItemList, getItemTypeOptions, type ItemOption } from './actions'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Checkbox } from '@/components/ui/checkbox'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Search } from 'lucide-react'; import type { ClassificationRule } from '@/types/process'; import { PROCESS_CATEGORY_OPTIONS } from '@/types/process'; import { toast } from 'sonner'; // 공정 필터 옵션 const PROCESS_FILTER_OPTIONS = [ { value: 'all', label: '전체' }, { value: '스크린', label: '스크린' }, { value: '슬릿', label: '슬릿' }, { value: '절곡', label: '절곡' }, ]; // 공정 필터에 따른 구분 필터 옵션 function getCategoryFilterOptions(processFilter: string): { value: string; label: string }[] { if (processFilter === 'all') { return [{ value: 'all', label: '전체' }]; } const categories = PROCESS_CATEGORY_OPTIONS[processFilter]; if (!categories || categories.length === 0) { return [{ value: 'all', label: '전체' }]; } // 스크린의 경우 '없음'만 있으므로 전체만 표시 if (categories.length === 1 && categories[0].value === '없음') { return [{ value: 'all', label: '전체' }]; } return [{ value: 'all', label: '전체' }, ...categories]; } interface RuleModalProps { open: boolean; onOpenChange: (open: boolean) => void; onAdd: (rule: Omit) => void; editRule?: ClassificationRule; /** 현재 공정 ID (다른 공정에 이미 배정된 품목 제외용) */ processId?: string; /** 현재 공정명 (하단 안내 문구용) */ processName?: string; /** 이미 등록된 품목 ID 목록 (검색 결과에서 제외) */ registeredItemIds?: Set; } export function RuleModal({ open, onOpenChange, onAdd, editRule, processId, processName, registeredItemIds }: RuleModalProps) { // 검색/필터 상태 const [searchKeyword, setSearchKeyword] = useState(''); const [selectedItemType, setSelectedItemType] = useState('all'); const [selectedItemIds, setSelectedItemIds] = useState>(new Set()); // 공정/구분 필터 상태 const [processFilter, setProcessFilter] = useState('all'); const [, setCategoryFilter] = useState('all'); // 품목 목록 API 상태 const [itemList, setItemList] = useState([]); const [isItemsLoading, setIsItemsLoading] = useState(false); // 품목 유형 옵션 (common_codes에서 동적 조회) const [, setItemTypeOptions] = useState>([ { value: 'all', label: '전체' }, ]); // 구분 필터 옵션 (공정 필터에 따라 변경) const _categoryFilterOptions = getCategoryFilterOptions(processFilter); // 품목 목록 로드 const loadItems = useCallback(async (q?: string, itemType?: string) => { setIsItemsLoading(true); const items = await getItemList({ q: q || undefined, itemType: itemType === 'all' ? undefined : itemType, size: 1000, excludeProcessId: processId, }); setItemList(items); setIsItemsLoading(false); }, [processId]); // 검색어 유효성 검사 함수 const isValidSearchKeyword = (keyword: string): boolean => { if (!keyword || keyword.trim() === '') return false; const trimmed = keyword.trim(); const hasKorean = /[가-힣]/.test(trimmed); if (hasKorean) return trimmed.length >= 1; return trimmed.length >= 2; }; // 검색어 변경 시 API 호출 (debounce) useEffect(() => { if (!isValidSearchKeyword(searchKeyword)) { setItemList([]); return; } const timer = setTimeout(() => { loadItems(searchKeyword, selectedItemType); }, 300); return () => clearTimeout(timer); }, [searchKeyword, selectedItemType, loadItems]); // 모달 열릴 때 초기화 useEffect(() => { if (open) { // 품목유형 옵션 로드 getItemTypeOptions().then((options) => { setItemTypeOptions([{ value: 'all', label: '전체' }, ...options]); }); if (editRule) { // 수정 모드: 기존 선택된 품목 ID 설정 if (editRule.registrationType === 'individual') { const ids = editRule.conditionValue.split(',').filter(Boolean); setSelectedItemIds(new Set(ids)); } else { setSelectedItemIds(new Set()); } } else { setSelectedItemIds(new Set()); } setSearchKeyword(''); setSelectedItemType('all'); setProcessFilter('all'); setCategoryFilter('all'); setItemList([]); } }, [open, editRule]); // 공정 필터 변경 시 구분 필터 리셋 useEffect(() => { setCategoryFilter('all'); }, [processFilter]); // 품목이 다른 공정에 배정되었는지 확인 (현재 공정 제외) const isAssignedToOtherProcess = useCallback((item: ItemOption): boolean => { if (!item.assignedProcesses?.length) return false; if (!processId) return item.assignedProcesses.length > 0; return item.assignedProcesses.some((ap) => String(ap.processId) !== processId); }, [processId]); // 이미 등록된 품목인지 확인 const isAlreadyRegistered = useCallback((item: ItemOption): boolean => { return registeredItemIds?.has(item.id) ?? false; }, [registeredItemIds]); // 품목 선택 불가 여부 const isItemDisabled = useCallback((item: ItemOption): boolean => { return isAssignedToOtherProcess(item) || isAlreadyRegistered(item); }, [isAssignedToOtherProcess, isAlreadyRegistered]); // 배정된 공정명 (현재 공정 제외) const getAssignedProcessName = useCallback((item: ItemOption): string | null => { if (!item.assignedProcesses?.length) return null; const otherProcesses = processId ? item.assignedProcesses.filter((ap) => String(ap.processId) !== processId) : item.assignedProcesses; if (otherProcesses.length === 0) return null; return otherProcesses.map((ap) => ap.processName).join(', '); }, [processId]); // 체크박스 토글 const handleToggleItem = (id: string) => { const item = itemList.find((i) => i.id === id); if (item && isItemDisabled(item)) return; setSelectedItemIds((prev) => { const newSet = new Set(prev); if (newSet.has(id)) { newSet.delete(id); } else { newSet.add(id); } return newSet; }); }; // 저장 const handleSubmit = () => { if (selectedItemIds.size === 0) { toast.warning('품목을 최소 1개 이상 선택해주세요.'); return; } const finalConditionValue = Array.from(selectedItemIds).join(','); onAdd({ registrationType: 'individual', ruleType: '품목코드', matchingType: 'equals', conditionValue: finalConditionValue, priority: 10, description: undefined, isActive: true, }); // Reset setSearchKeyword(''); setSelectedItemType('all'); setSelectedItemIds(new Set()); setProcessFilter('all'); setCategoryFilter('all'); onOpenChange(false); }; return ( 공정 품목 선택
{/* 검색 입력 */}
setSearchKeyword(e.target.value)} placeholder="품목코드 또는 품목명으로 검색..." className="pl-9" />
{/* 카운트 + 필터 행 */}
{isItemsLoading ? ( '로딩 중...' ) : ( <> 총 {itemList.length}건{' '} {selectedItemIds.size > 0 && ( {selectedItemIds.size}건 선택됨 )} )}
{/* TODO: 백엔드 API에서 process_name, process_category 필터/응답 지원 후 구분 필터 활성화 */}
{/* 품목 테이블 */}
{itemList.length > 0 && ( !isItemDisabled(item)).length > 0 && itemList.filter((item) => !isItemDisabled(item)).every((item) => selectedItemIds.has(item.id)) } onCheckedChange={(checked) => { setSelectedItemIds((prev) => { const newSet = new Set(prev); const selectableItems = itemList.filter((item) => !isItemDisabled(item)); if (checked) { selectableItems.forEach((item) => newSet.add(item.id)); } else { selectableItems.forEach((item) => newSet.delete(item.id)); } return newSet; }); }} /> )} 품목유형 품목코드 품목명 배정 공정 {isItemsLoading ? ( 품목 목록을 불러오는 중... ) : itemList.length === 0 ? ( {searchKeyword.trim() === '' ? '품목을 검색해주세요 (한글 1자 이상, 영문 2자 이상)' : '검색 결과가 없습니다'} ) : ( itemList.map((item) => { const disabled = isItemDisabled(item); const assignedName = getAssignedProcessName(item); const isRegistered = isAlreadyRegistered(item); return ( !disabled && handleToggleItem(item.id)} > handleToggleItem(item.id)} onClick={(e) => e.stopPropagation()} disabled={disabled} /> {item.type} {item.code} {item.fullName} {isRegistered ? ( 현재 공정 ) : assignedName ? ( {assignedName} ) : ( '-' )} ); }) )}
{/* 안내 문구 */}

선택 후 저장하시면 선택한 품목들이{' '} {processName || '해당'} {' '} 공정으로 변경됩니다.

); }