"use client"; import React, { useState, useEffect } 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 { FileInfo } from "./types"; import { Download, X, AlertTriangle, FileText, Trash2, ExternalLink } from "lucide-react"; import { formatFileSize } from "@/lib/utils"; import { API_BASE_URL } from "@/lib/api/client"; // Office 문서 렌더링을 위한 CDN 라이브러리 로드 const loadOfficeLibrariesFromCDN = async () => { if (typeof window === 'undefined') return { XLSX: null, mammoth: null }; try { // XLSX 라이브러리가 이미 로드되어 있는지 확인 if (!(window as any).XLSX) { await new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js'; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } // mammoth 라이브러리가 이미 로드되어 있는지 확인 if (!(window as any).mammoth) { await new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.4.2/mammoth.browser.min.js'; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } return { XLSX: (window as any).XLSX, mammoth: (window as any).mammoth }; } catch (error) { console.error('Office 라이브러리 CDN 로드 실패:', error); return { XLSX: null, mammoth: null }; } }; interface FileViewerModalProps { file: FileInfo | null; isOpen: boolean; onClose: () => void; onDownload?: (file: FileInfo) => void; onDelete?: (file: FileInfo) => void; } /** * 파일 뷰어 모달 컴포넌트 */ export const FileViewerModal: React.FC = ({ file, isOpen, onClose, onDownload, onDelete, }) => { const [previewUrl, setPreviewUrl] = useState(null); const [previewError, setPreviewError] = useState(null); const [isLoading, setIsLoading] = useState(false); const [renderedContent, setRenderedContent] = useState(null); // Office 문서를 CDN 라이브러리로 렌더링하는 함수 const renderOfficeDocument = async (blob: Blob, fileExt: string, fileName: string) => { try { setIsLoading(true); // CDN에서 라이브러리 로드 const { XLSX, mammoth } = await loadOfficeLibrariesFromCDN(); if (fileExt === "docx" && mammoth) { // Word 문서 렌더링 const arrayBuffer = await blob.arrayBuffer(); const result = await mammoth.convertToHtml({ arrayBuffer }); const htmlContent = `

📄 ${fileName}

${result.value || '내용을 읽을 수 없습니다.'}
`; setRenderedContent(htmlContent); return true; } else if (["xlsx", "xls"].includes(fileExt) && XLSX) { // Excel 문서 렌더링 const arrayBuffer = await blob.arrayBuffer(); const workbook = XLSX.read(arrayBuffer, { type: 'array' }); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; const html = XLSX.utils.sheet_to_html(worksheet, { table: { className: 'excel-table' } }); const htmlContent = `

📊 ${fileName}

시트: ${sheetName}

${html}
`; setRenderedContent(htmlContent); return true; } else if (fileExt === "doc") { // .doc 파일은 .docx로 변환 안내 const htmlContent = `

📄 ${fileName}

.doc 파일은 .docx로 변환 후 업로드해주세요.

(.docx 파일만 미리보기 지원)

`; setRenderedContent(htmlContent); return true; } else if (["ppt", "pptx"].includes(fileExt)) { // PowerPoint는 미리보기 불가 안내 const htmlContent = `

📑 ${fileName}

PowerPoint 파일은 브라우저에서 미리보기할 수 없습니다.

파일을 다운로드하여 확인해주세요.

