// components/contract-generator-client.tsx "use client"; import { useState } from "react"; import { toast } from "sonner"; import { prepareContractTemplate, saveContractPdf } from "@/app/actions/contract-actions"; interface ContractGeneratorProps { vendorId: number; rfqCompanyId: number; requestedBy: number; templateName: string; biddingCompanyId?: number | null; generalContractId?: number | null; } export function ContractGeneratorClient({ vendorId, rfqCompanyId, requestedBy, templateName, biddingCompanyId, generalContractId }: ContractGeneratorProps) { const [isGenerating, setIsGenerating] = useState(false); const [progress, setProgress] = useState(""); const generateContract = async () => { setIsGenerating(true); setProgress("템플릿 데이터 준비 중..."); try { // 1. 서버에서 템플릿 데이터 준비 const prepareResult = await prepareContractTemplate({ templateName, vendorId, biddingCompanyId, rfqCompanyId, generalContractId, requestedBy }); if (!prepareResult.success) { throw new Error("템플릿 준비 실패"); } setProgress("템플릿 파일 다운로드 중..."); // 2. 템플릿 파일 가져오기 const templateResponse = await fetch("/api/contracts/template", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ templatePath: prepareResult.template.filePath }) }); if (!templateResponse.ok) { throw new Error("템플릿 파일 다운로드 실패"); } const templateBlob = await templateResponse.blob(); const templateFile = new File([templateBlob], "template.docx", { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }); setProgress("PDF 변환 중..."); // 3. 클라이언트에서 PDFtron으로 변환 const pdfBuffer = await convertToPdfInBrowser( templateFile, prepareResult.templateData ); setProgress("PDF 저장 중..."); // 4. 변환된 PDF를 서버에 저장 const saveResult = await saveContractPdf({ pdfBuffer, fileName: `${templateName}_${vendorId}`, params: prepareResult.params, templateId: prepareResult.template.id }); if (saveResult.success) { toast.success("계약서가 성공적으로 생성되었습니다!"); setProgress(""); // 생성된 PDF 다운로드 또는 미리보기 window.open(saveResult.pdfPath, "_blank"); } } catch (error) { console.error("계약서 생성 실패:", error); toast.error(`계약서 생성 실패: ${error instanceof Error ? error.message : '알 수 없는 오류'}`); setProgress(""); } finally { setIsGenerating(false); } }; // PDFtron WebViewer를 사용한 PDF 변환 const convertToPdfInBrowser = async ( templateFile: File, templateData: Record ): Promise => { // 동적 import const { default: WebViewer } = await import("@pdftron/webviewer"); // 임시 div 생성 (화면에 표시하지 않음) const tempDiv = document.createElement('div'); tempDiv.style.display = 'none'; tempDiv.style.position = 'absolute'; tempDiv.style.top = '-9999px'; tempDiv.style.left = '-9999px'; tempDiv.style.width = '1px'; tempDiv.style.height = '1px'; document.body.appendChild(tempDiv); try { const instance = await WebViewer( { path: "/pdftronWeb", licenseKey: process.env.NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY, fullAPI: true, enableOfficeEditing: true, }, tempDiv ); // WebViewer 초기화 대기 await new Promise(resolve => setTimeout(resolve, 1000)); const { Core } = instance; const { createDocument } = Core; // 문서 생성 및 템플릿 데이터 적용 const templateDoc = await createDocument(templateFile, { filename: templateFile.name, extension: 'docx', }); await templateDoc.applyTemplateValues(templateData); // 문서 로드 완료 대기 await new Promise(resolve => setTimeout(resolve, 3000)); // PDF로 변환 const fileData = await templateDoc.getFileData(); const pdfBuffer = await Core.officeToPDFBuffer(fileData, { extension: 'docx' }); // 정리 instance.UI.dispose(); return new Uint8Array(pdfBuffer); } finally { // 임시 div 제거 if (tempDiv.parentNode) { document.body.removeChild(tempDiv); } } }; return (
{progress && (
{progress}
)}
); }