From 675b4e3d8ffcb57a041db285417d81e61284d900 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Sun, 14 Sep 2025 05:28:01 +0000 Subject: (대표님) RFQ-last, tbe-last, 기본계약 템플릿 내 견적,입찰,계약 추가, env.dev NAS_PATH 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/contract-generator-client.tsx | 181 +++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 components/contract-generator-client.tsx (limited to 'components/contract-generator-client.tsx') diff --git a/components/contract-generator-client.tsx b/components/contract-generator-client.tsx new file mode 100644 index 00000000..1abdc4f4 --- /dev/null +++ b/components/contract-generator-client.tsx @@ -0,0 +1,181 @@ +// 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} +
+ )} +
+ ); +} \ No newline at end of file -- cgit v1.2.3