summaryrefslogtreecommitdiff
path: root/components/contract-generator-client.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/contract-generator-client.tsx')
-rw-r--r--components/contract-generator-client.tsx181
1 files changed, 181 insertions, 0 deletions
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<string, string>
+ ): Promise<Uint8Array> => {
+ // 동적 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 (
+ <div className="space-y-4">
+ <button
+ onClick={generateContract}
+ disabled={isGenerating}
+ className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
+ >
+ {isGenerating ? "생성 중..." : "계약서 생성"}
+ </button>
+
+ {progress && (
+ <div className="text-sm text-gray-600">
+ {progress}
+ </div>
+ )}
+ </div>
+ );
+} \ No newline at end of file