From 04f2a8a74cba1f9a1e7a8623e2bc0d041c3c69ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Sat, 7 Mar 2026 03:02:59 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[=EB=AC=B8=EC=84=9C=EC=8A=A4=EB=83=85?= =?UTF-8?q?=EC=83=B7]=20Lazy=20Snapshot=20+=20rendered=5Fhtml=20=EC=BA=A1?= =?UTF-8?q?=EC=B2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - capture-rendered-html 유틸 추가 - 검사성적서/작업일지 저장 시 HTML 스냅샷 캡처 - 중간검사/작업일지 조회 시 자동 스냅샷 - DocumentViewer 스냅샷 출력 지원 --- .../document-system/viewer/DocumentViewer.tsx | 3 +- src/lib/utils/capture-rendered-html.tsx | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/lib/utils/capture-rendered-html.tsx diff --git a/src/components/document-system/viewer/DocumentViewer.tsx b/src/components/document-system/viewer/DocumentViewer.tsx index 1ebc5757..f382e735 100644 --- a/src/components/document-system/viewer/DocumentViewer.tsx +++ b/src/components/document-system/viewer/DocumentViewer.tsx @@ -198,7 +198,8 @@ export function DocumentViewer({ } } } catch { - // 변환 실패 시 원본 src 유지 + // 변환 실패 시 빈 이미지로 대체 (Puppeteer에서 proxy URL 요청 방지) + clonedImg.setAttribute('src', 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'); } } }) 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; + } +}