diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-11-07 02:59:13 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-11-07 02:59:13 +0000 |
| commit | cc2dc9c162ceadfc41f8fa1fcd4c82341c33af7c (patch) | |
| tree | 1062f38fa8c4f0fb2302843ab089e76c3582b93e /lib/basic-contract/template | |
| parent | 5c02b1cc95503cdd7bcd70c6e5fad848250685ec (diff) | |
(임수민) 기본계약서 수정하기 리비전 수정, 서명란 바로가기 버튼 추가
Diffstat (limited to 'lib/basic-contract/template')
| -rw-r--r-- | lib/basic-contract/template/basic-contract-template-viewer.tsx | 159 |
1 files changed, 148 insertions, 11 deletions
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" |
