// lib/vendor-document-list/plant/upload/components/view-submission-dialog.tsx "use client" import * as React from "react" import { useState, useEffect, useRef } from "react" import { WebViewerInstance } from "@pdftron/webviewer" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { ScrollArea } from "@/components/ui/scroll-area" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Download, Eye, FileText, Calendar, User, CheckCircle2, XCircle, Clock, RefreshCw, Loader2 } from "lucide-react" import { StageSubmissionView } from "@/db/schema" import { formatDateTime, formatDate } from "@/lib/utils" import { toast } from "sonner" import { downloadFile, formatFileSize } from "@/lib/file-download" interface ViewSubmissionDialogProps { open: boolean onOpenChange: (open: boolean) => void submission: StageSubmissionView } interface SubmissionDetail { id: number revisionNumber: number submissionStatus: string reviewStatus?: string reviewComments?: string submittedBy: string submittedAt: Date files: Array<{ id: number originalFileName: string fileSize: number uploadedAt: Date syncStatus: string storageUrl: string }> } // PDFTron 문서 뷰어 컴포넌트 const DocumentViewer: React.FC<{ open: boolean onClose: () => void files: Array<{ id: number originalFileName: string storageUrl: string }> }> = ({ open, onClose, files }) => { const [instance, setInstance] = useState(null) const [viewerLoading, setViewerLoading] = useState(true) const [fileSetLoading, setFileSetLoading] = useState(true) const viewer = useRef(null) const initialized = useRef(false) const isCancelled = useRef(false) const cleanupHtmlStyle = () => { const htmlElement = document.documentElement const originalStyle = htmlElement.getAttribute("style") || "" const colorSchemeStyle = originalStyle .split(";") .map((s) => s.trim()) .find((s) => s.startsWith("color-scheme:")) if (colorSchemeStyle) { htmlElement.setAttribute("style", colorSchemeStyle + ";") } else { htmlElement.removeAttribute("style") } } useEffect(() => { if (open && !initialized.current) { initialized.current = true isCancelled.current = false requestAnimationFrame(() => { if (viewer.current) { import("@pdftron/webviewer").then(({ default: WebViewer }) => { if (isCancelled.current) { console.log("WebViewer 초기화 취소됨") return } WebViewer( { path: "/pdftronWeb", licenseKey: "demo:1739264618684:616161d7030000000091db1c97c6f386d41d3506ab5b507381ef2ee2bd", fullAPI: true, css: "/globals.css", }, viewer.current as HTMLDivElement ).then(async (instance: WebViewerInstance) => { setInstance(instance) instance.UI.enableFeatures([instance.UI.Feature.MultiTab]) instance.UI.disableElements([ "addTabButton", "multiTabsEmptyPage", ]) setViewerLoading(false) }) }) } }) } return () => { if (instance) { instance.UI.dispose() } setTimeout(() => cleanupHtmlStyle(), 500) } }, [open]) useEffect(() => { const loadDocuments = async () => { if (instance && files.length > 0) { const { UI } = instance const tabIds = [] for (const file of files) { const fileExtension = file.originalFileName.split('.').pop()?.toLowerCase() const options = { filename: file.originalFileName, ...(fileExtension === 'xlsx' || fileExtension === 'xls' ? { officeOptions: { formatOptions: { applyPageBreaksToSheet: true, }, }, } : {}), } try { const response = await fetch(file.storageUrl) const blob = await response.blob() const tab = await UI.TabManager.addTab(blob, options) tabIds.push(tab) } catch (error) { console.error(`Failed to load ${file.originalFileName}:`, error) toast.error(`Failed to load ${file.originalFileName}`) } } if (tabIds.length > 0) { await UI.TabManager.setActiveTab(tabIds[0]) } setFileSetLoading(false) } } loadDocuments() }, [instance, files]) const handleClose = async () => { if (!fileSetLoading) { if (instance) { try { await instance.UI.dispose() setInstance(null) } catch (e) { console.warn("dispose error", e) } } setTimeout(() => cleanupHtmlStyle(), 1000) onClose() } } return ( !val && handleClose()}> Preview {/* 첨부파일 미리보기 */}
{viewerLoading && (

문서 뷰어 로딩 중...

)}
) } export function ViewSubmissionDialog({ open, onOpenChange, submission }: ViewSubmissionDialogProps) { const [loading, setLoading] = useState(false) const [submissionDetail, setSubmissionDetail] = useState(null) const [downloadingFiles, setDownloadingFiles] = useState>(new Set()) const [viewerOpen, setViewerOpen] = useState(false) const [selectedFiles, setSelectedFiles] = useState>([]) useEffect(() => { if (open && submission.latestSubmissionId) { fetchSubmissionDetail() } }, [open, submission.latestSubmissionId]) const fetchSubmissionDetail = async () => { if (!submission.latestSubmissionId) return setLoading(true) try { const response = await fetch(`/api/stage-submissions/${submission.latestSubmissionId}`) if (response.ok) { const data = await response.json() setSubmissionDetail(data) } } catch (error) { console.error("Failed to fetch submission details:", error) toast.error("Failed to load submission details") } finally { setLoading(false) } } const handleDownload = async (file: any) => { setDownloadingFiles(prev => new Set(prev).add(file.id)) try { const result = await downloadFile( file.storageUrl, file.originalFileName, { action: 'download', showToast: true, showSuccessToast: true, onError: (error) => { console.error("Download failed:", error) toast.error(`Failed to download ${file.originalFileName}`) }, onSuccess: (fileName, fileSize) => { console.log(`Successfully downloaded ${fileName}`) } } ) if (!result.success) { console.error("Download failed:", result.error) } } finally { setDownloadingFiles(prev => { const newSet = new Set(prev) newSet.delete(file.id) return newSet }) } } // PDFTron으로 미리보기 처리 const handlePreview = (file: any) => { setSelectedFiles([{ id: file.id, originalFileName: file.originalFileName, storageUrl: file.storageUrl }]) setViewerOpen(true) } // 모든 파일 미리보기 const handlePreviewAll = () => { if (submissionDetail) { const files = submissionDetail.files.map(file => ({ id: file.id, originalFileName: file.originalFileName, storageUrl: file.storageUrl })) setSelectedFiles(files) setViewerOpen(true) } } const getStatusBadge = (status?: string) => { if (!status) return null const variant = status === "APPROVED" ? "success" : status === "REJECTED" ? "destructive" : status === "SUBMITTED" ? "default" : "secondary" return {status} } return ( <> View Submission Submission details and attached files {loading ? (
) : submissionDetail ? ( Details Files ({submissionDetail.files.length})

Revision

Rev. {submissionDetail.revisionNumber}

Status

{getStatusBadge(submissionDetail.submissionStatus)} {submissionDetail.reviewStatus && getStatusBadge(submissionDetail.reviewStatus)}

Submitted By

{submissionDetail.submittedBy}

Submitted At

{formatDateTime(submissionDetail.submittedAt)}
{submissionDetail.reviewComments && (

Review Comments

{submissionDetail.reviewComments}

)}
File Name Size Upload Date Sync Status Actions {submissionDetail.files.map((file) => { const isDownloading = downloadingFiles.has(file.id) return (
{file.originalFileName}
{formatFileSize(file.fileSize)} {formatDate(file.uploadedAt)} {file.syncStatus}
) })}
) : (
No submission found
)}
{/* PDFTron 문서 뷰어 다이얼로그 */} {viewerOpen && ( { setViewerOpen(false) setSelectedFiles([]) }} files={selectedFiles} /> )} ) }