diff options
Diffstat (limited to 'lib/basic-contract/gtc-vendor/preview-document-dialog.tsx')
| -rw-r--r-- | lib/basic-contract/gtc-vendor/preview-document-dialog.tsx | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/lib/basic-contract/gtc-vendor/preview-document-dialog.tsx b/lib/basic-contract/gtc-vendor/preview-document-dialog.tsx new file mode 100644 index 00000000..78ddc7f7 --- /dev/null +++ b/lib/basic-contract/gtc-vendor/preview-document-dialog.tsx @@ -0,0 +1,272 @@ +"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 { + Eye, + Download, + Loader2, + FileText, + RefreshCw, + Settings, + AlertCircle +} from "lucide-react" +import { toast } from "sonner" + +import { type GtcClauseTreeView } from "@/db/schema/gtc" +import { ClausePreviewViewer } from "./clause-preview-viewer" + +interface PreviewDocumentDialogProps + extends React.ComponentPropsWithRef<typeof Dialog> { + clauses: GtcClauseTreeView[] + document: any + onExport?: () => void +} + +export function PreviewDocumentDialog({ + clauses, + document, + onExport, + ...props +}: PreviewDocumentDialogProps) { + const [isGenerating, setIsGenerating] = React.useState(false) + const [documentGenerated, setDocumentGenerated] = React.useState(false) + const [viewerInstance, setViewerInstance] = React.useState<any>(null) + const [hasError, setHasError] = React.useState(false) + + // 조항 통계 계산 + 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 { + // 실제로는 ClausePreviewViewer에서 문서 생성을 처리하므로 + // 여기서는 상태만 관리 + 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 handleExportDocument = () => { + if (viewerInstance) { + try { + // PDFTron의 다운로드 기능 실행 + viewerInstance.UI.downloadPdf({ + filename: `${document?.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) + setHasError(false) + setViewerInstance(null) + } + }, [props.open]) + + return ( + <Dialog {...props}> + <DialogContent className="max-w-7xl h-[90vh] flex flex-col"> + <DialogHeader className="flex-shrink-0"> + <DialogTitle className="flex items-center gap-2"> + <Eye className="h-5 w-5" /> + 문서 미리보기 + </DialogTitle> + <DialogDescription> + 현재 조항들을 기반으로 생성된 문서를 미리보기합니다. + </DialogDescription> + </DialogHeader> + + {/* 문서 정보 및 통계 */} + <div className="flex-shrink-0 p-4 bg-muted/30 rounded-lg"> + <div className="flex items-center justify-between mb-3"> + <div className="flex items-center gap-2"> + <FileText className="h-4 w-4" /> + <span className="font-medium">{document?.title || 'GTC 계약서'}</span> + <Badge variant="outline">{stats.total}개 조항</Badge> + {hasError && ( + <Badge variant="destructive" className="gap-1"> + <AlertCircle className="h-3 w-3" /> + 오류 발생 + </Badge> + )} + </div> + <div className="flex items-center gap-2"> + {documentGenerated && !hasError && ( + <> + <Button + variant="outline" + size="sm" + onClick={handleRegenerateDocument} + disabled={isGenerating} + > + <RefreshCw className={`mr-2 h-3 w-3 ${isGenerating ? 'animate-spin' : ''}`} /> + 재생성 + </Button> + {/* <Button + variant="outline" + size="sm" + onClick={handleExportDocument} + disabled={!viewerInstance} + > + <Download className="mr-2 h-3 w-3" /> + PDF 다운로드 + </Button> */} + </> + )} + {hasError && ( + <Button + variant="default" + size="sm" + onClick={handleRegenerateDocument} + disabled={isGenerating} + > + <RefreshCw className={`mr-2 h-3 w-3 ${isGenerating ? 'animate-spin' : ''}`} /> + 다시 시도 + </Button> + )} + </div> + </div> + + <div className="grid grid-cols-4 gap-4 text-sm"> + <div className="text-center p-2 bg-background rounded"> + <div className="font-medium text-lg">{stats.total}</div> + <div className="text-muted-foreground">총 조항</div> + </div> + <div className="text-center p-2 bg-background rounded"> + <div className="font-medium text-lg">{stats.topLevel}</div> + <div className="text-muted-foreground">최상위 조항</div> + </div> + <div className="text-center p-2 bg-background rounded"> + <div className="font-medium text-lg text-green-600">{stats.withContent}</div> + <div className="text-muted-foreground">내용 있음</div> + </div> + <div className="text-center p-2 bg-background rounded"> + <div className="font-medium text-lg text-amber-600">{stats.withoutContent}</div> + <div className="text-muted-foreground">제목만</div> + </div> + </div> + </div> + + <Separator /> + + {/* PDFTron 뷰어 영역 */} + <div className="flex-1 min-h-0 relative"> + {isGenerating ? ( + <div className="absolute inset-0 flex flex-col items-center justify-center bg-background"> + <Loader2 className="h-8 w-8 animate-spin text-primary mb-4" /> + <p className="text-lg font-medium mb-2">문서 생성 중...</p> + <p className="text-sm text-muted-foreground"> + {stats.total}개의 조항을 배치하고 있습니다. + </p> + <p className="text-xs text-gray-400 mt-2"> + 초기화에 시간이 걸릴 수 있습니다... + </p> + </div> + ) : hasError ? ( + <div className="absolute inset-0 flex flex-col items-center justify-center bg-muted/10"> + <AlertCircle className="h-12 w-12 text-destructive mb-4" /> + <p className="text-lg font-medium mb-2 text-destructive">문서 생성 실패</p> + <p className="text-sm text-muted-foreground mb-4 text-center max-w-md"> + 문서 생성 중 오류가 발생했습니다. 네트워크 연결이나 파일 권한을 확인해주세요. + </p> + <Button onClick={handleRegenerateDocument} disabled={isGenerating}> + <RefreshCw className="mr-2 h-4 w-4" /> + 다시 시도 + </Button> + </div> + ) : documentGenerated ? ( + <ClausePreviewViewer + clauses={clauses} + document={document} + instance={viewerInstance} + setInstance={setViewerInstance} + onSuccess={handleViewerSuccess} + onError={handleViewerError} + /> + ) : ( + <div className="absolute inset-0 flex flex-col items-center justify-center bg-muted/10"> + <FileText className="h-12 w-12 text-muted-foreground mb-4" /> + <p className="text-lg font-medium mb-2">문서 미리보기 준비 중</p> + <Button onClick={handleGeneratePreview} disabled={isGenerating}> + <Eye className="mr-2 h-4 w-4" /> + 미리보기 생성 + </Button> + </div> + )} + </div> + </DialogContent> + </Dialog> + ) +}
\ No newline at end of file |
