'use client'; /** * 중간검사 입력 모달 * * 공정별로 다른 검사 항목 표시: * - screen: 스크린 중간검사 * - slat: 슬랫 중간검사 * - slat_jointbar: 조인트바 중간검사 * - bending: 절곡 중간검사 * - bending_wip: 재고생산(재공품) 중간검사 */ import { useState, useEffect, useMemo, useRef } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { cn } from '@/lib/utils'; import type { InspectionTemplateData, InspectionTemplateSectionItem } from './types'; import { formatNumber } from '@/lib/utils/amount'; import { getInspectionConfig } from '@/components/production/WorkOrders/actions'; import type { InspectionConfigData } from '@/components/production/WorkOrders/actions'; // 중간검사 공정 타입 export type InspectionProcessType = | 'screen' | 'slat' | 'slat_jointbar' | 'bending' | 'bending_wip'; // 검사 결과 데이터 타입 export interface InspectionData { productName: string; specification: string; // 겉모양 상태 bendingStatus?: 'good' | 'bad' | null; // 절곡상태 processingStatus?: 'good' | 'bad' | null; // 가공상태 sewingStatus?: 'good' | 'bad' | null; // 재봉상태 assemblyStatus?: 'good' | 'bad' | null; // 조립상태 // 치수 length?: number | null; width?: number | null; height1?: number | null; height2?: number | null; length3?: number | null; gap4?: number | null; gapStatus?: 'ok' | 'ng' | null; // 간격 포인트들 (절곡용) gapPoints?: { left: number | null; right: number | null }[]; // 판정 judgment: 'pass' | 'fail' | null; nonConformingContent: string; // 동적 폼 값 (템플릿 기반 검사 시) templateValues?: Record; } interface InspectionInputModalProps { open: boolean; onOpenChange: (open: boolean) => void; processType: InspectionProcessType; productName?: string; specification?: string; initialData?: InspectionData; onComplete: (data: InspectionData) => void; /** 문서 템플릿 데이터 (있으면 동적 폼 모드) */ templateData?: InspectionTemplateData; /** 작업 아이템의 실제 치수 (reference_attribute 연동용) */ workItemDimensions?: { width?: number; height?: number }; /** 작업지시 ID (절곡 gap_points API 조회용) */ workOrderId?: string; } // ===== 절곡 7개 제품 검사 항목 (BendingInspectionContent의 INITIAL_PRODUCTS와 동일 구조) ===== interface BendingGapPointDef { point: string; design: string; } interface BendingProductDef { id: string; label: string; lengthDesign: string; widthDesign: string; gapPoints: BendingGapPointDef[]; } const BENDING_PRODUCTS: BendingProductDef[] = [ { id: 'guide-rail-wall', label: '가이드레일 (벽면형)', lengthDesign: '3000', widthDesign: 'N/A', gapPoints: [ { point: '①', design: '30' }, { point: '②', design: '80' }, { point: '③', design: '45' }, { point: '④', design: '40' }, { point: '⑤', design: '34' }, ], }, { id: 'guide-rail-side', label: '가이드레일 (측면형)', lengthDesign: '3000', widthDesign: 'N/A', gapPoints: [ { point: '①', design: '28' }, { point: '②', design: '75' }, { point: '③', design: '42' }, { point: '④', design: '38' }, { point: '⑤', design: '32' }, ], }, { id: 'case', label: '케이스 (500X380)', lengthDesign: '3000', widthDesign: 'N/A', gapPoints: [ { point: '①', design: '380' }, { point: '②', design: '50' }, { point: '③', design: '240' }, { point: '④', design: '50' }, ], }, { id: 'bottom-finish', label: '하단마감재 (60X40)', lengthDesign: '3000', widthDesign: 'N/A', gapPoints: [ { point: '②', design: '60' }, { point: '②', design: '64' }, ], }, { id: 'bottom-l-bar', label: '하단L-BAR (17X60)', lengthDesign: '3000', widthDesign: 'N/A', gapPoints: [ { point: '①', design: '17' }, ], }, { id: 'smoke-w50', label: '연기차단재 (W50)', lengthDesign: '3000', widthDesign: '', gapPoints: [ { point: '①', design: '50' }, { point: '②', design: '12' }, ], }, { id: 'smoke-w80', label: '연기차단재 (W80)', lengthDesign: '3000', widthDesign: '', gapPoints: [ { point: '①', design: '80' }, { point: '②', design: '12' }, ], }, ]; interface BendingProductState { id: string; bendingStatus: 'good' | 'bad' | null; lengthMeasured: string; widthMeasured: string; gapMeasured: string[]; } function createInitialBendingProducts(): BendingProductState[] { return BENDING_PRODUCTS.map(p => ({ id: p.id, bendingStatus: null, lengthMeasured: '', widthMeasured: '', gapMeasured: p.gapPoints.map(() => ''), })); } const PROCESS_TITLES: Record = { screen: '# 스크린 중간검사', slat: '# 슬랫 중간검사', slat_jointbar: '# 조인트바 중간검사', bending: '# 절곡 중간검사', bending_wip: '# 재고생산 중간검사', }; // 양호/불량 버튼 컴포넌트 function StatusToggle({ value, onChange, }: { value: 'good' | 'bad' | null; onChange: (v: 'good' | 'bad') => void; }) { return (
); } // OK/NG 버튼 컴포넌트 function OkNgToggle({ value, onChange, }: { value: 'ok' | 'ng' | null; onChange: (v: 'ok' | 'ng') => void; }) { return (
); } // 자동 판정 표시 컴포넌트 function JudgmentDisplay({ value }: { value: 'pass' | 'fail' | null }) { return (
적합
부적합
); } // 공정별 자동 판정 계산 function hasMeasurement(v: number | null | undefined): boolean { return v != null; } function computeJudgment(processType: InspectionProcessType, data: InspectionData): 'pass' | 'fail' | null { switch (processType) { case 'screen': { const { processingStatus, sewingStatus, assemblyStatus, gapStatus, length, width } = data; // 불량이 하나라도 있으면 즉시 부적합 if (processingStatus === 'bad' || sewingStatus === 'bad' || assemblyStatus === 'bad' || gapStatus === 'ng') return 'fail'; // 모든 상태 양호 + 측정값 입력 완료 시 적합 if (processingStatus === 'good' && sewingStatus === 'good' && assemblyStatus === 'good' && gapStatus === 'ok' && hasMeasurement(length) && hasMeasurement(width)) return 'pass'; return null; } case 'slat': { const { processingStatus, assemblyStatus, height1, height2, length } = data; if (processingStatus === 'bad' || assemblyStatus === 'bad') return 'fail'; if (processingStatus === 'good' && assemblyStatus === 'good' && hasMeasurement(height1) && hasMeasurement(height2) && hasMeasurement(length)) return 'pass'; return null; } case 'slat_jointbar': { const { processingStatus, assemblyStatus, height1, height2, length3, gap4 } = data; if (processingStatus === 'bad' || assemblyStatus === 'bad') return 'fail'; if (processingStatus === 'good' && assemblyStatus === 'good' && hasMeasurement(height1) && hasMeasurement(height2) && hasMeasurement(length3) && hasMeasurement(gap4)) return 'pass'; return null; } case 'bending': { const { bendingStatus, length } = data; if (bendingStatus === 'bad') return 'fail'; if (bendingStatus === 'good' && hasMeasurement(length)) return 'pass'; return null; } case 'bending_wip': { const { bendingStatus, length, width } = data; if (bendingStatus === 'bad') return 'fail'; if (bendingStatus === 'good' && hasMeasurement(length) && hasMeasurement(width)) return 'pass'; return null; } default: return null; } } // ===== Tolerance 기반 판정 유틸 ===== type ToleranceConfig = NonNullable['sections'][number]['items'][number]['tolerance']>; function evaluateTolerance(measured: number, design: number, tolerance: ToleranceConfig): 'pass' | 'fail' { switch (tolerance.type) { case 'symmetric': return Math.abs(measured - design) <= (tolerance.value ?? 0) ? 'pass' : 'fail'; case 'asymmetric': return (measured >= design - (tolerance.minus ?? 0) && measured <= design + (tolerance.plus ?? 0)) ? 'pass' : 'fail'; case 'range': return (measured >= (tolerance.min ?? -Infinity) && measured <= (tolerance.max ?? Infinity)) ? 'pass' : 'fail'; default: return 'pass'; } } function formatToleranceLabel(tolerance: ToleranceConfig): string { switch (tolerance.type) { case 'symmetric': return `± ${tolerance.value}`; case 'asymmetric': return `+${tolerance.plus} / -${tolerance.minus}`; case 'range': return `${tolerance.min} ~ ${tolerance.max}`; default: return ''; } } /** reference_attribute에서 치수 resolve */ function resolveRefValue( fieldValues: Record | null, dimensions?: { width?: number; height?: number } ): number | null { if (!fieldValues || !dimensions) return null; const refAttr = fieldValues.reference_attribute; if (typeof refAttr !== 'string') return null; const mapping: Record = { width: dimensions.width, height: dimensions.height, length: dimensions.width, // 스크린 '길이' = 폭(width) }; return mapping[refAttr] ?? null; } function formatDimension(val: number | undefined): string { if (val === undefined || val === null) return '-'; return formatNumber(val); } // ===== 항목별 입력 유형 판별 ===== // measurement_type이 'numeric' 또는 'measurement'이면 측정치수 입력, 나머지는 OK/NG 체크 function isNumericItem(item: InspectionTemplateSectionItem): boolean { const mt = item.measurement_type?.toLowerCase(); return mt === 'numeric' || mt === 'measurement'; } // 기준치수 resolve (standard_criteria → reference_attribute fallback) function resolveDesignValue( item: InspectionTemplateSectionItem, workItemDimensions?: { width?: number; height?: number } ): number | undefined { if (item.standard_criteria) { const designStr = typeof item.standard_criteria === 'object' ? String((item.standard_criteria as Record).nominal ?? '') : String(item.standard_criteria); const parsed = parseFloat(designStr); if (!isNaN(parsed)) return parsed; } const refVal = resolveRefValue(item.field_values, workItemDimensions); return refVal !== null ? refVal : undefined; } // ===== 동적 폼 (템플릿 기반) ===== function DynamicInspectionForm({ template, formValues, onValueChange, workItemDimensions, }: { template: NonNullable; formValues: Record; onValueChange: (key: string, value: unknown) => void; workItemDimensions?: { width?: number; height?: number }; }) { // 모든 섹션의 아이템을 플랫하게 렌더링 (섹션 구분은 가벼운 구분선으로) return (
{template.sections.map((section, sectionIdx) => (
{/* 섹션 구분: 2번째 섹션부터 구분선 표시 */} {sectionIdx > 0 && (
{section.name}
)} {section.items.map((item) => { const fieldKey = `section_${section.id}_item_${item.id}`; const itemLabel = item.item || item.name || ''; const designValue = resolveDesignValue(item, workItemDimensions); // ── 측정치수 입력 (길이, 높이 등) ── if (isNumericItem(item)) { const numValue = formValues[fieldKey] as number | null | undefined; const designLabel = designValue !== undefined ? formatNumber(designValue) : ''; const toleranceLabel = item.tolerance ? ` (${designLabel ? designLabel + ' ' : ''}${formatToleranceLabel(item.tolerance)})` : designLabel ? ` (${designLabel})` : ''; let itemJudgment: 'pass' | 'fail' | null = null; if (item.tolerance && numValue != null && designValue !== undefined) { itemJudgment = evaluateTolerance(numValue, designValue, item.tolerance); } return (
{itemLabel}{toleranceLabel} {itemJudgment && ( {itemJudgment === 'pass' ? '적합' : '부적합'} )}
{ const v = e.target.value === '' ? null : parseFloat(e.target.value); onValueChange(fieldKey, v); }} className="h-11 rounded-lg border-gray-300" />
); } // ── OK/NG 체크 (기준치수 표시 있으면 함께 표시) ── const value = formValues[fieldKey] as 'ok' | 'ng' | null | undefined; const hasStandard = designValue !== undefined || item.standard; const standardDisplay = designValue !== undefined ? (item.tolerance ? `${formatNumber(designValue)} ${formatToleranceLabel(item.tolerance)}` : String(formatNumber(designValue))) : item.standard; return (
{itemLabel} {hasStandard && standardDisplay && (

기준: {standardDisplay}

)}
onValueChange(fieldKey, v)} />
); })}
))}
); } // 동적 폼의 자동 판정 계산 function computeDynamicJudgment( template: NonNullable, formValues: Record, workItemDimensions?: { width?: number; height?: number } ): 'pass' | 'fail' | null { let totalItems = 0; let filledItems = 0; let hasFail = false; for (const section of template.sections) { for (const item of section.items) { totalItems++; const fieldKey = `section_${section.id}_item_${item.id}`; const value = formValues[fieldKey]; if (isNumericItem(item)) { const numValue = value as number | null | undefined; if (numValue != null) { filledItems++; if (item.tolerance) { const design = resolveDesignValue(item, workItemDimensions); if (design !== undefined) { const result = evaluateTolerance(numValue, design, item.tolerance); if (result === 'fail') hasFail = true; } } } } else { if (value != null) { filledItems++; if (value === 'ng') hasFail = true; } } } } if (filledItems === 0) return null; if (hasFail) return 'fail'; // 모든 항목이 입력되어야 적합 판정 if (filledItems < totalItems) return null; return 'pass'; } export function InspectionInputModal({ open, onOpenChange, processType, productName = '', specification = '', initialData, onComplete, templateData, workItemDimensions, workOrderId, }: InspectionInputModalProps) { // 템플릿 모드 여부 // 절곡(bending)은 7제품 커스텀 폼 사용 → TemplateInspectionContent의 bending 셀 키와 연동 const useTemplateMode = processType !== 'bending' && !!(templateData?.has_template && templateData.template); const [formData, setFormData] = useState({ productName, specification, judgment: null, nonConformingContent: '', }); // 동적 폼 값 (템플릿 모드용) const [dynamicFormValues, setDynamicFormValues] = useState>({}); // 이전 형식 데이터 로드 시 auto-judgment가 judgment를 덮어쓰지 않도록 보호 const skipAutoJudgmentRef = useRef(false); // 절곡용 간격 포인트 초기화 (레거시 — bending_wip 등에서 사용) const [gapPoints, setGapPoints] = useState<{ left: number | null; right: number | null }[]>( Array(5).fill(null).map(() => ({ left: null, right: null })) ); // 절곡 API 제품 정의 (gap_points 동적 로딩) const [apiProductDefs, setApiProductDefs] = useState(null); const effectiveProductDefs = apiProductDefs || BENDING_PRODUCTS; // 절곡 7개 제품별 상태 (bending 전용) const [bendingProducts, setBendingProducts] = useState(createInitialBendingProducts); // API에서 절곡 제품 gap_points 동적 로딩 useEffect(() => { if (!open || processType !== 'bending' || !workOrderId) return; let cancelled = false; getInspectionConfig(workOrderId).then(result => { if (cancelled) return; if (result.success && result.data?.items?.length) { const displayMap: Record = { guide_rail_wall: { label: '가이드레일 (벽면형)', len: '3000', wid: 'N/A' }, guide_rail_side: { label: '가이드레일 (측면형)', len: '3000', wid: 'N/A' }, case_box: { label: '케이스 (500X380)', len: '3000', wid: 'N/A' }, bottom_bar: { label: '하단마감재 (60X40)', len: '3000', wid: 'N/A' }, bottom_l_bar: { label: '하단L-BAR (17X60)', len: '3000', wid: 'N/A' }, smoke_w50: { label: '연기차단재 (W50)', len: '3000', wid: '' }, smoke_w80: { label: '연기차단재 (W80)', len: '3000', wid: '' }, }; const defs: BendingProductDef[] = result.data.items.map(item => { const d = displayMap[item.id] || { label: item.name, len: '-', wid: 'N/A' }; return { id: item.id, label: d.label, lengthDesign: d.len, widthDesign: d.wid, gapPoints: item.gap_points.map(gp => ({ point: gp.point, design: gp.design_value })), }; }); setApiProductDefs(defs); } }); return () => { cancelled = true; }; }, [open, processType, workOrderId]); // API 제품 정의 로딩 시 bendingProducts 갱신 (gap 개수 동기화) useEffect(() => { if (!apiProductDefs || processType !== 'bending') return; setBendingProducts(prev => { return apiProductDefs.map((def, idx) => { // 기존 입력값 보존 (ID 매칭 또는 인덱스 폴백) const existing = prev.find(p => p.id === def.id || p.id.replace(/[-_]/g, '') === def.id.replace(/[-_]/g, '')) || (idx < prev.length ? prev[idx] : undefined); return { id: def.id, bendingStatus: existing?.bendingStatus ?? null, lengthMeasured: existing?.lengthMeasured ?? '', widthMeasured: existing?.widthMeasured ?? '', gapMeasured: def.gapPoints.map((_, gi) => existing?.gapMeasured?.[gi] ?? ''), }; }); }); }, [apiProductDefs, processType]); useEffect(() => { if (open) { // initialData가 있으면 기존 저장 데이터로 복원 if (initialData) { setFormData({ ...initialData, productName: initialData.productName || productName, specification: initialData.specification || specification, nonConformingContent: initialData.nonConformingContent ?? '', }); if (initialData.gapPoints) { setGapPoints(initialData.gapPoints); } else { setGapPoints(Array(5).fill(null).map(() => ({ left: null, right: null }))); } // 절곡 제품별 데이터 복원 const savedProducts = (initialData as unknown as Record).products as Array<{ id: string; bendingStatus: string; lengthMeasured: string; widthMeasured: string; gapPoints: Array<{ point: string; designValue: string; measured: string }>; }> | undefined; if (savedProducts && Array.isArray(savedProducts)) { setBendingProducts(effectiveProductDefs.map((def, idx) => { const saved = savedProducts.find(sp => sp.id === def.id || sp.id.replace(/[-_]/g, '') === def.id.replace(/[-_]/g, '') ) || (idx < savedProducts.length ? savedProducts[idx] : undefined); if (!saved) return { id: def.id, bendingStatus: null, lengthMeasured: '', widthMeasured: '', gapMeasured: def.gapPoints.map(() => '') }; return { id: def.id, bendingStatus: saved.bendingStatus === '양호' ? 'good' : saved.bendingStatus === '불량' ? 'bad' : (saved.bendingStatus as 'good' | 'bad' | null), lengthMeasured: saved.lengthMeasured || '', widthMeasured: saved.widthMeasured || '', gapMeasured: def.gapPoints.map((_, gi) => saved.gapPoints?.[gi]?.measured || ''), }; })); } else if (processType === 'bending' && initialData.judgment) { // 이전 형식 데이터 호환: products 배열 없이 저장된 경우 // judgment 값으로 제품별 상태 추론 (pass → 전체 양호) const restoredStatus: 'good' | 'bad' | null = initialData.judgment === 'pass' ? 'good' : initialData.judgment === 'fail' ? 'bad' : null; setBendingProducts(effectiveProductDefs.map(def => ({ id: def.id, bendingStatus: restoredStatus, lengthMeasured: '', widthMeasured: '', gapMeasured: def.gapPoints.map(() => ''), }))); // 이전 형식은 lengthMeasured가 없어 autoJudgment가 null이 되므로 // 로드된 judgment를 덮어쓰지 않도록 보호 skipAutoJudgmentRef.current = true; } else { setBendingProducts(createInitialBendingProducts()); } // 동적 폼 값 복원 (템플릿 기반 검사 데이터) if (initialData.templateValues) { setDynamicFormValues(initialData.templateValues); } else { setDynamicFormValues({}); } return; } // 공정별 기본값 설정 - 모두 미선택(null) 상태로 초기화 const baseData: InspectionData = { productName, specification, judgment: null, nonConformingContent: '', }; // 공정별 추가 기본값 설정 (모두 null) switch (processType) { case 'screen': setFormData({ ...baseData, processingStatus: null, sewingStatus: null, assemblyStatus: null, gapStatus: null, }); break; case 'slat': setFormData({ ...baseData, processingStatus: null, assemblyStatus: null, }); break; case 'slat_jointbar': setFormData({ ...baseData, processingStatus: null, assemblyStatus: null, }); break; case 'bending': setFormData({ ...baseData, bendingStatus: null, }); break; case 'bending_wip': setFormData({ ...baseData, bendingStatus: null, }); break; default: setFormData(baseData); } setGapPoints(Array(5).fill(null).map(() => ({ left: null, right: null }))); setBendingProducts(createInitialBendingProducts()); setDynamicFormValues({}); } }, [open, productName, specification, processType, initialData]); // 자동 판정 계산 (템플릿 모드 vs 절곡 7제품 모드 vs 레거시 모드) const autoJudgment = useMemo(() => { if (useTemplateMode && templateData?.template) { return computeDynamicJudgment(templateData.template, dynamicFormValues, workItemDimensions); } // 절곡 7개 제품 전용 판정 if (processType === 'bending') { let allGood = true; let allFilled = true; for (const p of bendingProducts) { if (p.bendingStatus === 'bad') return 'fail'; if (p.bendingStatus !== 'good') { allGood = false; allFilled = false; } if (!p.lengthMeasured) allFilled = false; } if (allGood && allFilled) return 'pass'; return null; } return computeJudgment(processType, formData); }, [useTemplateMode, templateData, dynamicFormValues, workItemDimensions, processType, formData, bendingProducts]); // 판정값 자동 동기화 (이전 형식 데이터 로드 시 첫 번째 동기화 건너뜀) useEffect(() => { if (skipAutoJudgmentRef.current) { skipAutoJudgmentRef.current = false; return; } setFormData((prev) => { if (prev.judgment === autoJudgment) return prev; return { ...prev, judgment: autoJudgment }; }); }, [autoJudgment]); const handleComplete = () => { const baseData: InspectionData = { ...formData, // 동적 폼 값을 templateValues로 병합 ...(useTemplateMode ? { templateValues: dynamicFormValues } : {}), }; // 절곡: products 배열을 성적서와 동일 포맷으로 저장 if (processType === 'bending') { const products = bendingProducts.map((p, idx) => ({ id: p.id, bendingStatus: p.bendingStatus === 'good' ? '양호' : p.bendingStatus === 'bad' ? '불량' : null, lengthMeasured: p.lengthMeasured, widthMeasured: p.widthMeasured, gapPoints: (effectiveProductDefs[idx]?.gapPoints || []).map((gp, gi) => ({ point: gp.point, designValue: gp.design, measured: p.gapMeasured[gi] || '', })), })); const data = { ...baseData, products } as unknown as InspectionData; onComplete(data); onOpenChange(false); return; } onComplete(baseData); onOpenChange(false); }; const handleCancel = () => { onOpenChange(false); }; // 숫자 입력 핸들러 const handleNumberChange = ( key: keyof InspectionData, value: string ) => { const num = value === '' ? null : parseFloat(value); setFormData((prev) => ({ ...prev, [key]: num })); }; return ( {PROCESS_TITLES[processType]}
{/* 기본 정보 (읽기전용) */}
제품명
규격
{/* ===== 동적 폼 (템플릿 기반) ===== */} {useTemplateMode && templateData?.template && ( setDynamicFormValues((prev) => ({ ...prev, [key]: value })) } workItemDimensions={workItemDimensions} /> )} {/* ===== 레거시: 공정별 하드코딩 검사 항목 (템플릿 없을 때만 표시) ===== */} {/* ===== 재고생산 (bending_wip) 검사 항목 ===== */} {!useTemplateMode && processType === 'bending_wip' && ( <>
검모양 절곡상태 setFormData((prev) => ({ ...prev, bendingStatus: v }))} />
길이 ({formatDimension(workItemDimensions?.width)}) handleNumberChange('length', e.target.value)} className="h-11 rounded-lg border-gray-300" />
너비 ({formatDimension(workItemDimensions?.height)}) handleNumberChange('width', e.target.value)} className="h-11 rounded-lg border-gray-300" />
간격 (1,000)
)} {/* ===== 스크린 검사 항목 ===== */} {!useTemplateMode && processType === 'screen' && ( <>
검모양 가공상태 setFormData((prev) => ({ ...prev, processingStatus: v }))} />
검모양 재봉상태 setFormData((prev) => ({ ...prev, sewingStatus: v }))} />
검모양 조립상태 setFormData((prev) => ({ ...prev, assemblyStatus: v }))} />
길이 ({formatDimension(workItemDimensions?.width)}) handleNumberChange('length', e.target.value)} className="h-11 rounded-lg border-gray-300" />
너비 ({formatDimension(workItemDimensions?.height)}) handleNumberChange('width', e.target.value)} className="h-11 rounded-lg border-gray-300" />
간격 (400 이하) setFormData((prev) => ({ ...prev, gapStatus: v }))} />
)} {/* ===== 슬랫 검사 항목 ===== */} {!useTemplateMode && processType === 'slat' && ( <>
검모양 가공상태 setFormData((prev) => ({ ...prev, processingStatus: v }))} />
검모양 조립상태 setFormData((prev) => ({ ...prev, assemblyStatus: v }))} />
① 높이 (16.5 ± 1) handleNumberChange('height1', e.target.value)} className="h-11 rounded-lg border-gray-300" />
② 높이 (14.5 ± 1) handleNumberChange('height2', e.target.value)} className="h-11 rounded-lg border-gray-300" />
길이 (0) handleNumberChange('length', e.target.value)} className="h-11 rounded-lg border-gray-300" />
)} {/* ===== 조인트바 검사 항목 ===== */} {!useTemplateMode && processType === 'slat_jointbar' && ( <>
검모양 가공상태 setFormData((prev) => ({ ...prev, processingStatus: v }))} />
검모양 조립상태 setFormData((prev) => ({ ...prev, assemblyStatus: v }))} />
① 높이 (16.5 ± 1) handleNumberChange('height1', e.target.value)} className="h-11 rounded-lg border-gray-300" />
② 높이 (14.5 ± 1) handleNumberChange('height2', e.target.value)} className="h-11 rounded-lg border-gray-300" />
③ 길이 (300 ± 1) handleNumberChange('length3', e.target.value)} className="h-11 rounded-lg border-gray-300" />
④ 간격 (150 ± 1) handleNumberChange('gap4', e.target.value)} className="h-11 rounded-lg border-gray-300" />
)} {/* ===== 절곡 검사 항목 (7개 제품별) ===== */} {!useTemplateMode && processType === 'bending' && (
{effectiveProductDefs.map((productDef, pIdx) => { const pState = bendingProducts[pIdx]; if (!pState) return null; const updateProduct = (updates: Partial) => { setBendingProducts(prev => prev.map((p, i) => i === pIdx ? { ...p, ...updates } : p)); }; return (
0 && 'border-t border-gray-200 pt-4')}> {/* 제품명 헤더 */}
{pIdx + 1}. {productDef.label}
{/* 절곡상태 */}
절곡상태 updateProduct({ bendingStatus: v })} />
{/* 길이 / 너비 */}
길이 ({productDef.lengthDesign}) updateProduct({ lengthMeasured: e.target.value })} className="h-10 rounded-lg border-gray-300 text-sm" />
너비 ({productDef.widthDesign || '-'}) {productDef.widthDesign === 'N/A' ? ( ) : ( updateProduct({ widthMeasured: e.target.value })} className="h-10 rounded-lg border-gray-300 text-sm" /> )}
{/* 간격 포인트 */} {productDef.gapPoints.length > 0 && (
간격
{productDef.gapPoints.map((gp, gi) => (
{gp.point} ({gp.design}) { const newGaps = [...pState.gapMeasured]; newGaps[gi] = e.target.value; updateProduct({ gapMeasured: newGaps }); }} className="h-9 rounded-lg border-gray-300 text-sm" />
))}
)}
); })}
)} {/* 부적합 내용 */}
부적합 내용