'use client'; import { useState, useEffect, useCallback } from 'react'; import { getItemList, 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 { Label } from '@/components/ui/label'; import { QuantityInput } from '@/components/ui/quantity-input'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { Switch } from '@/components/ui/switch'; 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, Package } from 'lucide-react'; import type { ClassificationRule, RuleRegistrationType, RuleType, MatchingType, } from '@/types/process'; import { RULE_TYPE_OPTIONS, MATCHING_TYPE_OPTIONS } from '@/types/process'; // 품목 유형 옵션 const ITEM_TYPE_OPTIONS = [ { value: 'all', label: '전체' }, { value: '제품', label: '제품' }, { value: '반제품', label: '반제품' }, { value: '원자재', label: '원자재' }, { value: '부자재', label: '부자재' }, ]; interface RuleModalProps { open: boolean; onOpenChange: (open: boolean) => void; onAdd: (rule: Omit) => void; editRule?: ClassificationRule; } export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProps) { // 공통 상태 const [registrationType, setRegistrationType] = useState( editRule?.registrationType || 'pattern' ); const [description, setDescription] = useState(editRule?.description || ''); // 패턴 규칙용 상태 const [ruleType, setRuleType] = useState(editRule?.ruleType || '품목코드'); const [matchingType, setMatchingType] = useState( editRule?.matchingType || 'startsWith' ); const [conditionValue, setConditionValue] = useState(editRule?.conditionValue || ''); const [priority, setPriority] = useState(editRule?.priority || 10); const [isActive, setIsActive] = useState(editRule?.isActive ?? true); // 개별 품목용 상태 const [searchKeyword, setSearchKeyword] = useState(''); const [selectedItemType, setSelectedItemType] = useState('all'); const [selectedItemIds, setSelectedItemIds] = useState>(new Set()); // 품목 목록 API 상태 const [itemList, setItemList] = useState([]); const [isItemsLoading, setIsItemsLoading] = useState(false); // 품목 목록 로드 (debounced) const loadItems = useCallback(async (q?: string, itemType?: string) => { setIsItemsLoading(true); const items = await getItemList({ q: q || undefined, itemType: itemType === 'all' ? undefined : itemType, size: 1000, // 전체 품목 조회 }); setItemList(items); setIsItemsLoading(false); }, []); // 검색어 유효성 검사 함수 const isValidSearchKeyword = (keyword: string): boolean => { if (!keyword || keyword.trim() === '') return false; const trimmed = keyword.trim(); // 한글이 포함되어 있으면 1자 이상 const hasKorean = /[가-힣]/.test(trimmed); if (hasKorean) return trimmed.length >= 1; // 영어/숫자만 있으면 2자 이상 return trimmed.length >= 2; }; // 검색어/품목유형 변경 시 API 호출 (debounce) useEffect(() => { if (registrationType !== 'individual') return; // 검색어 유효성 검사 - 유효하지 않으면 빈 목록 if (!isValidSearchKeyword(searchKeyword)) { setItemList([]); return; } const timer = setTimeout(() => { loadItems(searchKeyword, selectedItemType); }, 300); return () => clearTimeout(timer); }, [searchKeyword, selectedItemType, registrationType, loadItems]); // 품목유형 변경 시 검색어가 유효하면 재검색 useEffect(() => { if (registrationType !== 'individual') return; if (!isValidSearchKeyword(searchKeyword)) return; loadItems(searchKeyword, selectedItemType); }, [selectedItemType]); // 모달 열릴 때 품목 목록 초기화 (초기 로드 안함) useEffect(() => { if (open && registrationType === 'individual') { setItemList([]); setSearchKeyword(''); } }, [open, registrationType]); // 체크박스 토글 const handleToggleItem = (id: string) => { setSelectedItemIds((prev) => { const newSet = new Set(prev); if (newSet.has(id)) { newSet.delete(id); } else { newSet.add(id); } return newSet; }); }; // 전체 선택 const handleSelectAll = () => { const allIds = itemList.map((item) => item.id); setSelectedItemIds(new Set(allIds)); }; // 초기화 const handleResetSelection = () => { setSelectedItemIds(new Set()); }; // 모달 열릴 때 초기화 또는 수정 데이터 로드 useEffect(() => { if (open) { if (editRule) { // 수정 모드: 기존 데이터 로드 setRegistrationType(editRule.registrationType); setDescription(editRule.description || ''); setRuleType(editRule.ruleType); setMatchingType(editRule.matchingType); setConditionValue(editRule.conditionValue); setPriority(editRule.priority); setIsActive(editRule.isActive); setSearchKeyword(''); setSelectedItemType('all'); // 개별 품목인 경우 선택된 품목 ID 설정 if (editRule.registrationType === 'individual') { const ids = editRule.conditionValue.split(',').filter(Boolean); setSelectedItemIds(new Set(ids)); } else { setSelectedItemIds(new Set()); } } else { // 추가 모드: 초기화 setRegistrationType('pattern'); setDescription(''); setRuleType('품목코드'); setMatchingType('startsWith'); setConditionValue(''); setPriority(10); setIsActive(true); setSearchKeyword(''); setSelectedItemType('all'); setSelectedItemIds(new Set()); } } }, [open, editRule]); const handleSubmit = () => { if (registrationType === 'pattern') { if (!conditionValue.trim()) { alert('조건 값을 입력해주세요.'); return; } } else { if (selectedItemIds.size === 0) { alert('품목을 최소 1개 이상 선택해주세요.'); return; } } // 개별 품목의 경우 conditionValue에 품목코드들을 저장 const finalConditionValue = registrationType === 'individual' ? Array.from(selectedItemIds).join(',') : conditionValue.trim(); onAdd({ registrationType, ruleType: registrationType === 'individual' ? '품목코드' : ruleType, matchingType: registrationType === 'individual' ? 'equals' : matchingType, conditionValue: finalConditionValue, priority: registrationType === 'individual' ? 10 : priority, description: description.trim() || undefined, isActive: registrationType === 'individual' ? true : isActive, }); // Reset form setRegistrationType('pattern'); setDescription(''); setRuleType('품목코드'); setMatchingType('startsWith'); setConditionValue(''); setPriority(10); setIsActive(true); setSearchKeyword(''); setSelectedItemType('all'); setSelectedItemIds(new Set()); onOpenChange(false); }; return ( {editRule ? '규칙 수정' : '규칙 추가'}
{/* 등록 방식 */}
setRegistrationType(v as RuleRegistrationType)} >
{/* 패턴 규칙 UI */} {registrationType === 'pattern' && ( <> {/* 규칙 유형 */}
{/* 매칭 방식 */}
{/* 조건 값 */}
setConditionValue(e.target.value)} placeholder="예: SCR-, E-, STEEL-" className="flex-1" />

Enter 키를 누르거나 검색 버튼을 클릭하세요

{/* 우선순위 - 패턴 규칙에서만 표시 */}
setPriority(value ?? 1)} min={1} max={100} className="w-24" />

