"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 { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { BasicContractTemplateViewer } from "./basic-contract-template-viewer"; import { getExistingTemplateNamesById, saveTemplateFile } from "../service"; interface TemplateEditorWrapperProps { templateId: string | number; filePath: string; fileName: string; refreshAction?: () => Promise; } const getVariablesForTemplate = (templateName: string): string[] => { let variables: string[] = []; // 정확한 매치 먼저 확인 if (TEMPLATE_VARIABLES_MAP[templateName as keyof typeof TEMPLATE_VARIABLES_MAP]) { variables = [...TEMPLATE_VARIABLES_MAP[templateName as keyof typeof TEMPLATE_VARIABLES_MAP]]; } else if (templateName.includes("GTC")) { // GTC가 포함된 경우 확인 variables = [...TEMPLATE_VARIABLES_MAP["GTC"]]; } else { // 다른 키워드들도 포함 관계로 확인 let found = false; for (const [key, vars] of Object.entries(TEMPLATE_VARIABLES_MAP)) { if (templateName.includes(key)) { variables = [...vars]; found = true; break; } } // 기본값 반환 if (!found) { variables = ["company_name", "company_address", "representative_name", "signature_date"]; } } // 모든 템플릿에 서명란 변수 추가 (중복 제거) const signatureVars = ["vendor_signature", "buyer_signature"]; const allVariables = [...variables, ...signatureVars]; return [...new Set(allVariables)]; // 중복 제거 }; // 템플릿 이름별 변수 매핑 const TEMPLATE_VARIABLES_MAP = { "준법서약 (한글)": ["company_name", "company_address", "representative_name", "signature_date"], "준법서약 (영문)": ["company_name", "company_address", "representative_name", "signature_date"], "기술자료 요구서": ["company_name", "company_address", "representative_name", "signature_date", 'tax_id', 'phone_number'], "비밀유지 계약서": ["company_name", "company_address", "representative_name", "signature_date"], "표준하도급기본 계약서": ["company_name", "company_address", "representative_name", "signature_date"], "GTC": ["company_name", "company_address", "representative_name", "signature_date"], "안전보건관리 약정서": ["company_name", "company_address", "representative_name", "signature_date"], "동반성장": ["company_name", "company_address", "representative_name", "signature_date"], "윤리규범 준수 서약서": ["company_name", "company_address", "representative_name", "signature_date"], "기술자료 동의서": ["company_name", "company_address", "representative_name", "signature_date", 'tax_id', 'phone_number'], "내국신용장 미개설 합의서": ["company_name", "company_address", "representative_name", "signature_date"], "직납자재 하도급대급등 연동제 의향서": ["company_name", "company_address", "representative_name", "signature_date"] } as const; // 변수별 한글 설명 매핑 const VARIABLE_DESCRIPTION_MAP = { "company_name": "협력회사명", "vendor_name": "협력회사명", "company_address": "회사주소", "address": "회사주소", "representative_name": "대표자명", "signature_date": "서명날짜", "today_date": "오늘날짜", "tax_id": "사업자등록번호", "phone_number": "전화번호", "phone": "전화번호", "email": "이메일", "vendor_signature": "협력업체 서명란", "buyer_signature": "구매자 서명란", "협력업체_서명란": "협력업체 서명란", "삼성중공업_서명란": "구매자 서명란" } as const; // 서명란 텍스트를 변수명으로 변환 const getSignatureVariableName = (searchText: string): string => { if (searchText === '협력업체_서명란') { return 'vendor_signature'; } else if (searchText === '삼성중공업_서명란') { return 'buyer_signature'; } return searchText; }; // 변수 패턴 감지를 위한 정규식 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 [templateName, setTemplateName] = React.useState(""); const [predefinedVariables, setPredefinedVariables] = React.useState([]); const [signatureVariables, setSignatureVariables] = React.useState([]); // 서명란 변수 // 템플릿 이름 로드 및 변수 설정 React.useEffect(() => { const loadTemplateInfo = async () => { try { const name = await getExistingTemplateNamesById(Number(templateId)); setTemplateName(name); // 템플릿 이름에 따른 변수 설정 const variables = getVariablesForTemplate(name); setPredefinedVariables([...variables]); console.log("🏷️ 템플릿 이름:", name); console.log("📝 할당된 변수들:", variables); } catch (error) { console.error("템플릿 정보 로드 오류:", error); // 기본 변수 설정 setPredefinedVariables(["company_name", "company_address", "representative_name", "signature_date"]); } }; if (templateId) { loadTemplateInfo(); } }, [templateId]); // 문서에서 변수 추출 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(); }; return (
{/* 상단 도구 모음 */}
{fileName} {templateName && ( {templateName} )} {documentVariables.length > 0 && ( 변수 {documentVariables.length}개 )}
{/* 변수 도구 */} {(documentVariables.length > 0 || predefinedVariables.length > 0 || signatureVariables.length > 0) && (

변수 관리 {templateName && ( ({templateName}) )}

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

문서에서 발견된 변수:

{documentVariables.map((variable, index) => ( {`{{${variable}}}`} ))}
)} {/* 서명란 변수 */} {signatureVariables.length > 0 && (

서명란 변수 (클릭하여 복사):

{signatureVariables.map((variable, index) => (

{VARIABLE_DESCRIPTION_MAP[variable as keyof typeof VARIABLE_DESCRIPTION_MAP] || variable}

))}
)} {/* 템플릿별 미리 정의된 변수들 */} {predefinedVariables.length > 0 && (

{templateName ? `${templateName}에 권장되는 변수` : "자주 사용하는 변수"} (클릭하여 복사):

{predefinedVariables.map((variable, index) => (

{VARIABLE_DESCRIPTION_MAP[variable as keyof typeof VARIABLE_DESCRIPTION_MAP] || variable}

))}
)}
)}
{/* 뷰어 영역 (확대 문제 해결을 위한 컨테이너 격리) */}
{ // 서명란 발견 시 변수명으로 변환하여 추가 const variableName = getSignatureVariableName(searchText); setSignatureVariables(prev => { if (!prev.includes(variableName)) { return [...prev, variableName]; } return prev; }); }} />
{/* 하단 안내 (한글 입력 팁 포함) */}
{'{{변수명}}'} 형식으로 변수를 삽입하면 계약서 생성 시 실제 값으로 치환됩니다.
한글 입력 제한시 외부 에디터에서 작성 후 복사-붙여넣기를 사용하세요.
); }