"use client" import * as React from "react" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Separator } from "@/components/ui/separator" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Eye, Download, Save, Upload, Loader2, FileText, RefreshCw, Settings, AlertCircle, CheckCircle } from "lucide-react" import { toast } from "sonner" import { type GtcClauseTreeView } from "@/db/schema/gtc" import { ClausePreviewViewer } from "./clause-preview-viewer" import { saveGtcDocumentAction } from "../service" interface Vendor { vendorName: string address: string representativeName: string taxId: string phone: string } interface PreviewDocumentDialogProps extends React.ComponentPropsWithRef { clauses: GtcClauseTreeView[] contractDocument: any vendor: Vendor contractId?: number onExport?: () => void } export function PreviewDocumentDialog({ clauses, contractDocument, vendor, contractId, onExport, ...props }: PreviewDocumentDialogProps) { const [isGenerating, setIsGenerating] = React.useState(false) const [isSaving, setIsSaving] = React.useState(false) const [isConverting, setIsConverting] = React.useState(false) const [documentGenerated, setDocumentGenerated] = React.useState(false) const [viewerInstance, setViewerInstance] = React.useState(null) const [hasError, setHasError] = React.useState(false) // 파일 업로드 관련 상태 const [selectedFile, setSelectedFile] = React.useState(null) const [convertedPdf, setConvertedPdf] = React.useState(null) const fileInputRef = React.useRef(null) // 조항 통계 계산 const stats = React.useMemo(() => { const activeClausesCount = clauses.filter(c => c.isActive !== false).length const topLevelCount = clauses.filter(c => !c.parentId && c.isActive !== false).length const hasContentCount = clauses.filter(c => c.content && c.isActive !== false).length return { total: activeClausesCount, topLevel: topLevelCount, withContent: hasContentCount, withoutContent: activeClausesCount - hasContentCount } }, [clauses]) const handleGeneratePreview = async () => { setIsGenerating(true) setHasError(false) setDocumentGenerated(false) try { console.log("🚀 문서 미리보기 생성 시작") // ClausePreviewViewer가 완전히 로드될 때까지 기다림 await new Promise(resolve => setTimeout(resolve, 2000)) if (!hasError) { setDocumentGenerated(true) toast.success("문서 미리보기가 생성되었습니다.") } } catch (error) { console.error("문서 생성 중 오류:", error) setHasError(true) toast.error("문서 생성 중 오류가 발생했습니다.") } finally { setIsGenerating(false) } } // 파일 선택 핸들러 const handleFileSelect = (event: React.ChangeEvent) => { const file = event.target.files?.[0] if (!file) return // Word 파일만 허용 const allowedTypes = [ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx 'application/msword' // .doc ] if (!allowedTypes.includes(file.type)) { toast.error("Word 파일(.doc, .docx)만 업로드할 수 있습니다.") return } if (file.size > 50 * 1024 * 1024) { // 50MB 제한 toast.error("파일 크기는 50MB 이하여야 합니다.") return } setSelectedFile(file) setConvertedPdf(null) // 이전 변환 결과 초기화 toast.success(`파일이 선택되었습니다: ${file.name}`) } // PDF 변환 함수 const handleConvertToPdf = async () => { if (!selectedFile) { toast.error("먼저 Word 파일을 선택해주세요.") return } // 브라우저 환경 체크 if (typeof window === 'undefined' || typeof document === 'undefined') { toast.error("브라우저 환경에서만 PDF 변환이 가능합니다.") return } setIsConverting(true) try { console.log("🔄 PDF 변환 시작:", selectedFile.name) // PDFTron WebViewer 동적 import const { default: WebViewer } = await import("@pdftron/webviewer") // 임시 WebViewer 인스턴스 생성 (화면에 표시하지 않음) 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) const instance = await WebViewer( { path: "/pdftronWeb", licenseKey: process.env.NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY, fullAPI: true, enableOfficeEditing: true, }, tempDiv ) try { // WebViewer 초기화 대기 await new Promise(resolve => setTimeout(resolve, 1000)) const { Core } = instance const { createDocument } = Core const templateData = { company_name: vendor.vendorName || '협력업체명', company_address: vendor.address || '주소', representative_name: vendor.representativeName || '대표자명', signature_date: new Date().toLocaleDateString('ko-KR'), tax_id: vendor.taxId || '사업자번호', phone_number: vendor.phone || '전화번호', } const templateDoc = await createDocument(selectedFile, { filename: selectedFile.name|| 'template.docx', 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' }) console.log("✅ PDF 변환 완료:", pdfBuffer.byteLength, "bytes") setConvertedPdf(new Uint8Array(pdfBuffer)) toast.success("PDF 변환이 완료되었습니다.") } finally { // 임시 WebViewer 정리 try { instance.UI.dispose() } catch (disposeError) { console.warn("WebViewer dispose 오류:", disposeError) } try { if (tempDiv && tempDiv.parentNode) { document.body.removeChild(tempDiv) } } catch (removeError) { console.warn("임시 div 제거 오류:", removeError) } } } catch (error) { console.error("❌ PDF 변환 실패:", error) toast.error(`PDF 변환 실패: ${error instanceof Error ? error.message : '알 수 없는 오류'}`) } finally { setIsConverting(false) } } // 문서 저장 함수 const handleSaveDocument = async () => { if (!convertedPdf || !selectedFile) { toast.error("먼저 파일을 업로드하고 PDF로 변환해주세요.") return } if (!contractId) { toast.error("계약 ID를 찾을 수 없습니다. URL에 contractId 파라미터가 필요합니다.") return } setIsSaving(true) try { console.log("💾 문서 저장 시작", { contractId }) const result = await saveGtcDocumentAction({ documentId: contractId, pdfBuffer: convertedPdf, originalFileName: selectedFile.name, vendor }) if (result.success) { toast.success(`문서가 성공적으로 저장되었습니다.`) console.log("✅ 문서 저장 완료:", { fileName: result.fileName, filePath: result.filePath, fileSize: result.fileSize }) // 저장 완료 후 상태 초기화 setSelectedFile(null) setConvertedPdf(null) if (fileInputRef.current) { fileInputRef.current.value = '' } } else { throw new Error(result.error || "문서 저장에 실패했습니다.") } } catch (error) { console.error("❌ 문서 저장 실패:", error) toast.error(`문서 저장 실패: ${error instanceof Error ? error.message : '알 수 없는 오류'}`) } finally { setIsSaving(false) } } const handleExportDocument = () => { if (viewerInstance) { try { viewerInstance.UI.downloadPdf({ filename: `${contractDocument?.title || 'GTC계약서'}_미리보기.pdf` }) toast.success("PDF 다운로드가 시작됩니다.") } catch (error) { console.error("다운로드 오류:", error) toast.error("다운로드 중 오류가 발생했습니다.") } } else { toast.error("뷰어가 준비되지 않았습니다.") } } const handleRegenerateDocument = () => { console.log("🔄 문서 재생성 시작") setDocumentGenerated(false) setHasError(false) handleGeneratePreview() } const handleViewerSuccess = React.useCallback(() => { setDocumentGenerated(true) setIsGenerating(false) setHasError(false) }, []) const handleViewerError = React.useCallback(() => { setHasError(true) setIsGenerating(false) setDocumentGenerated(false) }, []) // 다이얼로그가 열릴 때 자동으로 미리보기 생성 React.useEffect(() => { if (props.open && !documentGenerated && !isGenerating && !hasError) { const timer = setTimeout(() => { handleGeneratePreview() }, 300) return () => clearTimeout(timer) } }, [props.open, documentGenerated, isGenerating, hasError]) // 다이얼로그가 닫힐 때 상태 초기화 React.useEffect(() => { if (!props.open) { setDocumentGenerated(false) setIsGenerating(false) setIsSaving(false) setIsConverting(false) setHasError(false) setViewerInstance(null) setSelectedFile(null) setConvertedPdf(null) if (fileInputRef.current) { fileInputRef.current.value = '' } } }, [props.open]) return ( 문서 미리보기 및 저장 조항 기반 미리보기를 확인하고, Word 파일을 업로드하여 최종 문서를 저장하세요. {/* 문서 정보 및 통계 */}
{contractDocument?.title || 'GTC 계약서'} {stats.total}개 조항 {vendor && ( {vendor.vendorName} )} {hasError && ( 오류 발생 )}
{documentGenerated && !hasError && ( )} {hasError && ( )}
{/* 파일 업로드 섹션 */}
{selectedFile && ( )}
{selectedFile && (

선택됨: {selectedFile.name} ({(selectedFile.size / (1024 * 1024)).toFixed(2)}MB)

)}
{/* 통계 정보 */}
{stats.total}
총 조항
{stats.topLevel}
최상위 조항
{stats.withContent}
내용 있음
{stats.withoutContent}
제목만
{/* PDFTron 뷰어 영역 */}
{isGenerating ? (

문서 생성 중...

{stats.total}개의 조항을 배치하고 있습니다.

초기화에 시간이 걸릴 수 있습니다...

) : hasError ? (

문서 생성 실패

문서 생성 중 오류가 발생했습니다. 네트워크 연결이나 파일 권한을 확인해주세요.

) : documentGenerated ? ( ) : (

문서 미리보기 준비 중

)}
) }