diff --git a/src/components/process-management/RuleModal.tsx b/src/components/process-management/RuleModal.tsx index 9a43793c..1cacbe48 100644 --- a/src/components/process-management/RuleModal.tsx +++ b/src/components/process-management/RuleModal.tsx @@ -100,13 +100,9 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule, processId, proc size: 1000, excludeProcessId: processId, }); - // 이미 등록된 품목 필터링 - const filtered = registeredItemIds && registeredItemIds.size > 0 - ? items.filter((item) => !registeredItemIds.has(item.id)) - : items; - setItemList(filtered); + setItemList(items); setIsItemsLoading(false); - }, [processId, registeredItemIds]); + }, [processId]); // 검색어 유효성 검사 함수 const isValidSearchKeyword = (keyword: string): boolean => { @@ -164,8 +160,38 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule, processId, proc 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)) { @@ -285,50 +311,58 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule, processId, proc 품목유형 품목코드 품목명 - {/* TODO: 백엔드 API에서 process_name, process_category 응답 지원 후 공정/구분 컬럼 활성화 - 공정 - 구분 - */} + 배정 공정 {isItemsLoading ? ( - + 품목 목록을 불러오는 중... ) : itemList.length === 0 ? ( - + {searchKeyword.trim() === '' ? '품목을 검색해주세요 (한글 1자 이상, 영문 2자 이상)' : '검색 결과가 없습니다'} ) : ( - itemList.map((item) => ( - handleToggleItem(item.id)} - > - - handleToggleItem(item.id)} - onClick={(e) => e.stopPropagation()} - /> - - {item.type} - {item.code} - {item.fullName} - {/* TODO: 백엔드 API 지원 후 item.processName / item.processCategory 표시 - {item.processName || '-'} - {item.processCategory || '-'} - */} - - )) + 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} + ) : ( + '-' + )} + + + ); + }) )} diff --git a/src/components/process-management/actions.ts b/src/components/process-management/actions.ts index b6f5e314..cc736015 100644 --- a/src/components/process-management/actions.ts +++ b/src/components/process-management/actions.ts @@ -483,29 +483,25 @@ export interface ItemOption { id: string; fullName: string; type: string; - // TODO: API 응답에 process_name, process_category 필드 추가 후 활성화 - processName?: string; - processCategory?: string; + /** 배정된 공정 목록 */ + assignedProcesses?: Array<{ processId: number; processName: string }>; } interface GetItemListParams { q?: string; itemType?: string; size?: number; - /** 해당 공정 외 다른 공정에 이미 배정된 품목 제외 (공정 ID) */ + /** 공정 배정 정보 포함을 위한 현재 공정 ID */ excludeProcessId?: string; } /** * 품목 목록 조회 (분류 규칙용) - * - excludeProcessId: 다른 공정에 이미 배정된 품목 제외 (중복 방지) - * - * TODO: 백엔드 API 수정 요청 - * - 응답에 process_name, process_category 필드 추가 필요 (공정 품목 선택 팝업에서 공정/구분 컬럼 표시용) - * - 파라미터에 processName, processCategory 필터 추가 필요 (공정/구분 필터링용) + * - excludeProcessId: 공정 배정 정보를 포함하여 응답 (다른 공정 배정 품목은 disabled 처리용) */ export async function getItemList(params?: GetItemListParams): Promise { - interface ItemListResponse { data: Array<{ id: number; name: string; item_code?: string; item_type?: string; item_type_name?: string }> } + interface AssignedProcess { process_id: number; process_name: string } + interface ItemListResponse { data: Array<{ id: number; name: string; item_code?: string; item_type?: string; item_type_name?: string; assigned_processes?: AssignedProcess[] }> } const result = await executeServerAction({ url: buildApiUrl('/api/v1/items', { size: params?.size || 1000, @@ -523,6 +519,10 @@ export async function getItemList(params?: GetItemListParams): Promise ({ + processId: ap.process_id, + processName: ap.process_name, + })), })); }