"use client"; import * as React from "react"; import { Button } from "@/components/ui/button"; import { toast } from "sonner"; import { Save, RefreshCw, Type, FileText, AlertCircle } from "lucide-react"; import type { WebViewerInstance } from "@pdftron/webviewer"; import { Badge } from "@/components/ui/badge"; import { BasicContractTemplateViewer } from "./basic-contract-template-viewer"; import { saveTemplateFile } from "../service"; interface TemplateEditorWrapperProps { templateId: string | number; filePath: string; fileName: string; refreshAction?: () => Promise; } // 변수 패턴 감지를 위한 정규식 const VARIABLE_PATTERN = /\{\{([^}]+)\}\}/g; export function TemplateEditorWrapper({ templateId, filePath, fileName, refreshAction }: TemplateEditorWrapperProps) { const [instance, setInstance] = React.useState(null); const [isSaving, setIsSaving] = React.useState(false); const [documentVariables, setDocumentVariables] = React.useState([]); // 문서에서 변수 추출 const extractVariablesFromDocument = async () => { if (!instance) return; try { const { documentViewer } = instance.Core; const doc = documentViewer.getDocument(); if (!doc) return; // 문서 텍스트 추출 const textContent = await doc.getDocumentCompletePromise().then(async () => { const pageCount = doc.getPageCount(); let fullText = ""; for (let i = 1; i <= pageCount; i++) { try { const pageText = await doc.loadPageText(i); fullText += pageText + " "; } catch (error) { console.warn(`페이지 ${i} 텍스트 추출 실패:`, error); } } return fullText; }); // 변수 패턴 매칭 const matches = textContent.match(VARIABLE_PATTERN); const variables = matches ? [...new Set(matches.map(match => match.replace(/[{}]/g, '')))] : []; setDocumentVariables(variables); if (variables.length > 0) { console.log("🔍 발견된 변수들:", variables); } } catch (error) { console.error("변수 추출 중 오류:", error); } }; // 인스턴스가 변경될 때마다 변수 추출 React.useEffect(() => { if (instance) { // 문서 로드 완료 이벤트 리스너 추가 const { documentViewer } = instance.Core; const onDocumentLoaded = () => { setTimeout(() => extractVariablesFromDocument(), 1000); }; documentViewer.addEventListener("documentLoaded", onDocumentLoaded); return () => { documentViewer.removeEventListener("documentLoaded", onDocumentLoaded); }; } }, [instance]); // 변수 삽입 함수 (한글 입력 제한 고려) const insertVariable = async (variableName: string) => { if (!instance) { toast.error("뷰어가 준비되지 않았습니다."); return; } try { const textToInsert = `{{${variableName}}}`; // 1단계: 클립보드 API 시도 if (navigator.clipboard && navigator.clipboard.writeText) { try { await navigator.clipboard.writeText(textToInsert); toast.success(`변수 "${textToInsert}"가 클립보드에 복사되었습니다.`, { description: "문서에서 원하는 위치에 Ctrl+V로 붙여넣기 하세요." }); return; } catch (clipboardError) { console.warn("클립보드 API 사용 실패:", clipboardError); } } // 2단계: Office 편집기에 직접 삽입 시도 (실험적) try { const { documentViewer } = instance.Core; const doc = documentViewer.getDocument(); if (doc && typeof doc.getOfficeEditor === 'function') { const officeEditor = doc.getOfficeEditor(); if (officeEditor && typeof officeEditor.insertText === 'function') { await officeEditor.insertText(textToInsert); toast.success(`변수 "${textToInsert}"가 문서에 삽입되었습니다.`); return; } } } catch (insertError) { console.warn("직접 삽입 실패:", insertError); } // 3단계: 임시 텍스트 영역을 통한 복사 (대안) try { const tempTextArea = document.createElement('textarea'); tempTextArea.value = textToInsert; tempTextArea.style.position = 'fixed'; tempTextArea.style.left = '-9999px'; document.body.appendChild(tempTextArea); tempTextArea.select(); document.execCommand('copy'); document.body.removeChild(tempTextArea); toast.success(`변수 "${textToInsert}"가 클립보드에 복사되었습니다.`, { description: "Office 편집기에서 한글 입력이 제한될 수 있습니다. 외부에서 작성 후 붙여넣기를 권장합니다." }); return; } catch (fallbackError) { console.warn("대안 복사 실패:", fallbackError); } // 4단계: 수동 입력 안내 toast.info(`다음 변수를 수동으로 입력해주세요: ${textToInsert}`, { description: "한글과 변수는 외부 에디터에서 작성 후 복사-붙여넣기를 권장합니다.", duration: 8000, }); } catch (error) { console.error("변수 삽입 오류:", error); toast.error("변수 삽입 중 오류가 발생했습니다."); } }; // 문서 저장 const handleSave = async () => { if (!instance) { toast.error("뷰어가 준비되지 않았습니다."); return; } setIsSaving(true); try { const { documentViewer } = instance.Core; const doc = documentViewer.getDocument(); if (!doc) { throw new Error("문서를 찾을 수 없습니다."); } // Word 문서 저장 const data = await doc.getFileData({ downloadType: "office", // Word 파일로 저장 includeAnnotations: true }); // FormData 생성하여 서버 액션에 전달 const formData = new FormData(); formData.append('file', new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }), fileName); // 서버 액션 호출 const result = await saveTemplateFile(Number(templateId), formData); if (result.error) { throw new Error(result.error); } toast.success("템플릿이 성공적으로 저장되었습니다."); // 변수 재추출 await extractVariablesFromDocument(); // 페이지 새로고침 (서버 액션) if (refreshAction) { await refreshAction(); } } catch (error) { console.error("저장 오류:", error); toast.error(error instanceof Error ? error.message : "저장 중 오류가 발생했습니다."); } finally { setIsSaving(false); } }; // 문서 새로고침 const handleRefresh = () => { window.location.reload(); }; // 미리 정의된 변수들 const predefinedVariables = [ "회사명", "계약자명", "계약일자", "계약금액", "납기일자", "담당자명", "담당자연락처", "프로젝트명", "계약번호", "사업부", "부서명", "승인자명" ]; return (
{/* 상단 도구 모음 */}
{fileName} {documentVariables.length > 0 && ( 변수 {documentVariables.length}개 )}
{/* 변수 도구 */} {(documentVariables.length > 0 || predefinedVariables.length > 0) && (

변수 관리

{/* 발견된 변수들 */} {documentVariables.length > 0 && (

문서에서 발견된 변수:

{documentVariables.map((variable, index) => ( {`{{${variable}}}`} ))}
)} {/* 미리 정의된 변수들 */}

자주 사용하는 변수 (클릭하여 복사):

{predefinedVariables.map((variable, index) => ( ))}
)}
{/* 뷰어 영역 (확대 문제 해결을 위한 컨테이너 격리) */}
{/* 하단 안내 (한글 입력 팁 포함) */}
{'{{변수명}}'} 형식으로 변수를 삽입하면 계약서 생성 시 실제 값으로 치환됩니다.
한글 입력 제한시 외부 에디터에서 작성 후 복사-붙여넣기를 사용하세요.
); }