"use client"; import React, { useState, useEffect, useRef, SetStateAction, Dispatch, } from "react"; import { WebViewerInstance } from "@pdftron/webviewer"; import { Loader2, Target } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; interface BasicContractTemplateViewerProps { templateId?: number; filePath?: string; instance: WebViewerInstance | null; setInstance: Dispatch>; } // 서명란 위치 정보 인터페이스 interface SignatureFieldLocation { pageNumber: number; x: number; y: number; searchText: string; } // 서명란으로 이동하는 함수 const navigateToSignatureField = async ( instance: WebViewerInstance | null, location: SignatureFieldLocation | null ) => { if (!instance || !location) { toast.error("서명란 위치를 찾을 수 없습니다."); return; } try { const { documentViewer } = instance.Core; // 해당 페이지로 이동 documentViewer.setCurrentPage(location.pageNumber, true); // 페이지 로드 후 알림 setTimeout(() => { toast.success(`서명란으로 이동했습니다. (페이지 ${location.pageNumber})`, { description: `"${location.searchText}" 텍스트를 찾아주세요.` }); }, 500); } catch (error) { console.error("서명란으로 이동 실패:", error); toast.error("서명란으로 이동하는데 실패했습니다."); } }; // 서명란 텍스트 찾기 함수 const findSignatureFieldLocation = async ( instance: WebViewerInstance | null ): Promise => { if (!instance?.Core?.documentViewer) { return null; } try { const { documentViewer } = instance.Core; const doc = documentViewer.getDocument(); if (!doc) return null; // 검색할 텍스트 목록 (협력업체와 구매자 모두) const searchTexts = ['협력업체_서명란', '삼성중공업_서명란']; const pageCount = documentViewer.getPageCount(); 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; const q = quads[0] as any; const x = Math.min(q.x1, q.x2, q.x3, q.x4); const y = Math.min(q.y1, q.y2, q.y3, q.y4); return { pageNumber, x, y, searchText }; } catch (error) { console.warn(`페이지 ${pageNumber}에서 ${searchText} 검색 실패:`, error); continue; } } } return null; } catch (error) { console.error("서명란 위치 찾기 실패:", error); return null; } }; export function BasicContractTemplateViewer({ templateId, filePath, instance, setInstance, }: BasicContractTemplateViewerProps) { const [fileLoading, setFileLoading] = useState(true); const [signatureLocation, setSignatureLocation] = useState(null); 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 location = await findSignatureFieldLocation(instance); if (location) { setSignatureLocation(location); console.log("✅ 서명란 위치 발견:", location); } }, 2000); }; documentViewer.addEventListener('documentLoaded', handleDocumentLoaded); // 이미 문서가 로드되어 있다면 즉시 실행 if (documentViewer.getDocument()) { setTimeout(async () => { const location = await findSignatureFieldLocation(instance); if (location) { setSignatureLocation(location); } }, 2000); } return () => { documentViewer.removeEventListener('documentLoaded', handleDocumentLoaded); }; }, [instance]); // 한글 지원 Office 문서 로드 const loadDocument = async (instance: WebViewerInstance, documentPath: string) => { setFileLoading(true); 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); // 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); toast.error(`Office 문서 로드 실패: ${err instanceof Error ? err.message : '알 수 없는 오류'}`); } finally { setFileLoading(false); } }; // 기존 SignViewer와 동일한 렌더링 (확대 문제 해결) return (
{/* 서명란으로 이동 버튼 */} {signatureLocation && !fileLoading && (
)}
{fileLoading && (

문서 로딩 중...

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