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

---
 .../viewer/basic-contract-sign-viewer.tsx          | 153 ++++++++++++++++++---
 1 file changed, 131 insertions(+), 22 deletions(-)

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

diff --git a/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx b/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
index 246c5200..d1492fdb 100644
--- a/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
+++ b/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
@@ -66,16 +66,31 @@ interface SignaturePattern {
   height?: number;
 }
 
+// 서명 필드 위치 정보를 저장하는 인터페이스
+interface SignatureFieldLocation {
+  pageNumber: number;
+  x: number;
+  y: number;
+  fieldName: string;
+  searchText: string;
+}
+
 // 초간단 안전한 서명 필드 감지 클래스
 class AutoSignatureFieldDetector {
   private instance: WebViewerInstance;
   private mode: 'vendor' | 'buyer';
+  private location: SignatureFieldLocation | null = null; // 위치 정보 저장
 
   constructor(instance: WebViewerInstance, mode: 'vendor' | 'buyer' = 'vendor') {
     this.instance = instance;
     this.mode = mode;
   }
 
+  // 위치 정보 getter 추가
+  getLocation(): SignatureFieldLocation | null {
+    return this.location;
+  }
+
   async detectAndCreateSignatureFields(): Promise<string[]> {
     console.log(`🔍 텍스트 기반 서명 필드 감지 시작... (모드: ${this.mode})`);
 
@@ -149,13 +164,24 @@ class AutoSignatureFieldDetector {
         if (!quads || quads.length === 0) continue;
   
         // 첫 글자의 quad만 사용해 대략적인 위치 산출
-        const q = quads[0];
+        const q = quads[0] as any; // PDFTron의 Quad 타입
         const x = Math.min(q.x1, q.x2, q.x3, q.x4);
         const y = Math.min(q.y1, q.y2, q.y3, q.y4);
         const textHeight = Math.abs(q.y3 - q.y1);
   
         // 4) 서명 필드 생성
         const fieldName = `signature_at_text_${Date.now()}`;
+        const signatureY = y + textHeight + 5;
+        
+        // 위치 정보 저장
+        this.location = {
+          pageNumber,
+          x,
+          y: signatureY,
+          fieldName,
+          searchText
+        };
+        
         const flags = new Annotations.WidgetFlags();
         flags.set('Required', true);
   
@@ -165,7 +191,7 @@ class AutoSignatureFieldDetector {
         widget.setPageNumber(pageNumber);
         // 텍스트 바로 아래에 배치 (필요하면 오른쪽 배치로 바꿀 수 있음)
         widget.setX(x);
-        widget.setY(y + textHeight + 5);
+        widget.setY(signatureY);
         widget.setWidth(150);
         widget.setHeight(50);
   
@@ -194,6 +220,20 @@ class AutoSignatureFieldDetector {
     const h = documentViewer.getPageHeight(page) || 792;
 
     const fieldName = `simple_signature_${Date.now()}`;
+    
+    // 구매자 모드일 때는 왼쪽 하단으로 위치 설정
+    const x = this.mode === 'buyer' ? w * 0.1 : w * 0.7;
+    const y = h * 0.85;
+    
+    // 위치 정보 저장
+    this.location = {
+      pageNumber: page,
+      x,
+      y,
+      fieldName,
+      searchText: this.mode === 'buyer' ? '삼성중공업_서명란' : '협력업체_서명란'
+    };
+    
     const flags = new Annotations.WidgetFlags();
     flags.set('Required', true);
 
@@ -208,17 +248,8 @@ class AutoSignatureFieldDetector {
     });
 
     widget.setPageNumber(page);
-
-    // 구매자 모드일 때는 왼쪽 하단으로 위치 설정
-    if (this.mode === 'buyer') {
-      widget.setX(w * 0.1); // 왼쪽 (10%)
-      widget.setY(h * 0.85); // 하단 (85%)
-    } else {
-      // 협력업체 모드일 때는 기존처럼 오른쪽
-      widget.setX(w * 0.7); // 오른쪽 (70%)
-      widget.setY(h * 0.85); // 하단 (85%)
-    }
-
+    widget.setX(x);
+    widget.setY(y);
     widget.setWidth(150);
     widget.setHeight(50);
 
@@ -298,6 +329,7 @@ function useAutoSignatureFields(instance: WebViewerInstance | null, mode: 'vendo
   const [signatureFields, setSignatureFields] = useState<string[]>([]);
   const [isProcessing, setIsProcessing] = useState(false);
   const [error, setError] = useState<string | null>(null);
+  const [signatureLocation, setSignatureLocation] = useState<SignatureFieldLocation | null>(null); // 위치 정보 state 추가
 
   // 한 번만 실행되도록 보장하는 플래그들
   const processingRef = useRef(false);
@@ -377,6 +409,12 @@ function useAutoSignatureFields(instance: WebViewerInstance | null, mode: 'vendo
           const fields = await detector.detectAndCreateSignatureFields();
 
           setSignatureFields(fields);
+          
+          // 위치 정보 저장
+          const location = detector.getLocation();
+          if (location) {
+            setSignatureLocation(location);
+          }
 
           // 처리 완료 표시
           if (documentId) {
@@ -477,9 +515,58 @@ function useAutoSignatureFields(instance: WebViewerInstance | null, mode: 'vendo
     signatureFields,
     isProcessing,
     hasSignatureFields: signatureFields.length > 0,
-    error
+    error,
+    signatureLocation // 위치 정보 반환
   };
 }
+
+// 서명란으로 이동하는 함수 추가
+const navigateToSignatureField = (
+  instance: WebViewerInstance | null,
+  location: SignatureFieldLocation | null
+) => {
+  if (!instance || !location) {
+    toast.error("서명란 위치를 찾을 수 없습니다.");
+    return;
+  }
+
+  try {
+    const { documentViewer, annotationManager } = instance.Core;
+    
+    // 1. 해당 페이지로 이동
+    documentViewer.setCurrentPage(location.pageNumber);
+    
+    // 2. 서명 필드 위젯 찾기
+    const annotations = annotationManager.getAnnotationsList();
+    const signatureWidget = annotations.find(
+      (annot: any) => annot instanceof instance.Core.Annotations.SignatureWidgetAnnotation &&
+      annot.getField()?.name === location.fieldName
+    );
+
+    if (signatureWidget) {
+      // 위젯이 있으면 선택하여 자동으로 해당 위치로 스크롤
+      annotationManager.selectAnnotation(signatureWidget);
+      
+      // 추가로 스크롤 위치 조정
+      setTimeout(() => {
+        try {
+          // annotation을 다시 선택하여 스크롤 보장
+          annotationManager.selectAnnotation(signatureWidget);
+        } catch (scrollError) {
+          console.warn("스크롤 조정 실패:", scrollError);
+        }
+      }, 300);
+      
+      toast.success(`서명란으로 이동했습니다. (페이지 ${location.pageNumber})`);
+    } else {
+      // 위젯을 찾지 못한 경우 페이지만 이동
+      toast.info(`페이지 ${location.pageNumber}로 이동했습니다.`);
+    }
+  } catch (error) {
+    console.error("서명란으로 이동 실패:", error);
+    toast.error("서명란으로 이동하는데 실패했습니다.");
+  }
+};
 // XFDF 기반 서명 감지
 function useSignatureDetection(instance: WebViewerInstance | null, onSignatureComplete?: () => void) {
   const [hasValidSignature, setHasValidSignature] = useState(false);
@@ -644,8 +731,14 @@ export function BasicContractSignViewer({
   const [showDialog, setShowDialog] = useState(isOpen);
   const webViewerInstance = useRef<WebViewerInstance | null>(null);
 
-  // mode 전달
-  const { signatureFields, isProcessing: isAutoSignProcessing, hasSignatureFields, error: autoSignError } = useAutoSignatureFields(webViewerInstance.current || instance, mode);
+  // mode 전달 - signatureLocation도 받아오기
+  const { 
+    signatureFields, 
+    isProcessing: isAutoSignProcessing, 
+    hasSignatureFields, 
+    error: autoSignError,
+    signatureLocation // 위치 정보 추가
+  } = useAutoSignatureFields(webViewerInstance.current || instance, mode);
 
   const { hasValidSignature } = useSignatureDetection(webViewerInstance.current || instance, onSignatureComplete);
 
@@ -887,8 +980,9 @@ export function BasicContractSignViewer({
                     stamp.Width = Width;
                     stamp.Height = Height;
 
-                    if (signatureImage) {
-                      await stamp.setImageData(signatureImage.data.dataUrl);
+                    const dataUrl = signatureImage?.data?.dataUrl;
+                    if (dataUrl) {
+                      await stamp.setImageData(dataUrl);
                       annot.sign(stamp);
                       annot.setFieldFlag(WidgetFlags.READ_ONLY, true);
                     }
@@ -1182,6 +1276,8 @@ export function BasicContractSignViewer({
   const SignatureFieldsStatus = () => {
     if (!hasSignatureFields && !isAutoSignProcessing && !autoSignError && !hasValidSignature) return null;
 
+    const currentInstance = webViewerInstance.current || instance;
+
     return (
       <div className="mb-2 flex items-center space-x-2">
         {isAutoSignProcessing ? (
@@ -1195,10 +1291,23 @@ export function BasicContractSignViewer({
             자동 생성 실패
           </Badge>
         ) : hasSignatureFields ? (
-          <Badge variant="outline" className="text-xs bg-blue-50 text-blue-700 border-blue-200">
-            <Target className="h-3 w-3 mr-1" />
-            {signatureFields.length}개 서명 필드 자동 생성됨 {mode === 'buyer' ? '(왼쪽 하단)' : ''}
-          </Badge>
+          <>
+            <Badge variant="outline" className="text-xs bg-blue-50 text-blue-700 border-blue-200">
+              <Target className="h-3 w-3 mr-1" />
+              {signatureFields.length}개 서명 필드 자동 생성됨 {mode === 'buyer' ? '(왼쪽 하단)' : ''}
+            </Badge>
+            {signatureLocation && (
+              <Button
+                variant="ghost"
+                size="sm"
+                className="h-6 px-2 text-xs hover:bg-blue-50"
+                onClick={() => navigateToSignatureField(currentInstance, signatureLocation)}
+              >
+                <Target className="h-3 w-3 mr-1" />
+                서명란으로 이동
+              </Button>
+            )}
+          </>
         ) : null}
 
         {hasValidSignature && (
-- 
cgit v1.2.3

