From 72a2a3e9a9d49219185b2477cf265740e1bec0d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Fri, 6 Mar 2026 20:35:30 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[=EB=AC=B8=EC=84=9C=EC=8A=A4=EB=83=85?= =?UTF-8?q?=EC=83=B7]=20=EC=BA=A1=EC=B2=98=20=EB=B0=A9=EC=8B=9D=20?= =?UTF-8?q?=EB=B3=B4=EC=A0=95=20-=20=EC=98=A4=ED=94=84=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=B0=20=EC=84=B1=EC=A0=81=EC=84=9C=20=EB=A0=8C=EB=8D=94?= =?UTF-8?q?=EB=A7=81,=20readOnly=20=EC=9E=90=EB=8F=99=20=EC=BA=A1=EC=B2=98?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ImportInspectionInputModal: 입력폼 캡처 → 오프스크린 성적서 문서 렌더링으로 변경 - InspectionReportModal: readOnly 자동 캡처 useEffect 제거 (불필요 PUT 방지) - capture-rendered-html.tsx: 오프스크린 렌더링 유틸리티 신규 추가 --- .../ImportInspectionInputModal.tsx | 41 +++++++++++++++---- .../documents/InspectionReportModal.tsx | 15 ------- src/lib/utils/capture-rendered-html.tsx | 36 ++++++++++++++++ 3 files changed, 70 insertions(+), 22 deletions(-) create mode 100644 src/lib/utils/capture-rendered-html.tsx diff --git a/src/components/material/ReceivingManagement/ImportInspectionInputModal.tsx b/src/components/material/ReceivingManagement/ImportInspectionInputModal.tsx index ae817459..ad939d76 100644 --- a/src/components/material/ReceivingManagement/ImportInspectionInputModal.tsx +++ b/src/components/material/ReceivingManagement/ImportInspectionInputModal.tsx @@ -12,7 +12,7 @@ * - saveInspectionData()로 저장 */ -import { useState, useEffect, useMemo, useCallback, useRef } from 'react'; +import { useState, useEffect, useMemo, useCallback } from 'react'; import { Dialog, DialogContent, @@ -35,6 +35,9 @@ import { type InspectionTemplateResponse, type DocumentResolveResponse, } from './actions'; +import { captureRenderedHtml } from '@/lib/utils/capture-rendered-html'; +import { ImportInspectionDocument } from '@/app/[locale]/(protected)/quality/qms/components/documents/ImportInspectionDocument'; +import type { ImportInspectionTemplate, InspectionItemValue } from '@/app/[locale]/(protected)/quality/qms/components/documents/ImportInspectionDocument'; // ===== Props ===== interface ImportInspectionInputModalProps { @@ -216,9 +219,6 @@ export function ImportInspectionInputModal({ receivingId, onSave, }: ImportInspectionInputModalProps) { - // HTML 스냅샷 캡처 ref (MNG 출력용) - const contentWrapperRef = useRef(null); - // Template const [template, setTemplate] = useState(null); const [resolveData, setResolveData] = useState(null); @@ -639,8 +639,35 @@ export function ImportInspectionInputModal({ })), ]; - // 4. HTML 스냅샷 캡처 (MNG 출력용) - const renderedHtml = contentWrapperRef.current?.innerHTML || undefined; + // 4. 성적서 문서를 오프스크린 렌더링하여 HTML 스냅샷 캡처 (MNG 출력용) + let renderedHtml: string | undefined; + try { + // 현재 입력값을 ImportInspectionDocument의 initialValues 형식으로 변환 + const docValues: InspectionItemValue[] = template.inspectionItems + .filter(i => i.isFirstInItem !== false) + .map(item => ({ + itemId: item.id, + measurements: Array.from({ length: item.measurementCount }, (_, n) => { + if (item.measurementType === 'okng') { + const v = okngValues[item.id]?.[n]; + return v === 'ok' ? ('OK' as const) : v === 'ng' ? ('NG' as const) : null; + } + const v = measurements[item.id]?.[n]; + return v ? Number(v) : null; + }), + result: getItemResult(item) === 'ok' ? ('OK' as const) : getItemResult(item) === 'ng' ? ('NG' as const) : null, + })); + // 성적서 문서 컴포넌트를 오프스크린에서 렌더링 + renderedHtml = captureRenderedHtml( + + ); + } catch { + // 캡처 실패 시 무시 — rendered_html 없이 저장 진행 + } // 5. 저장 API 호출 const result = await saveInspectionData({ @@ -741,7 +768,7 @@ export function ImportInspectionInputModal({ 검사 템플릿을 불러올 수 없습니다. ) : ( -
+
{/* 기본 정보 (읽기전용 인풋 스타일) */}
diff --git a/src/components/production/WorkOrders/documents/InspectionReportModal.tsx b/src/components/production/WorkOrders/documents/InspectionReportModal.tsx index 6aa94651..8593cf84 100644 --- a/src/components/production/WorkOrders/documents/InspectionReportModal.tsx +++ b/src/components/production/WorkOrders/documents/InspectionReportModal.tsx @@ -469,21 +469,6 @@ export function InspectionReportModal({ } }; - // readOnly 모드에서도 콘텐츠 렌더 후 rendered_html 자동 캡처 (MNG 출력용) - useEffect(() => { - if (!open || !order || isLoading || !readOnly || !workOrderId) return; - // 렌더링 완료 후 캡처 - const timer = setTimeout(() => { - const html = contentWrapperRef.current?.innerHTML; - if (html && html.length > 100) { - saveInspectionDocument(workOrderId, { rendered_html: html }).catch(() => { - // 자동 캡처 실패는 무시 (template 미연결 시 404 가능) - }); - } - }, 500); - return () => clearTimeout(timer); - }, [open, order, isLoading, readOnly, workOrderId]); - // 결재 상신 가능 여부: 저장된 DRAFT 문서가 있을 때 const canSubmitForApproval = savedDocumentId != null && savedDocumentStatus === 'DRAFT'; diff --git a/src/lib/utils/capture-rendered-html.tsx b/src/lib/utils/capture-rendered-html.tsx new file mode 100644 index 00000000..7a25ff93 --- /dev/null +++ b/src/lib/utils/capture-rendered-html.tsx @@ -0,0 +1,36 @@ +/** + * 오프스크린 렌더링으로 React 컴포넌트의 HTML을 캡처하는 유틸리티 + * + * 사용 사례: 입력 화면에서 저장 시, 해당 데이터의 "문서 뷰" HTML을 캡처하여 + * rendered_html로 저장 (MNG 출력용) + */ + +import { createRoot } from 'react-dom/client'; +import { flushSync } from 'react-dom'; + +/** + * React 엘리먼트를 오프스크린에서 렌더링하고 innerHTML을 캡처합니다. + * + * @param element - 렌더링할 React 엘리먼트 (예: ) + * @returns 캡처된 HTML 문자열, 실패 시 undefined + */ +export function captureRenderedHtml(element: React.ReactElement): string | undefined { + try { + const container = document.createElement('div'); + container.style.cssText = 'position:fixed;left:-9999px;top:0;visibility:hidden;width:210mm;'; + document.body.appendChild(container); + + const root = createRoot(container); + flushSync(() => { + root.render(element); + }); + + const html = container.innerHTML; + root.unmount(); + document.body.removeChild(container); + + return html && html.length > 50 ? html : undefined; + } catch { + return undefined; + } +}