낮을수록 먼저 적용됩니다

{/* 설명 - 패턴 규칙 */}
setDescription(e.target.value)} placeholder="규칙에 대한 설명" />
{/* 활성 상태 - 패턴 규칙에서만 표시 */}
)} {/* 개별 품목 UI - 기획서 기준 */} {registrationType === 'individual' && ( <> {/* 설명 (선택) */}
setDescription(e.target.value)} placeholder="이 품목 그룹에 대한 설명" />
{/* 품목 검색 + 품목 유형 필터 */}
setSearchKeyword(e.target.value)} placeholder="품목코드 또는 품목명으로 검색..." className="pl-9" />
{/* 품목 목록 헤더 */}
{isItemsLoading ? ( '로딩 중...' ) : ( <>품목 목록 ({itemList.length}개) | 선택됨 ({selectedItemIds.size}개) )}
|
{/* 품목 테이블 */}
품목코드 품목명 품목유형 {isItemsLoading ? ( 품목 목록을 불러오는 중... ) : itemList.length === 0 ? ( {searchKeyword.trim() === '' ? '품목을 검색해주세요 (한글 1자 이상, 영문 2자 이상)' : '검색 결과가 없습니다'} ) : ( itemList.map((item) => ( handleToggleItem(item.id)} > handleToggleItem(item.id)} onClick={(e) => e.stopPropagation()} /> {item.code} {item.fullName} {item.type} )) )}
{/* 안내 문구 */}

이 공정에 배정할 품목을 선택하세요. 다른 공정에 이미 배정된 품목은 표시되지 않습니다.

)}
); }