"use client"; import React, { useState, useEffect, useRef, SetStateAction, Dispatch, } from "react"; import { WebViewerInstance } from "@pdftron/webviewer"; import { Loader2 } from "lucide-react"; import { toast } from "sonner"; interface BasicContractTemplateViewerProps { templateId?: number; filePath?: string; instance: WebViewerInstance | null; setInstance: Dispatch>; onSignatureFieldFound?: (searchText: string) => void; // 서명란 발견 시 콜백 onLoadError?: (error: Error) => void; // 파일 로드 실패 시 콜백 } // 서명란 텍스트 찾기 함수 (변수 추가를 위해 사용) - 모든 서명란 텍스트를 찾아서 반환 const findSignatureFieldLocation = async ( instance: WebViewerInstance | null ): Promise => { if (!instance?.Core?.documentViewer) { return []; } try { const { documentViewer } = instance.Core; const doc = documentViewer.getDocument(); if (!doc) return []; // 검색할 텍스트 목록 (협력업체와 구매자 모두) const searchTexts = ['협력업체_서명란', '삼성중공업_서명란']; const pageCount = documentViewer.getPageCount(); const foundTexts: string[] = []; for (const searchText of searchTexts) { for (let pageNumber = 1; pageNumber <= pageCount; pageNumber++) { try { const pageText = await doc.loadPageText(pageNumber); if (!pageText) continue; const startIndex = pageText.indexOf(searchText); if (startIndex === -1) continue; const endIndex = startIndex + searchText.length; // 텍스트 위치 가져오기 const quads = await doc.getTextPosition(pageNumber, startIndex, endIndex); if (!quads || quads.length === 0) continue; // 찾은 서명란 텍스트를 배열에 추가 (중복 제거) if (!foundTexts.includes(searchText)) { foundTexts.push(searchText); } break; // 해당 텍스트를 찾았으면 다음 텍스트로 이동 } catch (error) { console.warn(`페이지 ${pageNumber}에서 ${searchText} 검색 실패:`, error); continue; } } } return foundTexts; } catch (error) { console.error("서명란 위치 찾기 실패:", error); return []; } }; export function BasicContractTemplateViewer({ templateId, filePath, instance, setInstance, onSignatureFieldFound, onLoadError, }: BasicContractTemplateViewerProps) { const [fileLoading, setFileLoading] = useState(true); const [hasError, setHasError] = useState(false); const viewer = useRef(null); const initialized = useRef(false); const isCancelled = useRef(false); // WebViewer 초기화 (기존 SignViewer와 완전히 동일) useEffect(() => { if (!initialized.current && viewer.current) { initialized.current = true; isCancelled.current = false; requestAnimationFrame(() => { if (viewer.current) { import("@pdftron/webviewer").then(({ default: WebViewer }) => { if (isCancelled.current) { console.log("📛 WebViewer 초기화 취소됨"); return; } // viewerElement이 확실히 존재함을 확인 const viewerElement = viewer.current; if (!viewerElement) return; WebViewer( { path: "/pdftronWeb", licenseKey: process.env.NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY, fullAPI: true, // 한글 입력 지원을 위한 설정 enableOfficeEditing: true, // Office 편집 모드에서 IME 지원 필요 }, viewerElement ).then((instance: WebViewerInstance) => { setInstance(instance); setFileLoading(false); try { const { disableElements, enableElements, setToolbarGroup } = instance.UI; // 편집에 필요한 요소들 활성화 enableElements([ "toolbarGroup-Edit", "toolbarGroup-Insert", "textSelectButton", "panToolButton" ]); // 불필요한 요소들만 비활성화 disableElements([ "toolbarGroup-Annotate", // 주석 도구 "toolbarGroup-Shapes", // 도형 도구 "toolbarGroup-Forms", // 폼 도구 "signatureToolButton", // 서명 도구 "stampToolButton", // 스탬프 도구 "rubberStampToolButton", // 러버스탬프 도구 "freeHandToolButton", // 자유 그리기 "stickyToolButton", // 스티키 노트 "calloutToolButton", // 콜아웃 ]); // 편집 툴바 설정 setToolbarGroup("toolbarGroup-Edit"); // 한글 입력 지원을 위한 추가 설정 const iframeWindow = instance.UI.iframeWindow; if (iframeWindow && iframeWindow.document) { // IME 지원 활성화 const documentBody = iframeWindow.document.body; if (documentBody) { (documentBody.style as any).imeMode = 'active'; documentBody.setAttribute('lang', 'ko-KR'); } // 키보드 이벤트 리스너 추가 (한글 입력 감지) iframeWindow.document.addEventListener('compositionstart', (e) => { console.log('🇰🇷 한글 입력 시작'); }); iframeWindow.document.addEventListener('compositionend', (e) => { console.log('🇰🇷 한글 입력 완료:', e.data); }); } console.log("📝 WebViewer 한글 지원 초기화 완료"); } catch (uiError) { console.warn("⚠️ UI 설정 중 오류 (무시됨):", uiError); } }).catch((error) => { console.error("❌ WebViewer 초기화 실패:", error); setFileLoading(false); toast.error("뷰어 초기화에 실패했습니다."); }); }); } }); } return () => { if (instance) { instance.UI.dispose(); } isCancelled.current = true; setTimeout(() => cleanupHtmlStyle(), 500); }; }, []); // 문서 로드 (기존 SignViewer와 동일) useEffect(() => { if (!instance || !filePath) return; loadDocument(instance, filePath); }, [instance, filePath]); // 문서 로드 후 서명란 텍스트 찾기 (변수 추가를 위해) useEffect(() => { if (!instance) return; const { documentViewer } = instance.Core; const handleDocumentLoaded = async () => { // 문서 로드 완료 후 서명란 텍스트 찾기 setTimeout(async () => { const foundTexts = await findSignatureFieldLocation(instance); if (foundTexts.length > 0 && onSignatureFieldFound) { console.log("✅ 서명란 텍스트 발견:", foundTexts); // 모든 찾은 서명란 텍스트에 대해 콜백 호출 foundTexts.forEach(searchText => { onSignatureFieldFound(searchText); }); } }, 2000); }; documentViewer.addEventListener('documentLoaded', handleDocumentLoaded); // 이미 문서가 로드되어 있다면 즉시 실행 if (documentViewer.getDocument()) { setTimeout(async () => { const foundTexts = await findSignatureFieldLocation(instance); if (foundTexts.length > 0 && onSignatureFieldFound) { // 모든 찾은 서명란 텍스트에 대해 콜백 호출 foundTexts.forEach(searchText => { onSignatureFieldFound(searchText); }); } }, 2000); } return () => { documentViewer.removeEventListener('documentLoaded', handleDocumentLoaded); }; }, [instance, onSignatureFieldFound]); // 한글 지원 Office 문서 로드 const loadDocument = async (instance: WebViewerInstance, documentPath: string) => { setFileLoading(true); setHasError(false); try { // 절대 URL로 변환 const fullPath = documentPath.startsWith('http') ? documentPath : `${window.location.origin}${documentPath}`; // 파일명 추출 const fileName = documentPath.split('/').pop() || 'document.docx'; console.log("📄 한글 지원 Office 문서 로드 시작:", fullPath); console.log("📎 파일명:", fileName); // 파일 존재 여부 먼저 확인 try { const response = await fetch(fullPath, { method: 'HEAD' }); if (!response.ok) { throw new Error(`파일을 찾을 수 없습니다 (${response.status})`); } } catch (fetchError) { const error = new Error('파일이 존재하지 않거나 접근할 수 없습니다.'); setHasError(true); if (onLoadError) { onLoadError(error); } throw error; } // PDFTron 공식 방법: instance.UI.loadDocument() + 한글 지원 옵션 await instance.UI.loadDocument(fullPath, { filename: fileName, enableOfficeEditing: true, }); // 문서 로드 후 한글 입력 환경 설정 setTimeout(() => { try { const iframeWindow = instance.UI.iframeWindow; if (iframeWindow && iframeWindow.document) { // Office 편집기 컨테이너 찾기 const officeContainer = iframeWindow.document.querySelector('[data-office-editor]') || iframeWindow.document.querySelector('.office-editor') || iframeWindow.document.body; if (officeContainer && officeContainer instanceof HTMLElement) { // 한글 입력 최적화 설정 officeContainer.style.setProperty('ime-mode', 'active'); officeContainer.setAttribute('lang', 'ko-KR'); officeContainer.setAttribute('inputmode', 'text'); console.log("🇰🇷 한글 입력 환경 설정 완료"); } } } catch (setupError) { console.warn("⚠️ 한글 입력 환경 설정 실패:", setupError); } }, 1000); console.log("✅ 한글 지원 Office 편집 모드로 문서 로드 완료"); toast.success("Office 편집 모드가 활성화되었습니다.", { description: "한글 입력이 안 될 경우 외부에서 작성 후 복사-붙여넣기를 사용하세요." }); } catch (err) { console.error("❌ Office 문서 로딩 중 오류:", err); const errorMessage = err instanceof Error ? err.message : '알 수 없는 오류'; setHasError(true); if (onLoadError) { onLoadError(err instanceof Error ? err : new Error(errorMessage)); } toast.error(`Office 문서 로드 실패: ${errorMessage}`); } finally { setFileLoading(false); } }; // 기존 SignViewer와 동일한 렌더링 (확대 문제 해결) return (
{fileLoading && !hasError && (

문서 로딩 중...

)}
); } // WebViewer 정리 함수 (기존과 동일) const cleanupHtmlStyle = () => { // iframe 스타일 정리 (WebViewer가 추가한 스타일) const elements = document.querySelectorAll('.Document_container'); elements.forEach((elem) => { elem.remove(); }); };