From d38877eef87917087a4a217bea32ae84d6738a7d Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Fri, 22 Aug 2025 08:20:45 +0000
Subject: (최겸) 인포메이션 첨부파일 뷰어 추가
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../document-viewer/pdftron-viewer-dialog.tsx      | 207 +++++++++++++++++++++
 1 file changed, 207 insertions(+)
 create mode 100644 components/document-viewer/pdftron-viewer-dialog.tsx

(limited to 'components/document-viewer/pdftron-viewer-dialog.tsx')

diff --git a/components/document-viewer/pdftron-viewer-dialog.tsx b/components/document-viewer/pdftron-viewer-dialog.tsx
new file mode 100644
index 00000000..58d2a91f
--- /dev/null
+++ b/components/document-viewer/pdftron-viewer-dialog.tsx
@@ -0,0 +1,207 @@
+"use client"
+
+import * as React from "react"
+import { useState, useRef, useEffect } from "react"
+import {
+  Dialog,
+  DialogContent,
+  DialogHeader,
+  DialogTitle,
+  DialogDescription,
+} from "@/components/ui/dialog"
+import { Button } from "@/components/ui/button"
+import { X, Loader2, AlertCircle } from "lucide-react"
+
+interface PDFTronViewerDialogProps {
+  open: boolean
+  onOpenChange: (open: boolean) => void
+  fileUrl?: string
+  fileName?: string
+}
+
+
+
+export function PDFTronViewerDialog({
+  open,
+  onOpenChange,
+  fileUrl,
+  fileName
+}: PDFTronViewerDialogProps) {
+  const viewerRef = useRef<HTMLDivElement>(null)
+  const [isLoading, setIsLoading] = useState(false)
+  const [error, setError] = useState<string | null>(null)
+  const [instance, setInstance] = useState<any>(null)
+
+  // PDFTron WebViewer 초기화
+  const initializeViewer = async () => {
+    if (!viewerRef.current || !fileUrl) return
+
+    setIsLoading(true)
+    setError(null)
+
+    try {
+      // @pdftron/webviewer 패키지에서 동적으로 import
+      const { default: WebViewer } = await import("@pdftron/webviewer")
+      
+      const viewerInstance = await WebViewer({
+        path: '/pdftronWeb',
+        licenseKey: process.env.NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY,
+        fullAPI: false,
+        enableFilePicker: false,
+        enableRedaction: false,
+        enableMeasurement: false,
+        enableAnnotations: false,
+        disabledElements: [
+          'header',
+          'toolsHeader',
+          'searchButton',
+          'menuButton',
+          'viewControlsButton',
+          'selectToolButton',
+          'freeHandToolButton',
+          'textSelectButton',
+          'panToolButton',
+          'annotationCommentButton',
+          'leftPanel',
+          'leftPanelButton',
+          'notesPanelButton',
+          'searchPanel',
+          'thumbnailControl',
+          'outlineControl',
+          'contextMenuPopup',
+          'toolsOverlay',
+          'signatureModal',
+          'redactionModal',
+          'linkModal',
+          'filterModal',
+          'passwordModal',
+          'progressModal',
+          'errorModal',
+          'toolbarGroup-Annotate',
+          'toolbarGroup-Shapes',
+          'toolbarGroup-Edit',
+          'toolbarGroup-Insert',
+          'toolbarGroup-Forms',
+          'toolbarGroup-View',
+          'downloadButton',
+          'printButton',
+          'saveAsButton',
+          'textToolButton',
+          'stickyToolButton',
+          'calloutToolButton',
+          'rubberStampToolButton',
+          'stampToolButton',
+          'freeTextToolButton',
+          'toolsButton'
+        ]
+      }, viewerRef.current)
+
+      setInstance(viewerInstance)
+
+      // 문서 로드
+      if (fileUrl) {
+        const { documentViewer } = viewerInstance.Core
+        
+        // 문서 로드 완료 후 읽기 전용 설정
+        documentViewer.addEventListener('documentLoaded', () => {
+          setIsLoading(false)
+        })
+
+        // 문서 로드
+        viewerInstance.UI.loadDocument(fileUrl)
+      } else {
+        setIsLoading(false)
+      }
+
+    } catch (err) {
+      console.error('PDFTron 뷰어 초기화 실패:', err)
+      setError('문서를 불러올 수 없습니다. PDFTron이 설치되어 있는지 확인해주세요.')
+      setIsLoading(false)
+    }
+  }
+
+  // 다이얼로그가 열릴 때 뷰어 초기화
+  useEffect(() => {
+    if (open && fileUrl) {
+      // 약간의 지연을 두어 DOM이 완전히 렌더링된 후 초기화
+      const timer = setTimeout(initializeViewer, 100)
+      return () => clearTimeout(timer)
+    }
+  }, [open, fileUrl])
+
+  // 다이얼로그가 닫힐 때 정리
+  useEffect(() => {
+    if (!open && instance) {
+      try {
+        // WebViewer 인스턴스 정리
+        if (viewerRef.current) {
+          viewerRef.current.innerHTML = ''
+        }
+        setInstance(null)
+      } catch (err) {
+        console.error('뷰어 정리 실패:', err)
+      }
+    }
+  }, [open, instance])
+
+  return (
+    <Dialog open={open} onOpenChange={onOpenChange}>
+      <DialogContent className="max-w-6xl max-h-[90vh] h-[90vh] p-0">
+        <DialogHeader className="px-4 py-3 border-b">
+          <div className="flex items-center justify-between">
+            <div>
+              <DialogTitle className="text-lg font-semibold">
+                {fileName || '문서 뷰어'}
+              </DialogTitle>
+              <DialogDescription className="text-sm text-gray-600 mt-1">
+                PDF 및 Office 문서를 읽기 전용으로 보기
+              </DialogDescription>
+            </div>
+            {/* <Button
+              variant="ghost"
+              size="sm"
+              onClick={() => onOpenChange(false)}
+            >
+            </Button> */}
+          </div>
+        </DialogHeader>
+        
+        <div className="flex-1 relative">
+          {isLoading && (
+            <div className="absolute inset-0 flex items-center justify-center bg-white z-10">
+              <div className="flex flex-col items-center gap-3">
+                <Loader2 className="h-8 w-8 animate-spin text-blue-500" />
+                <span className="text-gray-600">문서를 불러오는 중...</span>
+              </div>
+            </div>
+          )}
+          
+          {error && (
+            <div className="absolute inset-0 flex items-center justify-center bg-white z-10">
+              <div className="flex flex-col items-center gap-3 text-center max-w-md">
+                <AlertCircle className="h-12 w-12 text-red-500" />
+                <div>
+                  <h3 className="font-semibold text-gray-900 mb-2">문서를 불러올 수 없습니다</h3>
+                  <p className="text-gray-600 text-sm">{error}</p>
+                </div>
+                <Button 
+                  variant="outline" 
+                  onClick={() => onOpenChange(false)}
+                  className="mt-2"
+                >
+                  닫기
+                </Button>
+              </div>
+            </div>
+          )}
+          
+          <div 
+            ref={viewerRef} 
+            className="w-full h-full"
+            style={{ height: 'calc(90vh - 60px)' }}
+          />
+        </div>
+      </DialogContent>
+    </Dialog>
+  )
+}
-- 
cgit v1.2.3

