'use client'; /** * 절곡 중간검사 성적서 문서 콘텐츠 * * 기획서 기준: * - 헤더: "중간검사성적서 (절곡)" + 결재란 * - 기본정보: 제품명/슬랫, 규격/절곡, 수주처, 현장명 | 제품LOT NO, 로트크기, 검사일자, 검사자 * + 제품명/KWE01, 마감유형/소니자감 * - ■ 중간검사 기준서: 2섹션 (가이드레일류 + 연기차단재) * - ■ 중간검사 DATA: 분류, 제품명, 타입, 절곡상태결모양(양호/불량), * 길이(도면치수/측정값입력), 너비(도면치수/측정값입력), * 간격(포인트/도면치수/측정값입력), 판정(자동) * - 부적합 내용 / 종합판정(자동) */ import { useState, useCallback, useMemo, forwardRef, useImperativeHandle, useEffect } from 'react'; import type { WorkOrder } from '../types'; import type { InspectionData } from '@/components/production/WorkerScreen/InspectionInputModal'; import type { WorkItemData } from '@/components/production/WorkerScreen/types'; import type { InspectionDataMap } from './InspectionReportModal'; export interface InspectionContentRef { getInspectionData: () => unknown; } export interface BendingInspectionContentProps { data: WorkOrder; readOnly?: boolean; inspectionData?: InspectionData; /** 작업 아이템 목록 - 행 개수 동적 생성용 */ workItems?: WorkItemData[]; /** 아이템별 검사 데이터 맵 */ inspectionDataMap?: InspectionDataMap; /** 기준서 도해 이미지 URL */ schematicImage?: string; /** 검사기준 이미지 URL */ inspectionStandardImage?: string; } type CheckStatus = '양호' | '불량' | null; interface GapPoint { point: string; // ①②③④⑤ designValue: string; // 도면치수 measured: string; // 측정값 (입력) } interface ProductRow { id: string; category: string; productName: string; productType: string; bendingStatus: CheckStatus; lengthDesign: string; lengthMeasured: string; widthDesign: string; widthMeasured: string; gapPoints: GapPoint[]; } /** * 절곡 검사성적서 - 가이드레일 타입별 행 구조 * * | 타입 조합 | 가이드레일 행 개수 | * |-----------------------|-------------------| * | 벽면형/벽면형 (벽벽) | 1행 | * | 측면형/측면형 (측측) | 1행 | * | 벽면형/측면형 (혼합형) | 2행 (규격이 달라서) | * * TODO: 실제 구현 시 공정 데이터에서 타입 정보를 받아서 * INITIAL_PRODUCTS를 동적으로 생성해야 함 */ const INITIAL_PRODUCTS: Omit[] = [ // 현재 목업: 혼합형(벽/측)인 경우 가이드레일 2행 { id: 'guide-rail-wall', category: 'KWE01', productName: '가이드레일', productType: '벽면형', lengthDesign: '3000', widthDesign: 'N/A', gapPoints: [ { point: '①', designValue: '30', measured: '' }, { point: '②', designValue: '80', measured: '' }, { point: '③', designValue: '45', measured: '' }, { point: '④', designValue: '40', measured: '' }, { point: '⑤', designValue: '34', measured: '' }, ], }, { id: 'guide-rail-side', category: 'KWE01', productName: '가이드레일', productType: '측면형', lengthDesign: '3000', widthDesign: 'N/A', gapPoints: [ { point: '①', designValue: '28', measured: '' }, { point: '②', designValue: '75', measured: '' }, { point: '③', designValue: '42', measured: '' }, { point: '④', designValue: '38', measured: '' }, { point: '⑤', designValue: '32', measured: '' }, ], }, { id: 'case', category: 'KWE01', productName: '케이스', productType: '500X380', lengthDesign: '3000', widthDesign: 'N/A', gapPoints: [ { point: '①', designValue: '380', measured: '' }, { point: '②', designValue: '50', measured: '' }, { point: '③', designValue: '240', measured: '' }, { point: '④', designValue: '50', measured: '' }, ], }, { id: 'bottom-finish', category: 'KWE01', productName: '하단마감재', productType: '60X40', lengthDesign: '3000', widthDesign: 'N/A', gapPoints: [ { point: '②', designValue: '60', measured: '' }, { point: '②', designValue: '64', measured: '' }, ], }, { id: 'bottom-l-bar', category: 'KWE01', productName: '하단L-BAR', productType: '17X60', lengthDesign: '3000', widthDesign: 'N/A', gapPoints: [ { point: '①', designValue: '17', measured: '' }, ], }, { id: 'smoke-w50', category: 'KWE01', productName: '연기차단재', productType: 'W50\n가이드레일용', lengthDesign: '3000', widthDesign: '', gapPoints: [ { point: '①', designValue: '50', measured: '' }, { point: '②', designValue: '12', measured: '' }, ], }, { id: 'smoke-w80', category: 'KWE01', productName: '연기차단재', productType: 'W80\n케이스용', lengthDesign: '3000', widthDesign: '', gapPoints: [ { point: '①', designValue: '80', measured: '' }, { point: '②', designValue: '12', measured: '' }, ], }, ]; // 상태 변환 함수: 'good'/'bad' → '양호'/'불량' const convertToCheckStatus = (status: 'good' | 'bad' | null | undefined): CheckStatus => { if (status === 'good') return '양호'; if (status === 'bad') return '불량'; return null; }; export const BendingInspectionContent = forwardRef(function BendingInspectionContent({ data: order, readOnly = false, workItems, inspectionDataMap, schematicImage, inspectionStandardImage, }, ref) { const fullDate = new Date().toLocaleDateString('ko-KR', { year: 'numeric', month: 'long', day: 'numeric', }); const today = new Date().toLocaleDateString('ko-KR', { year: 'numeric', month: '2-digit', day: '2-digit', }).replace(/\. /g, '-').replace('.', ''); const documentNo = order.workOrderNo || 'ABC123'; const primaryAssignee = order.assignees?.find(a => a.isPrimary)?.name || order.assignee || '-'; const [products, setProducts] = useState(() => INITIAL_PRODUCTS.map(p => ({ ...p, bendingStatus: null, lengthMeasured: '', widthMeasured: '', gapPoints: p.gapPoints.map(gp => ({ ...gp })), })) ); const [inadequateContent, setInadequateContent] = useState(''); // workItems의 첫 번째 아이템 검사 데이터로 절곡상태 적용 useEffect(() => { if (workItems && workItems.length > 0 && inspectionDataMap) { const firstItem = workItems[0]; const itemData = inspectionDataMap.get(firstItem.id); if (itemData?.bendingStatus) { const bendingStatusValue = convertToCheckStatus(itemData.bendingStatus); setProducts(prev => prev.map(p => ({ ...p, bendingStatus: bendingStatusValue, }))); } } }, [workItems, inspectionDataMap]); const handleStatusChange = useCallback((productId: string, value: CheckStatus) => { if (readOnly) return; setProducts(prev => prev.map(p => p.id === productId ? { ...p, bendingStatus: value } : p )); }, [readOnly]); const handleInputChange = useCallback((productId: string, field: 'lengthMeasured' | 'widthMeasured', value: string) => { if (readOnly) return; setProducts(prev => prev.map(p => p.id === productId ? { ...p, [field]: value } : p )); }, [readOnly]); const handleGapMeasuredChange = useCallback((productId: string, gapIndex: number, value: string) => { if (readOnly) return; setProducts(prev => prev.map(p => { if (p.id !== productId) return p; const newGapPoints = p.gapPoints.map((gp, i) => i === gapIndex ? { ...gp, measured: value } : gp ); return { ...p, gapPoints: newGapPoints }; })); }, [readOnly]); // 행별 판정 자동 계산 const getProductJudgment = useCallback((product: ProductRow): '적' | '부' | null => { if (product.bendingStatus === '불량') return '부'; if (product.bendingStatus === '양호') return '적'; return null; }, []); // 종합판정 자동 계산 const overallResult = useMemo(() => { const judgments = products.map(getProductJudgment); if (judgments.some(j => j === '부')) return '불합격'; if (judgments.every(j => j === '적')) return '합격'; return null; }, [products, getProductJudgment]); useImperativeHandle(ref, () => ({ getInspectionData: () => ({ products: products.map(p => ({ id: p.id, category: p.category, productName: p.productName, productType: p.productType, bendingStatus: p.bendingStatus, lengthMeasured: p.lengthMeasured, widthMeasured: p.widthMeasured, gapPoints: p.gapPoints.map(gp => ({ point: gp.point, designValue: gp.designValue, measured: gp.measured, })), })), inadequateContent, overallResult, }), }), [products, inadequateContent, overallResult]); // PDF 호환 체크박스 렌더 const renderCheckbox = (checked: boolean, onClick: () => void) => ( !readOnly && onClick()} role="checkbox" aria-checked={checked} > {checked ? '✓' : ''} ); const inputClass = 'w-full text-center border-0 bg-transparent focus:outline-none focus:ring-1 focus:ring-blue-500 rounded text-xs'; // 전체 행 수 계산 (간격 포인트 수 합계) const totalRows = products.reduce((sum, p) => sum + p.gapPoints.length, 0); return (
{/* ===== 헤더 영역 ===== */}

중간검사성적서 (절곡)

문서번호: {documentNo} | 작성일자: {fullDate}

{/* 결재란 */}

작성 승인 승인 승인
{primaryAssignee} 이름 이름 이름
부서명 부서명 부서명 부서명
{/* ===== 기본 정보 ===== */}
제품명 슬랫 제품 LOT NO {order.lotNo || '-'}
규격 절곡 로트크기 {order.items?.length || 0} 개소
수주처 {order.client || '-'} 검사일자 {today}
현장명 {order.projectName || '-'} 검사자 {primaryAssignee}
제품명 KWE01 마감유형 소니자감
{/* ===== 중간검사 기준서 (1) 가이드레일/케이스/하단마감재/하단L-BAR ===== */}
■ 중간검사 기준서
{/* 도해 3개 (가이드레일 / 케이스 / 하단마감재) */} {/* 기준서 헤더 */} {/* 겉모양 | 절곡상태 */} {/* 치수 > 길이 */} {/* 치수 > 간격 */}
가이드레일
케이스
하단마감재
하단 L-BAR
가이드레일 도해
케이스 도해
하단마감재 도해
{schematicImage ? (
기준서 도해
) : (
IMG
IMG
IMG
)}
도해 검사항목 검사기준 검사방법 검사주기 관련규정
절곡류 중간검사
상세도면 참조
겉모양 절곡상태 사용상 해로운 결함이 없을것 육안검사 n = 1, c = 0 KS F 4510 5.1항
치수
(mm)
길이 도면치수 ± 4 체크검사 KS F 4510 7항
표9
간격 도면치수 ± 2 KS F 4510 7항
표9 / 자체규정
{/* ===== 중간검사 기준서 (2) 연기차단재 ===== */} {/* 헤더 */} {/* 겉모양 | 절곡상태 (row 1) */} {/* 겉모양 | 절곡상태 (row 2 - 관련규정 분리) */} {/* 치수 > 길이 */} {/* 치수 > 나비 */} {/* 치수 > 간격 */}
연기차단재 도해 검사항목 검사기준 검사방법 검사주기 관련규정
{inspectionStandardImage ? ( 검사기준 도해 ) : (
도해 이미지 영역
)}
겉모양 절곡상태 절단이 프레임에서 빠지지 않을것 육안검사 n = 1, c = 0 KS F 4510 5.1항
KS F 4510 7항
표9 인용
치수
(mm)
길이 도면치수 ± 4 체크검사 자체규정
나비 W50 : 50 ± 5
W80 : 80 ± 5
간격 도면치수 ± 2
{/* ===== 중간검사 DATA ===== */}
■ 중간검사 DATA
{products.map((product) => { const judgment = getProductJudgment(product); const rowCount = product.gapPoints.length; return product.gapPoints.map((gap, gapIdx) => ( {/* 첫 번째 간격 행에만 rowSpan 적용 */} {gapIdx === 0 && ( <> {/* 절곡상태 - 양호/불량 체크 */} {/* 길이 */} {/* 너비 */} )} {/* 간격 - 포인트별 개별 행 */} {/* 판정 - 자동 (첫 행에만) */} {gapIdx === 0 && ( )} )); })}
분류 제품명 타입 절곡상태
결모양
길이 (mm) 너비 (mm) 간격 (mm) 판정
(적/부)
도면치수 측정값 도면치수 측정값 포인트 도면치수 측정값
{product.category} {product.productName} {product.productType}
{product.lengthDesign} handleInputChange(product.id, 'lengthMeasured', e.target.value)} disabled={readOnly} className={inputClass} placeholder="-" /> {product.widthDesign || 'N/A'} handleInputChange(product.id, 'widthMeasured', e.target.value)} disabled={readOnly} className={inputClass} placeholder="-" /> {gap.point} {gap.designValue} handleGapMeasuredChange(product.id, gapIdx, e.target.value)} disabled={readOnly} className={inputClass} placeholder="-" /> {judgment || '-'}
{/* ===== 부적합 내용 + 종합판정 ===== */}
부적합 내용