diff --git a/src/app/[locale]/(protected)/quality/qms/components/InspectionModal.tsx b/src/app/[locale]/(protected)/quality/qms/components/InspectionModal.tsx index 1cc805bf..535942dd 100644 --- a/src/app/[locale]/(protected)/quality/qms/components/InspectionModal.tsx +++ b/src/app/[locale]/(protected)/quality/qms/components/InspectionModal.tsx @@ -6,6 +6,7 @@ import { DocumentViewer } from '@/components/document-system'; import { Button } from '@/components/ui/button'; import { toast } from 'sonner'; import { Document, DocumentItem } from '../types'; +import { getDocumentDetail } from '../actions'; import { MOCK_SHIPMENT_DETAIL } from '../mockData'; // 기존 문서 컴포넌트 import @@ -24,10 +25,12 @@ import { QualityDocumentUploader, } from './documents'; -// 제품검사 성적서 (신규 양식) import +// 제품검사 성적서 (FQC 양식) import +import { FqcDocumentContent } from '@/components/quality/InspectionManagement/documents/FqcDocumentContent'; import { InspectionReportDocument } from '@/components/quality/InspectionManagement/documents/InspectionReportDocument'; -import { mockReportInspectionItems } from '@/components/quality/InspectionManagement/mockData'; -import type { InspectionReportDocument as InspectionReportDocumentType } from '@/components/quality/InspectionManagement/types'; +import type { FqcTemplate, FqcDocumentData } from '@/components/quality/InspectionManagement/fqcActions'; +import type { InspectionReportDocument as InspectionReportDocumentType, ProductInspectionData } from '@/components/quality/InspectionManagement/types'; +import { mockReportInspectionItems, mapInspectionDataToItems } from '@/components/quality/InspectionManagement/mockData'; import type { ImportInspectionTemplate, ImportInspectionRef, InspectionItemValue } from './documents/ImportInspectionDocument'; // 작업일지 + 중간검사 성적서 문서 컴포넌트 import (공정별 신규 버전) @@ -44,6 +47,9 @@ import type { WorkOrder } from '@/components/production/WorkOrders/types'; // 검사 템플릿 API import { getInspectionTemplate } from '@/components/material/ReceivingManagement/actions'; +// 작업지시 상세 API (QMS 작업일지/중간검사용) +import { getWorkOrderById } from '@/components/production/WorkOrders/actions'; + /** * 저장된 document.data (field_key 기반)를 ImportInspectionDocument의 initialValues로 변환 * @@ -160,26 +166,32 @@ const QMS_MOCK_ORDER_ITEMS: OrderItem[] = [ { id: 'bf-1', itemCode: 'BF-001', itemName: '하단마감재', specification: 'EGI 1.5ST', type: '하단마감재', quantity: 8, unit: 'EA', unitPrice: 18000, supplyAmount: 144000, taxAmount: 14400, totalAmount: 158400, sortOrder: 5 }, ]; -// QMS용 제품검사 성적서 Mock 데이터 -const QMS_MOCK_REPORT_DATA: InspectionReportDocumentType = { - documentNumber: 'RPT-KD-SS-2024-530', - createdDate: '2024-09-24', - approvalLine: [ - { role: '작성', name: '김검사', department: '품질관리부' }, - { role: '승인', name: '박승인', department: '품질관리부' }, - ], - productName: '방화스크린', - productLotNo: 'KD-SS-240924-19', - productCode: 'WY-SC780', - lotSize: '8', - client: '삼성물산(주)', - inspectionDate: '2024-09-26', - siteName: '강남 아파트 단지', - inspector: '김검사', - inspectionItems: mockReportInspectionItems, - specialNotes: '', - finalJudgment: '합격', -}; +// FQC 문서 API 응답 → FqcTemplate 변환 +function transformFqcApiToTemplate(apiTemplate: Record): FqcTemplate { + const t = apiTemplate as { + id: number; name: string; category: string; title: string | null; + approval_lines: { id: number; name: string; department: string; sort_order: number }[]; + basic_fields: { id: number; label: string; field_key: string; field_type: string; default_value: string | null; is_required: boolean; sort_order: number }[]; + sections: { id: number; name: string; title: string | null; description: string | null; image_path: string | null; sort_order: number; + items: { id: number; section_id: number; item_name: string; standard: string | null; tolerance: string | null; measurement_type: string; frequency: string; sort_order: number; category: string; method: string }[]; + }[]; + columns: { id: number; label: string; column_type: string; width: string | null; group_name: string | null; sort_order: number }[]; + }; + return { + id: t.id, name: t.name, category: t.category, title: t.title, + approvalLines: (t.approval_lines || []).map(a => ({ id: a.id, name: a.name, department: a.department, sortOrder: a.sort_order })), + basicFields: (t.basic_fields || []).map(f => ({ id: f.id, label: f.label, fieldKey: f.field_key, fieldType: f.field_type, defaultValue: f.default_value, isRequired: f.is_required, sortOrder: f.sort_order })), + sections: (t.sections || []).map(s => ({ + id: s.id, name: s.name, title: s.title, description: s.description, imagePath: s.image_path, sortOrder: s.sort_order, + items: (s.items || []).map(i => ({ id: i.id, sectionId: i.section_id, itemName: i.item_name, standard: i.standard, tolerance: i.tolerance, measurementType: i.measurement_type, frequency: i.frequency, sortOrder: i.sort_order, category: i.category || '', method: i.method || '' })), + })), + columns: (t.columns || []).map(c => ({ id: c.id, label: c.label, columnType: c.column_type, width: c.width, groupName: c.group_name ?? null, sortOrder: c.sort_order })), + }; +} + +function transformFqcApiToData(apiData: { section_id: number | null; column_id: number | null; row_index: number; field_key: string; field_value: string | null }[]): FqcDocumentData[] { + return (apiData || []).map(d => ({ sectionId: d.section_id, columnId: d.column_id, rowIndex: d.row_index, fieldKey: d.field_key, fieldValue: d.field_value })); +} // QMS용 작업일지 Mock WorkOrder 생성 const createQmsMockWorkOrder = (subType?: string): WorkOrder => ({ @@ -270,10 +282,24 @@ export const InspectionModal = ({ const [isLoadingTemplate, setIsLoadingTemplate] = useState(false); const [templateError, setTemplateError] = useState(null); + // 작업일지/중간검사용 WorkOrder 상태 + const [workOrderData, setWorkOrderData] = useState(null); + const [isLoadingWorkOrder, setIsLoadingWorkOrder] = useState(false); + const [workOrderError, setWorkOrderError] = useState(null); + // 수입검사 저장용 ref/상태 const importDocRef = useRef(null); const [isSaving, setIsSaving] = useState(false); + // 제품검사 성적서 FQC 상태 + const [fqcTemplate, setFqcTemplate] = useState(null); + const [fqcData, setFqcData] = useState([]); + const [fqcDocumentNo, setFqcDocumentNo] = useState(''); + const [isLoadingFqc, setIsLoadingFqc] = useState(false); + const [fqcError, setFqcError] = useState(null); + // 레거시 inspection_data 기반 제품검사 성적서 + const [legacyReportData, setLegacyReportData] = useState(null); + // 수입검사 템플릿 로드 (모달 열릴 때) useEffect(() => { // itemId가 있으면 실제 API로 조회, 없으면 itemName/specification으로 mock 조회 @@ -285,9 +311,53 @@ export const InspectionModal = ({ setImportTemplate(null); setImportInitialValues(undefined); setTemplateError(null); + setFqcTemplate(null); + setFqcData([]); + setFqcDocumentNo(''); + setFqcError(null); + setLegacyReportData(null); + setWorkOrderData(null); + setWorkOrderError(null); } }, [isOpen, doc?.type, itemId, itemName, specification]); + // 작업일지/중간검사 WorkOrder 로드 (모달 열릴 때) + // log: documentItem.id === work_order_id, report: documentItem.workOrderId로 전달 + useEffect(() => { + if (isOpen && (doc?.type === 'log' || doc?.type === 'report')) { + const woId = documentItem?.workOrderId || (doc?.type === 'log' ? Number(documentItem?.id) : null); + if (woId) { + loadWorkOrderData(woId); + } + } + }, [isOpen, doc?.type, documentItem?.workOrderId, documentItem?.id]); + + // 제품검사 성적서 FQC 로드 (모달 열릴 때) + useEffect(() => { + if (isOpen && doc?.type === 'product' && documentItem?.id) { + loadFqcDocument(documentItem.id); + } + }, [isOpen, doc?.type, documentItem?.id]); + + const loadWorkOrderData = async (workOrderId: number) => { + setIsLoadingWorkOrder(true); + setWorkOrderError(null); + + try { + const result = await getWorkOrderById(String(workOrderId)); + if (result.success && result.data) { + setWorkOrderData(result.data); + } else { + setWorkOrderError(result.error || '작업지시 데이터를 불러올 수 없습니다.'); + } + } catch (error) { + console.error('[InspectionModal] loadWorkOrderData error:', error); + setWorkOrderError('작업지시 데이터 로드 중 오류가 발생했습니다.'); + } finally { + setIsLoadingWorkOrder(false); + } + }; + const loadInspectionTemplate = async () => { // itemId가 있으면 실제 API 호출, 없으면 itemName/specification 필요 if (!itemId && (!itemName || !specification)) return; @@ -330,6 +400,73 @@ export const InspectionModal = ({ } }; + // 제품검사 성적서 문서 로드 (FQC 우선, inspection_data fallback) + const loadFqcDocument = async (locationId: string) => { + setIsLoadingFqc(true); + setFqcError(null); + setLegacyReportData(null); + + try { + const result = await getDocumentDetail('product', locationId); + if (result.success && result.data) { + const data = result.data as { + document_id: number | null; + inspection_status: string | null; + inspection_data: ProductInspectionData | null; + floor_code: string | null; + symbol_code: string | null; + fqc_document?: { + document_no: string; + template: Record; + data: { section_id: number | null; column_id: number | null; row_index: number; field_key: string; field_value: string | null }[]; + }; + }; + + if (data.fqc_document) { + // FQC 문서가 있는 경우 + setFqcTemplate(transformFqcApiToTemplate(data.fqc_document.template)); + setFqcData(transformFqcApiToData(data.fqc_document.data)); + setFqcDocumentNo(data.fqc_document.document_no || ''); + } else if (data.inspection_data && data.inspection_status === 'completed') { + // FQC 없지만 inspection_data가 있는 경우 → 레거시 리포트 생성 + const inspData = data.inspection_data; + const mappedItems = mapInspectionDataToItems(mockReportInspectionItems, inspData); + const locationLabel = [data.floor_code, data.symbol_code].filter(Boolean).join(' '); + + setLegacyReportData({ + documentNumber: '', + createdDate: '', + approvalLine: [ + { role: '작성', name: '', department: '' }, + { role: '승인', name: '', department: '' }, + ], + productName: inspData.productName || '', + productLotNo: '', + productCode: '', + lotSize: '1', + client: '', + inspectionDate: '', + siteName: locationLabel, + inspector: '', + productImages: inspData.productImages || [], + inspectionItems: mappedItems, + specialNotes: inspData.specialNotes || '', + finalJudgment: '합격', + }); + } else { + setFqcError('제품검사 성적서 문서가 아직 생성되지 않았습니다.'); + } + } else { + setFqcError(result.error || '제품검사 성적서 조회에 실패했습니다.'); + } + } catch (error) { + console.error('[InspectionModal] loadFqcDocument error:', error); + setFqcError('제품검사 성적서 로드 중 오류가 발생했습니다.'); + } finally { + setIsLoadingFqc(false); + } + }; + // 수입검사 저장 핸들러 (hooks는 early return 전에 호출해야 함) const handleImportSave = useCallback(async () => { if (!importDocRef.current) return; @@ -360,39 +497,63 @@ export const InspectionModal = ({ const handleQualityFileDelete = () => { }; + // 작업일지/중간검사 공통: WorkOrder 데이터 로딩 상태 처리 + const renderWorkOrderLoading = () => { + if (isLoadingWorkOrder) { + return ( +
+ +

