'use client'; /** * 스크린 중간검사 성적서 문서 콘텐츠 * * 기획서 기준: * - 헤더: "중간검사성적서 (스크린)" + 결재란 * - 기본정보: 제품명/스크린, 규격/와이어 글라스 코팅직물, 수주처, 현장명 | 제품LOT NO, 로트크기, 검사일자, 검사자 * - ■ 중간검사 기준서: 도해 + 검사항목/검사기준/검사방법/검사주기/관련규정 * 가공상태, 재봉상태, 조립상태, 치수(길이/높이/간격) * - ■ 중간검사 DATA: No, 가공상태결모양(양호/불량), 재봉상태결모양(양호/불량), 조립상태(양호/불량), * 길이(도면치수/측정값입력), 나비(도면치수/측정값입력), 간격(기준치/OK·NG선택), 판정(자동) * - 부적합 내용 / 종합판정(자동) */ 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 ScreenInspectionContentProps { data: WorkOrder; readOnly?: boolean; inspectionData?: InspectionData; /** 작업 아이템 목록 - 행 개수 동적 생성용 */ workItems?: WorkItemData[]; /** 아이템별 검사 데이터 맵 */ inspectionDataMap?: InspectionDataMap; /** 기준서 도해 이미지 URL */ schematicImage?: string; /** 검사기준 이미지 URL */ inspectionStandardImage?: string; } type CheckStatus = '양호' | '불량' | null; type GapResult = 'OK' | 'NG' | null; interface InspectionRow { id: number; itemId?: string; // 작업 아이템 ID itemName?: string; // 작업 아이템 이름 processStatus: CheckStatus; // 가공상태 결모양 sewingStatus: CheckStatus; // 재봉상태 결모양 assemblyStatus: CheckStatus; // 조립상태 lengthDesign: string; // 길이 도면치수 (표시용) lengthMeasured: string; // 길이 측정값 (입력) widthDesign: string; // 나비 도면치수 (표시용) widthMeasured: string; // 나비 측정값 (입력) gapStandard: string; // 간격 기준치 (표시용) gapResult: GapResult; // 간격 측정값 (OK/NG 선택) } const DEFAULT_ROW_COUNT = 6; export const ScreenInspectionContent = forwardRef(function ScreenInspectionContent({ 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 || '-'; // 행 개수: workItems가 있으면 그 개수, 없으면 기본값 const rowCount = workItems?.length || DEFAULT_ROW_COUNT; // InspectionData를 InspectionRow로 변환하는 함수 const convertToCheckStatus = (status: 'good' | 'bad' | null | undefined): CheckStatus => { if (status === 'good') return '양호'; if (status === 'bad') return '불량'; return null; }; const convertToGapResult = (status: 'ok' | 'ng' | null | undefined): GapResult => { if (status === 'ok') return 'OK'; if (status === 'ng') return 'NG'; return null; }; const [rows, setRows] = useState(() => { return Array.from({ length: rowCount }, (_, i) => { const item = workItems?.[i]; const itemData = item && inspectionDataMap?.get(item.id); return { id: i + 1, itemId: item?.id, itemName: item?.itemName || '', processStatus: itemData ? convertToCheckStatus(itemData.processingStatus) : null, sewingStatus: itemData ? convertToCheckStatus(itemData.sewingStatus) : null, assemblyStatus: itemData ? convertToCheckStatus(itemData.assemblyStatus) : null, lengthDesign: '7,400', lengthMeasured: itemData?.length?.toString() || '', widthDesign: '2,950', widthMeasured: itemData?.width?.toString() || '', gapStandard: '400 이하', gapResult: itemData ? convertToGapResult(itemData.gapStatus) : null, }; }); }); // workItems나 inspectionDataMap이 변경되면 rows 업데이트 useEffect(() => { const newRowCount = workItems?.length || DEFAULT_ROW_COUNT; setRows(Array.from({ length: newRowCount }, (_, i) => { const item = workItems?.[i]; const itemData = item && inspectionDataMap?.get(item.id); return { id: i + 1, itemId: item?.id, itemName: item?.itemName || '', processStatus: itemData ? convertToCheckStatus(itemData.processingStatus) : null, sewingStatus: itemData ? convertToCheckStatus(itemData.sewingStatus) : null, assemblyStatus: itemData ? convertToCheckStatus(itemData.assemblyStatus) : null, lengthDesign: '7,400', lengthMeasured: itemData?.length?.toString() || '', widthDesign: '2,950', widthMeasured: itemData?.width?.toString() || '', gapStandard: '400 이하', gapResult: itemData ? convertToGapResult(itemData.gapStatus) : null, }; })); }, [workItems, inspectionDataMap]); const [inadequateContent, setInadequateContent] = useState(''); const handleStatusChange = useCallback((rowId: number, field: 'processStatus' | 'sewingStatus' | 'assemblyStatus', value: CheckStatus) => { if (readOnly) return; setRows(prev => prev.map(row => row.id === rowId ? { ...row, [field]: value } : row )); }, [readOnly]); // 숫자 콤마 포맷 const formatNumberWithComma = (value: string): string => { const num = value.replace(/[^\d]/g, ''); if (!num) return ''; return Number(num).toLocaleString(); }; const handleInputChange = useCallback((rowId: number, field: 'lengthMeasured' | 'widthMeasured', value: string) => { if (readOnly) return; // 숫자만 저장 (콤마 제거) const numOnly = value.replace(/[^\d]/g, ''); setRows(prev => prev.map(row => row.id === rowId ? { ...row, [field]: numOnly } : row )); }, [readOnly]); const handleGapChange = useCallback((rowId: number, value: GapResult) => { if (readOnly) return; setRows(prev => prev.map(row => row.id === rowId ? { ...row, gapResult: value } : row )); }, [readOnly]); // 행별 판정 자동 계산 const getRowJudgment = useCallback((row: InspectionRow): '적' | '부' | null => { const { processStatus, sewingStatus, assemblyStatus, gapResult } = row; // 하나라도 불량 or NG → 부 if (processStatus === '불량' || sewingStatus === '불량' || assemblyStatus === '불량' || gapResult === 'NG') { return '부'; } // 모두 양호 + OK → 적 if (processStatus === '양호' && sewingStatus === '양호' && assemblyStatus === '양호' && gapResult === 'OK') { return '적'; } return null; }, []); // 종합판정 자동 계산 const overallResult = useMemo(() => { const judgments = rows.map(getRowJudgment); if (judgments.some(j => j === '부')) return '불합격'; if (judgments.every(j => j === '적')) return '합격'; return null; }, [rows, getRowJudgment]); useImperativeHandle(ref, () => ({ getInspectionData: () => ({ rows: rows.map(row => ({ id: row.id, processStatus: row.processStatus, sewingStatus: row.sewingStatus, assemblyStatus: row.assemblyStatus, lengthMeasured: row.lengthMeasured, widthMeasured: row.widthMeasured, gapResult: row.gapResult, })), inadequateContent, overallResult, }), }), [rows, inadequateContent, overallResult]); // PDF 호환 체크박스 렌더 (양호/불량) const renderCheckbox = (checked: boolean, onClick: () => void) => ( !readOnly && onClick()} role="checkbox" aria-checked={checked} > {checked ? '✓' : ''} ); const renderCheckStatus = (rowId: number, field: 'processStatus' | 'sewingStatus' | 'assemblyStatus', value: CheckStatus) => (
); const inputClass = 'w-full text-center border-0 bg-transparent focus:outline-none focus:ring-1 focus:ring-blue-500 rounded text-xs'; return (
{/* ===== 헤더 영역 ===== */}

중간검사성적서 (스크린)

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

{/* 결재란 */}

작성 승인 승인 승인
{primaryAssignee} 이름 이름 이름
부서명 부서명 부서명 부서명
{/* ===== 기본 정보 ===== */}
제품명 스크린 제품 LOT NO {order.lotNo || '-'}
규격 와이어 글라스 코팅직물 로트크기 {order.items?.length || 0} 개소
수주처 {order.client || '-'} 검사일자 {today}
현장명 {order.projectName || '-'} 검사자 {primaryAssignee}
{/* ===== 중간검사 기준서 ===== */}
■ 중간검사 기준서
{/* 도해 영역 */} {/* 헤더 행 */}
{schematicImage ? ( 기준서 도해 ) : (
도해 이미지 영역
)}
검사항목 검사기준 검사방법 검사주기 관련규정
가공상태 사용상 해로운 결함이 없을것 육안검사 KS F 4510 5.1항
재봉상태 내화실험 되어 견고하게
조립상태 엔도확인 견고하게 조립되어야 함 KS F 4510
n = 1, c = 0
치수
(mm)
길이 도면치수 ± 4
높이 도면치수 + 제한없음 − 40
간격 400 이하 GONO 게이지 자체규정
{/* ===== 중간검사 DATA ===== */}
■ 중간검사 DATA
{rows.map((row) => { const judgment = getRowJudgment(row); return ( {/* 가공상태 - 양호/불량 체크 */} {renderCheckStatus(row.id, 'processStatus', row.processStatus)} {/* 재봉상태 - 양호/불량 체크 */} {renderCheckStatus(row.id, 'sewingStatus', row.sewingStatus)} {/* 조립상태 - 양호/불량 체크 */} {renderCheckStatus(row.id, 'assemblyStatus', row.assemblyStatus)} {/* 길이 - 도면치수 표시 + 측정값 입력 */} {/* 나비 - 도면치수 표시 + 측정값 입력 */} {/* 간격 - 기준치 표시 + OK/NG 선택 */} {/* 판정 - 자동 계산 */} ); })}
No. 가공상태
결모양
재봉상태
결모양
조립상태 길이 (mm) 나비 (mm) 간격 (mm) 판정
(적/부)
도면치수 측정값 도면치수 측정값 기준치 측정값
{row.id}{row.lengthDesign} handleInputChange(row.id, 'lengthMeasured', e.target.value)} disabled={readOnly} className={inputClass} placeholder="-" /> {row.widthDesign} handleInputChange(row.id, 'widthMeasured', e.target.value)} disabled={readOnly} className={inputClass} placeholder="-" /> {row.gapStandard}
{judgment || '-'}
{/* ===== 부적합 내용 + 종합판정 ===== */}
부적합 내용