`; setRenderedContent(htmlContent); return true; } return false; // 지원하지 않는 형식 } catch (error) { console.error("Office 문서 렌더링 오류:", error); const htmlContent = `
Office 문서를 읽을 수 없습니다.
파일이 손상되었거나 지원하지 않는 형식일 수 있습니다.
`; setRenderedContent(htmlContent); return true; // 오류 메시지라도 표시 } finally { setIsLoading(false); } }; // 파일이 변경될 때마다 미리보기 URL 생성 useEffect(() => { if (!file || !isOpen) { setPreviewUrl(null); setPreviewError(null); setRenderedContent(null); return; } setIsLoading(true); setPreviewError(null); // 로컬 파일인 경우 if (file._file) { const url = URL.createObjectURL(file._file); setPreviewUrl(url); setIsLoading(false); return () => URL.revokeObjectURL(url); } let cleanup: (() => void) | undefined; // 서버 파일인 경우 - 미리보기 API 호출 const generatePreviewUrl = async () => { try { const fileExt = file.fileExt.toLowerCase(); // 미리보기 지원 파일 타입 정의 const imageExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg"]; const documentExtensions = ["pdf","doc", "docx", "xls", "xlsx", "ppt", "pptx", "rtf", "odt", "ods", "odp", "hwp", "hwpx", "hwpml", "hcdt", "hpt", "pages", "numbers", "keynote"]; const textExtensions = ["txt", "md", "json", "xml", "csv"]; const mediaExtensions = ["mp4", "webm", "ogg", "mp3", "wav"]; const supportedExtensions = [ ...imageExtensions, ...documentExtensions, ...textExtensions, ...mediaExtensions ]; if (supportedExtensions.includes(fileExt)) { // 이미지나 PDF는 인증된 요청으로 Blob 생성 if (imageExtensions.includes(fileExt) || fileExt === "pdf") { try { // 인증된 요청으로 파일 데이터 가져오기 const response = await fetch(`${API_BASE_URL}/files/preview/${file.objid}`, { headers: { "Authorization": `Bearer ${localStorage.getItem("authToken")}`, }, }); if (response.ok) { const blob = await response.blob(); const blobUrl = URL.createObjectURL(blob); setPreviewUrl(blobUrl); // 컴포넌트 언마운트 시 URL 정리를 위해 cleanup 함수 저장 cleanup = () => URL.revokeObjectURL(blobUrl); } else { throw new Error(`HTTP ${response.status}`); } } catch (error) { console.error("파일 미리보기 로드 실패:", error); setPreviewError("파일을 불러올 수 없습니다. 권한을 확인해주세요."); } } else if (documentExtensions.includes(fileExt)) { // Office 문서는 OnlyOffice 또는 안정적인 뷰어 사용 try { const response = await fetch(`${API_BASE_URL}/files/preview/${file.objid}`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('authToken')}`, }, }); if (response.ok) { const blob = await response.blob(); const blobUrl = URL.createObjectURL(blob); // Office 문서를 위한 특별한 처리 - CDN 라이브러리 사용 if (["doc", "docx", "xls", "xlsx", "ppt", "pptx"].includes(fileExt)) { // CDN 라이브러리로 클라이언트 사이드 렌더링 시도 try { const renderSuccess = await renderOfficeDocument(blob, fileExt, file.realFileName); if (!renderSuccess) { // 렌더링 실패 시 Blob URL 사용 setPreviewUrl(blobUrl); } } catch (error) { console.error("Office 문서 렌더링 중 오류:", error); // 오류 발생 시 Blob URL 사용 setPreviewUrl(blobUrl); } } else { // 기타 문서는 직접 Blob URL 사용 setPreviewUrl(blobUrl); } return () => URL.revokeObjectURL(blobUrl); // Cleanup function } else { throw new Error(`HTTP ${response.status}`); } } catch (error) { console.error("Office 문서 로드 실패:", error); // 오류 발생 시 다운로드 옵션 제공 setPreviewError(`${fileExt.toUpperCase()} 문서를 미리보기할 수 없습니다. 다운로드하여 확인해주세요.`); } } else { // 기타 파일은 다운로드 URL 사용 const url = `${API_BASE_URL.replace("/api", "")}/api/files/download/${file.objid}`; setPreviewUrl(url); } } else { // 지원하지 않는 파일 타입 setPreviewError(`${file.fileExt.toUpperCase()} 파일은 미리보기를 지원하지 않습니다.`); } } catch (error) { console.error("미리보기 URL 생성 오류:", error); setPreviewError("미리보기를 불러오는데 실패했습니다."); } finally { setIsLoading(false); } }; generatePreviewUrl(); // cleanup 함수 반환 return () => { if (cleanup) { cleanup(); } }; }, [file, isOpen]); if (!file) return null; // 파일 타입별 미리보기 컴포넌트 const renderPreview = () => { if (isLoading) { return (
); } if (previewError) { return (

미리보기 불가

{previewError}

); } const fileExt = file.fileExt.toLowerCase(); // 이미지 파일 if (["jpg", "jpeg", "png", "gif", "webp", "svg"].includes(fileExt)) { return (
{file.realFileName} { console.error("이미지 로드 오류:", previewUrl, e); setPreviewError("이미지를 불러올 수 없습니다. 파일이 손상되었거나 서버에서 접근할 수 없습니다."); }} onLoad={() => { console.log("이미지 로드 성공:", previewUrl); }} />
); } // 텍스트 파일 if (["txt", "md", "json", "xml", "csv"].includes(fileExt)) { return (