From cc2dc9c162ceadfc41f8fa1fcd4c82341c33af7c Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Fri, 7 Nov 2025 02:59:13 +0000
Subject: (임수민) 기본계약서 수정하기 리비전 수정, 서명란 바로가기 버튼 추가
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../template/basic-contract-template-viewer.tsx    | 159 +++++++++++++++++++--
 1 file changed, 148 insertions(+), 11 deletions(-)

(limited to 'lib/basic-contract/template/basic-contract-template-viewer.tsx')

diff --git a/lib/basic-contract/template/basic-contract-template-viewer.tsx b/lib/basic-contract/template/basic-contract-template-viewer.tsx
index 59989e46..018db3a0 100644
--- a/lib/basic-contract/template/basic-contract-template-viewer.tsx
+++ b/lib/basic-contract/template/basic-contract-template-viewer.tsx
@@ -8,8 +8,9 @@ import React, {
   Dispatch,
 } from "react";
 import { WebViewerInstance } from "@pdftron/webviewer";
-import { Loader2 } from "lucide-react";
+import { Loader2, Target } from "lucide-react";
 import { toast } from "sonner";
+import { Button } from "@/components/ui/button";
 
 interface BasicContractTemplateViewerProps {
   templateId?: number;
@@ -18,6 +19,98 @@ interface BasicContractTemplateViewerProps {
   setInstance: Dispatch<SetStateAction<WebViewerInstance | null>>;
 }
 
+// 서명란 위치 정보 인터페이스
+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<SignatureFieldLocation | null> => {
+  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,
@@ -25,6 +118,7 @@ export function BasicContractTemplateViewer({
   setInstance,
 }: BasicContractTemplateViewerProps) {
   const [fileLoading, setFileLoading] = useState<boolean>(true);
+  const [signatureLocation, setSignatureLocation] = useState<SignatureFieldLocation | null>(null);
   const viewer = useRef<HTMLDivElement>(null);
   const initialized = useRef(false);
   const isCancelled = useRef(false);
@@ -54,7 +148,6 @@ export function BasicContractTemplateViewer({
                 fullAPI: true,
                 // 한글 입력 지원을 위한 설정
                 enableOfficeEditing: true, // Office 편집 모드에서 IME 지원 필요
-                l: "ko", // 한국어 로케일 설정
               },
               viewerElement
             ).then((instance: WebViewerInstance) => {
@@ -94,7 +187,7 @@ export function BasicContractTemplateViewer({
                   // IME 지원 활성화
                   const documentBody = iframeWindow.document.body;
                   if (documentBody) {
-                    documentBody.style.imeMode = 'active';
+                    (documentBody.style as any).imeMode = 'active';
                     documentBody.setAttribute('lang', 'ko-KR');
                   }
                   
@@ -138,6 +231,40 @@ export function BasicContractTemplateViewer({
     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);
@@ -157,11 +284,6 @@ export function BasicContractTemplateViewer({
       await instance.UI.loadDocument(fullPath, {
         filename: fileName,
         enableOfficeEditing: true,
-        // 한글 입력 지원을 위한 추가 옵션
-        officeOptions: {
-          locale: 'ko-KR',
-          enableIME: true,
-        }
       });
       
       // 문서 로드 후 한글 입력 환경 설정
@@ -174,9 +296,9 @@ export function BasicContractTemplateViewer({
                                   iframeWindow.document.querySelector('.office-editor') ||
                                   iframeWindow.document.body;
             
-            if (officeContainer) {
+            if (officeContainer && officeContainer instanceof HTMLElement) {
               // 한글 입력 최적화 설정
-              officeContainer.style.imeMode = 'active';
+              officeContainer.style.setProperty('ime-mode', 'active');
               officeContainer.setAttribute('lang', 'ko-KR');
               officeContainer.setAttribute('inputmode', 'text');
               
@@ -203,7 +325,22 @@ export function BasicContractTemplateViewer({
 
   // 기존 SignViewer와 동일한 렌더링 (확대 문제 해결)
   return (
-    <div className="relative w-full h-full overflow-hidden">
+    <div className="relative w-full h-full overflow-hidden flex flex-col">
+      {/* 서명란으로 이동 버튼 */}
+      {signatureLocation && !fileLoading && (
+        <div className="absolute top-2 right-2 z-20">
+          <Button
+            variant="outline"
+            size="sm"
+            className="h-7 px-2 text-xs bg-blue-50 hover:bg-blue-100 border-blue-200"
+            onClick={() => navigateToSignatureField(instance, signatureLocation)}
+          >
+            <Target className="h-3 w-3 mr-1" />
+            서명란으로 이동
+          </Button>
+        </div>
+      )}
+      
       <div 
         ref={viewer} 
         className="w-full h-full"
-- 
cgit v1.2.3

