fix(WEB): 개소 그룹핑 키를 order_node_id 기반으로 변경 + 검사 step 동적 처리
- WorkerScreen/actions.ts: 그룹핑 키를 floor_code/symbol_code → order_node_id 우선으로 변경
- WorkOrderDetail.tsx: 개소 그룹핑을 orderNodeId 기반으로 단순화
- WorkerScreen/index.tsx: 검사 step name 하드코딩('중간검사') 제거, 동적 step name 사용
- InspectionReportModal.tsx: inspectionDataMap 빈 Map 허용 수정
This commit is contained in:
@@ -514,35 +514,15 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) {
|
||||
// 개소(층/부호)별로 그룹화
|
||||
const nodeGroups = new Map<string, { label: string; items: typeof order.items }>();
|
||||
|
||||
// 모든 아이템이 동일 그룹으로 들어가는지 확인
|
||||
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[] = [];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<string, { nodeId: number | null; nodeName: string; items: typeof api.items }>();
|
||||
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: [] });
|
||||
}
|
||||
|
||||
@@ -403,6 +403,8 @@ export default function WorkerScreen() {
|
||||
// 문서 템플릿 데이터 (document_template 기반 동적 검사용)
|
||||
const [inspectionTemplateData, setInspectionTemplateData] = useState<InspectionTemplateData | undefined>();
|
||||
const [inspectionDimensions, setInspectionDimensions] = useState<{ width?: number; height?: number }>({});
|
||||
// 검사 클릭 시 해당 step name 추적 (하드코딩 '중간검사' 제거용)
|
||||
const [inspectionStepName, setInspectionStepName] = useState<string>('');
|
||||
|
||||
// 중간검사 체크 상태 관리: { [itemId]: boolean }
|
||||
const [inspectionCheckedMap, setInspectionCheckedMap] = useState<Record<string, boolean>>({});
|
||||
@@ -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<string, unknown>
|
||||
);
|
||||
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(() => {
|
||||
|
||||
Reference in New Issue
Block a user