summaryrefslogtreecommitdiff
path: root/components/contract-generator-client.tsx
blob: 1abdc4f40bf7096d01d69c37fb1e2fbf4961a746 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
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>
    );
}