작업지시 데이터를 불러오는 중...

+
+ ); + } + if (workOrderError) { + return loadWorkOrderData(documentItem.workOrderId!) : undefined} />; + } + return null; + }; + // 작업일지 공정별 렌더링 const renderWorkLogDocument = () => { + const loadingEl = renderWorkOrderLoading(); + if (loadingEl) return loadingEl; + const subType = documentItem?.subType; - const mockOrder = createQmsMockWorkOrder(subType); + // 실제 WorkOrder 데이터 사용, 없으면 fallback mock + const orderData = workOrderData || createQmsMockWorkOrder(subType); switch (subType) { case 'screen': - return ; + return ; case 'slat': - return ; + return ; case 'bending': - return ; + return ; default: - // subType 미지정 시 스크린 기본 - return ; + return ; } }; // 중간검사 성적서 서브타입에 따른 렌더링 (신규 버전 통일) const renderReportDocument = () => { + const loadingEl = renderWorkOrderLoading(); + if (loadingEl) return loadingEl; + const subType = documentItem?.subType; - const mockOrder = createQmsMockWorkOrder(subType || 'screen'); + // 실제 WorkOrder 데이터 사용, 없으면 fallback mock + const orderData = workOrderData || createQmsMockWorkOrder(subType || 'screen'); + switch (subType) { case 'screen': - return ; + return ; case 'bending': - return ; + return ; case 'slat': - return ; + return ; case 'jointbar': return ; default: - return ; + return ; } }; @@ -418,6 +579,36 @@ export const InspectionModal = ({ ); }; + // 제품검사 성적서 렌더링 (FQC 우선, inspection_data fallback) + const renderProductDocument = () => { + if (isLoadingFqc) { + return ; + } + + if (fqcError) { + return loadFqcDocument(documentItem.id) : undefined} />; + } + + // FQC 문서 기반 렌더링 + if (fqcTemplate) { + return ( + + ); + } + + // 레거시 inspection_data 기반 렌더링 + if (legacyReportData) { + return ; + } + + return ; + }; + // 문서 타입에 따른 컨텐츠 렌더링 const renderDocumentContent = () => { switch (doc.type) { @@ -453,7 +644,7 @@ export const InspectionModal = ({ case 'import': return renderImportInspectionDocument(); case 'product': - return ; + return renderProductDocument(); case 'report': return renderReportDocument(); case 'quality': diff --git a/src/components/quality/InspectionManagement/mockData.ts b/src/components/quality/InspectionManagement/mockData.ts index c6a9dea7..d6333cc3 100644 --- a/src/components/quality/InspectionManagement/mockData.ts +++ b/src/components/quality/InspectionManagement/mockData.ts @@ -477,14 +477,14 @@ export const mockReportInspectionItems: ReportInspectionItem[] = [ ]; /** pass/fail → 적합/부적합 변환 */ -const convertJudgment = (value: 'pass' | 'fail' | null): '적합' | '부적합' | undefined => { +export const convertJudgment = (value: 'pass' | 'fail' | null): '적합' | '부적합' | undefined => { if (value === 'pass') return '적합'; if (value === 'fail') return '부적합'; return undefined; }; /** 검사 데이터를 검사항목에 매핑 */ -const mapInspectionDataToItems = ( +export const mapInspectionDataToItems = ( items: ReportInspectionItem[], inspectionData?: ProductInspectionData ): ReportInspectionItem[] => {