diff --git a/src/components/process-management/actions.ts b/src/components/process-management/actions.ts index 3aae331d..5dbf2971 100644 --- a/src/components/process-management/actions.ts +++ b/src/components/process-management/actions.ts @@ -103,6 +103,7 @@ function transformApiToFrontend(apiData: ApiProcess): Process { workLogTemplateName: apiData.work_log_template_relation?.name ?? undefined, needsInspection: apiData.options?.needs_inspection ?? false, needsWorkLog: apiData.options?.needs_work_log ?? false, + processGroup: apiData.options?.process_group ?? undefined, classificationRules: [...patternRules, ...individualRules], requiredWorkers: apiData.required_workers, equipmentInfo: apiData.equipment_info ?? undefined, diff --git a/src/components/production/WorkerScreen/index.tsx b/src/components/production/WorkerScreen/index.tsx index 28be4f41..f1e311e5 100644 --- a/src/components/production/WorkerScreen/index.tsx +++ b/src/components/production/WorkerScreen/index.tsx @@ -284,20 +284,50 @@ export default function WorkerScreen() { // 공정 목록 캐시 const [processListCache, setProcessListCache] = useState([]); - // 활성 공정 목록 (탭용) - 공정관리에서 등록된 활성 공정만 - const processTabs = useMemo(() => { + // 활성 공정 목록 + const activeProcesses = useMemo(() => { return processListCache.filter((p) => p.status === '사용중'); }, [processListCache]); - // 공정 목록 로드 후 첫 번째 공정을 기본 선택 (폴백: 'screen') + // 그룹별 탭 구성 (process_group 기준, 없으면 공정명 사용) + const groupedTabs = useMemo(() => { + const groupMap = new Map(); + activeProcesses.forEach((p) => { + const group = p.processGroup || p.processName; + if (!groupMap.has(group)) groupMap.set(group, []); + groupMap.get(group)!.push(p); + }); + return Array.from(groupMap.entries()).map(([group, processes]) => ({ + group, + processes, + // 그룹 내 첫 번째 공정 ID를 탭 value로 사용 + defaultProcessId: processes[0].id, + })); + }, [activeProcesses]); + + // processTabs 호환 (기존 코드에서 참조) + const processTabs = activeProcesses; + + // 선택된 그룹 내 하위 공정 필터 + const [subProcessId, setSubProcessId] = useState('all'); + + // 현재 탭의 그룹 정보 + const activeGroup = useMemo(() => { + const process = processListCache.find((p) => p.id === activeTab); + if (!process) return null; + const group = process.processGroup || process.processName; + return groupedTabs.find((g) => g.group === group) || null; + }, [activeTab, processListCache, groupedTabs]); + + // 공정 목록 로드 후 첫 번째 그룹을 기본 선택 useEffect(() => { if (activeTab) return; - if (processTabs.length > 0) { - setActiveTab(processTabs[0].id); + if (groupedTabs.length > 0) { + setActiveTab(groupedTabs[0].defaultProcessId); } else if (!isLoading) { setActiveTab('screen'); } - }, [processTabs, activeTab, isLoading]); + }, [groupedTabs, activeTab, isLoading]); // 선택된 공정의 ProcessTab 키 (mock 데이터 및 기존 로직 호환용) const activeProcessTabKey: ProcessTab = useMemo(() => { @@ -358,17 +388,40 @@ export default function WorkerScreen() { }, [selectedSidebarOrderId]); - // ===== 탭별 필터링된 작업 ===== + // ===== 탭별 필터링된 작업 (그룹 + 하위 공정 필터) ===== const filteredWorkOrders = useMemo(() => { + // 하위 공정이 특정되면 해당 공정만 필터 + if (subProcessId !== 'all') { + const subProcess = processListCache.find((p) => p.id === subProcessId); + if (subProcess) { + const subName = subProcess.processName.toLowerCase(); + return workOrders.filter((order) => { + const orderProcessName = (order.processName || '').toLowerCase(); + return orderProcessName.includes(subName) || subName.includes(orderProcessName); + }); + } + } + + // 그룹 내 모든 공정 매칭 + if (activeGroup) { + const groupProcessNames = activeGroup.processes.map((p) => p.processName.toLowerCase()); + return workOrders.filter((order) => { + const orderProcessName = (order.processName || '').toLowerCase(); + return groupProcessNames.some((gpn) => + orderProcessName.includes(gpn) || gpn.includes(orderProcessName) + ); + }); + } + + // 폴백: 기존 방식 const selectedProcess = processListCache.find((p) => p.id === activeTab); if (!selectedProcess) return workOrders; - const selectedName = selectedProcess.processName.toLowerCase(); return workOrders.filter((order) => { const orderProcessName = (order.processName || '').toLowerCase(); return orderProcessName.includes(selectedName) || selectedName.includes(orderProcessName); }); - }, [workOrders, activeTab, processListCache]); + }, [workOrders, activeTab, activeGroup, subProcessId, processListCache]); // ===== API WorkOrders → SidebarOrder 변환 ===== const apiSidebarOrders: SidebarOrder[] = useMemo(() => { @@ -1313,10 +1366,15 @@ export default function WorkerScreen() { >
- {processTabs.length > 0 ? ( - processTabs.map((proc) => ( - - {proc.processName} + {groupedTabs.length > 0 ? ( + groupedTabs.map((g) => ( + { setSubProcessId('all'); }} + > + {g.group} )) ) : ( @@ -1329,8 +1387,37 @@ export default function WorkerScreen() {
- {(processTabs.length > 0 - ? processTabs.map((p) => p.id) + {/* 그룹 내 하위 공정 필터 (2개 이상일 때만 표시) */} + {activeGroup && activeGroup.processes.length > 1 && ( +
+ + {activeGroup.processes.map((p) => ( + + ))} +
+ )} + + {(groupedTabs.length > 0 + ? groupedTabs.map((g) => g.defaultProcessId) : ['screen', 'slat', 'bending'] ).map((tabValue) => ( diff --git a/src/types/process.ts b/src/types/process.ts index e342d4c1..1cd825e8 100644 --- a/src/types/process.ts +++ b/src/types/process.ts @@ -92,6 +92,9 @@ export interface Process { // 단계 목록 steps?: ProcessStep[]; + // 공정 그룹 (같은 그룹은 하나의 탭으로 표시) + processGroup?: string; + // 상태 status: ProcessStatus;