diff --git a/src/components/production/WorkOrders/WorkOrderDetail.tsx b/src/components/production/WorkOrders/WorkOrderDetail.tsx index f1968bf1..501ba9f2 100644 --- a/src/components/production/WorkOrders/WorkOrderDetail.tsx +++ b/src/components/production/WorkOrders/WorkOrderDetail.tsx @@ -514,35 +514,15 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) { // 개소(층/부호)별로 그룹화 const nodeGroups = new Map(); - // 모든 아이템이 동일 그룹으로 들어가는지 확인 - const uniqueKeys = new Set(order.items.map(item => - item.floorCode !== '-' ? item.floorCode : String(item.orderNodeId ?? 'none') - )); - const allSameGroup = uniqueKeys.size <= 1; - const locationCount = order.shutterCount || 1; - - if (allSameGroup && locationCount > 1) { - // 인덱스 기반 균등 분배 - const itemsPerLoc = Math.ceil(order.items.length / locationCount); - for (let loc = 0; loc < locationCount; loc++) { - const start = loc * itemsPerLoc; - const end = Math.min(start + itemsPerLoc, order.items.length); - if (start >= order.items.length) break; - const key = `loc-${loc}`; - nodeGroups.set(key, { - label: `개소 ${loc + 1}`, - items: order.items.slice(start, end), - }); - } - } else { - for (const item of order.items) { - const key = item.floorCode !== '-' ? item.floorCode : (item.orderNodeId != null ? String(item.orderNodeId) : 'none'); - const label = item.floorCode !== '-' ? item.floorCode : item.orderNodeName; - if (!nodeGroups.has(key)) { - nodeGroups.set(key, { label, items: [] }); - } - nodeGroups.get(key)!.items.push(item); + // order_node_id 기반 그룹핑 (floor_code/symbol_code는 표시용) + for (const item of order.items) { + const floorLabel = item.floorCode !== '-' ? item.floorCode : ''; + const key = item.orderNodeId != null ? String(item.orderNodeId) : (floorLabel || 'none'); + const label = floorLabel || item.orderNodeName || `개소 ${nodeGroups.size + 1}`; + if (!nodeGroups.has(key)) { + nodeGroups.set(key, { label, items: [] }); } + nodeGroups.get(key)!.items.push(item); } const rows: React.ReactNode[] = []; diff --git a/src/components/production/WorkOrders/documents/InspectionReportModal.tsx b/src/components/production/WorkOrders/documents/InspectionReportModal.tsx index 904c86c5..f2a66686 100644 --- a/src/components/production/WorkOrders/documents/InspectionReportModal.tsx +++ b/src/components/production/WorkOrders/documents/InspectionReportModal.tsx @@ -140,7 +140,7 @@ export function InspectionReportModal({ // API: key = "report-item-${itemId}" (예: "report-item-42") const propFiltered = propWorkItems?.filter(w => !w.id.startsWith('mock-')); const propHasWorkItems = propFiltered && propFiltered.length > 0; - const propHasInspectionData = propInspectionDataMap && propInspectionDataMap.size > 0; + const propHasInspectionData = propInspectionDataMap != null; const usePropsSource = propHasWorkItems && propHasInspectionData; const effectiveWorkItems = usePropsSource ? propFiltered : apiWorkItems ?? undefined; const effectiveInspectionDataMap = usePropsSource ? propInspectionDataMap : apiInspectionDataMap ?? undefined; diff --git a/src/components/production/WorkerScreen/actions.ts b/src/components/production/WorkerScreen/actions.ts index 07e3e6a9..5d48fce8 100644 --- a/src/components/production/WorkerScreen/actions.ts +++ b/src/components/production/WorkerScreen/actions.ts @@ -133,15 +133,15 @@ function transformToWorkerScreenFormat(api: WorkOrderApiItem): WorkOrder { processInfo = { code: rawProcessCode || 'unknown', name: rawProcessName || '알수없음' }; } - // 아이템을 개소(floor_code/symbol_code)별로 그룹핑 + // 아이템을 개소(order_node_id)별로 그룹핑 (floor_code/symbol_code는 표시용) const nodeMap = new Map(); for (const item of (api.items || [])) { const nodeId = item.source_order_item?.order_node_id ?? null; const floorCode = item.source_order_item?.floor_code; const symbolCode = item.source_order_item?.symbol_code; - const floorLabel = [floorCode, symbolCode].filter(Boolean).join('/'); + const floorLabel = [floorCode, symbolCode].filter(v => v && v !== '-').join('/'); const nodeName = floorLabel || item.source_order_item?.node?.name || '미지정'; - const key = floorLabel || (nodeId != null ? String(nodeId) : `unassigned-${item.id}`); + const key = nodeId != null ? String(nodeId) : (floorLabel || `unassigned-${item.id}`); if (!nodeMap.has(key)) { nodeMap.set(key, { nodeId, nodeName, items: [] }); } diff --git a/src/components/production/WorkerScreen/index.tsx b/src/components/production/WorkerScreen/index.tsx index b3efe076..1856d502 100644 --- a/src/components/production/WorkerScreen/index.tsx +++ b/src/components/production/WorkerScreen/index.tsx @@ -403,6 +403,8 @@ export default function WorkerScreen() { // 문서 템플릿 데이터 (document_template 기반 동적 검사용) const [inspectionTemplateData, setInspectionTemplateData] = useState(); const [inspectionDimensions, setInspectionDimensions] = useState<{ width?: number; height?: number }>({}); + // 검사 클릭 시 해당 step name 추적 (하드코딩 '중간검사' 제거용) + const [inspectionStepName, setInspectionStepName] = useState(''); // 중간검사 체크 상태 관리: { [itemId]: boolean } const [inspectionCheckedMap, setInspectionCheckedMap] = useState>({}); @@ -692,7 +694,7 @@ export default function WorkerScreen() { workOrderId: selectedOrder.id, itemNo: index + 1, itemCode: selectedOrder.orderNo || '-', - itemName: `${group.nodeName} : ${itemSummary}`, + itemName: itemSummary, floor: (opts.floor as string) || '-', code: (opts.code as string) || '-', width: (opts.width as number) || 0, @@ -811,9 +813,12 @@ export default function WorkerScreen() { const match = workItems.find((w) => w.apiItemId === apiItem.item_id); if (match) { next.set(match.id, apiItem.inspection_data as unknown as InspectionData); - // 중간검사 step 완료 처리 - const stepKey = match.id.replace('-node-', '-') + '-중간검사'; - completionUpdates[stepKey] = true; + // 검사 step 완료 처리 (실제 step name 사용) + const inspStep = match.steps.find((s) => s.isInspection || s.needsInspection); + if (inspStep) { + const stepKey = `${match.id.replace('-node-', '-')}-${inspStep.name}`; + completionUpdates[stepKey] = true; + } } } return next; @@ -901,10 +906,16 @@ export default function WorkerScreen() { // ===== 핸들러 ===== // 중간검사 버튼 클릭 핸들러 - 템플릿 로드 후 모달 열기 - const handleInspectionClick = useCallback(async (itemId: string) => { + const handleInspectionClick = useCallback(async (itemId: string, stepName?: string) => { // 해당 아이템 찾기 const item = workItems.find((w) => w.id === itemId); if (item) { + // 클릭된 검사 step name 저장 (stepName 미전달 시 item.steps에서 검사 step 탐색) + const resolvedStepName = stepName + || item.steps.find((s) => s.isInspection || s.needsInspection)?.name + || ''; + setInspectionStepName(resolvedStepName); + // 합성 WorkOrder 생성 const syntheticOrder: WorkOrder = { id: item.id, @@ -988,10 +999,10 @@ export default function WorkerScreen() { } } else if (step.connectionType === '팝업' && step.connectionTarget === '중간검사') { // 연결정보: 팝업 + 중간검사 → 중간검사 모달 열기 - handleInspectionClick(itemId); + handleInspectionClick(itemId, step.name); } else if (step.needsInspection || step.isInspection) { // 검사 단계 (processListCache 설정 또는 하드코딩 폴백) → 중간검사 모달 열기 - handleInspectionClick(itemId); + handleInspectionClick(itemId, step.name); } else if (step.completionType === 'click_complete' && step.stepProgressId) { // 클릭 시 완료 → 서버 토글 API 호출 const workItem = workItems.find((item) => item.id === itemId); @@ -1256,10 +1267,14 @@ export default function WorkerScreen() { } }, [getTargetOrder]); - // 중간검사 완료 핸들러 (API 저장 + 메모리 업데이트 + 공정 단계 완료 처리) + // 검사 완료 핸들러 (API 저장 + 메모리 업데이트 + 공정 단계 완료 처리) const handleInspectionComplete = useCallback(async (data: InspectionData) => { if (!selectedOrder) return; + // stepCompletionMap 키 생성 헬퍼 (enrichStep 형식 일치: -node- 제거 + 실제 step name) + const buildStepKey = (stepName: string) => + `${selectedOrder.id.replace('-node-', '-')}-${stepName}`; + // 메모리에 즉시 반영 setInspectionDataMap((prev) => { const next = new Map(prev); @@ -1279,7 +1294,7 @@ export default function WorkerScreen() { data as unknown as Record ); if (result.success) { - toast.success('중간검사가 저장되었습니다.'); + toast.success('검사가 저장되었습니다.'); } else { toast.error(result.error || '검사 데이터 저장에 실패했습니다.'); } @@ -1291,39 +1306,38 @@ export default function WorkerScreen() { // Document 동기화 실패는 무시 (template 미연결 시 404 가능) } - // 3. completionType='inspection_complete'인 단계 자동 완료 처리 - const inspectionStep = targetItem.steps.find( - (s) => (s.completionType === 'inspection_complete') || s.needsInspection || s.isInspection - ); + // 3. 검사 단계 자동 완료 처리 (inspectionStepName 우선, 없으면 steps에서 탐색) + const stepName = inspectionStepName + || targetItem.steps.find((s) => (s.completionType === 'inspection_complete') || s.needsInspection || s.isInspection)?.name + || ''; + const inspectionStep = stepName + ? targetItem.steps.find((s) => s.name === stepName) + : undefined; + if (inspectionStep?.stepProgressId) { // 서버에 단계 완료 토글 try { const toggleResult = await toggleStepProgress(targetItem.workOrderId, inspectionStep.stepProgressId); if (toggleResult.success) { - // 로컬 상태도 동기화 - const stepKey = `${selectedOrder.id}-${inspectionStep.name}`; - setStepCompletionMap((prev) => ({ ...prev, [stepKey]: true })); + setStepCompletionMap((prev) => ({ ...prev, [buildStepKey(stepName)]: true })); } } catch { // 단계 완료 실패 시 로컬만 업데이트 - const stepKey = `${selectedOrder.id}-${inspectionStep.name}`; - setStepCompletionMap((prev) => ({ ...prev, [stepKey]: true })); + setStepCompletionMap((prev) => ({ ...prev, [buildStepKey(stepName)]: true })); } - } else { - // stepProgressId 없으면 로컬만 완료 처리 (목업 호환) - const stepKey = selectedOrder.id.replace('-node-', '-') + '-중간검사'; - setStepCompletionMap((prev) => ({ ...prev, [stepKey]: true })); + } else if (stepName) { + // stepProgressId 없으면 로컬만 완료 처리 + setStepCompletionMap((prev) => ({ ...prev, [buildStepKey(stepName)]: true })); } } catch { toast.error('검사 데이터 저장 중 오류가 발생했습니다.'); } - } else { + } else if (inspectionStepName) { // 목업 데이터는 메모리만 저장 + 로컬 완료 처리 - const stepKey = selectedOrder.id.replace('-node-', '-') + '-중간검사'; - setStepCompletionMap((prev) => ({ ...prev, [stepKey]: true })); + setStepCompletionMap((prev) => ({ ...prev, [buildStepKey(inspectionStepName)]: true })); toast.success('중간검사가 완료되었습니다.'); } - }, [selectedOrder, workItems, getInspectionProcessType]); + }, [selectedOrder, workItems, getInspectionProcessType, inspectionStepName]); // ===== 재공품 감지 ===== const hasWipItems = useMemo(() => {