"use client"; import React, { useState, useEffect, useMemo } from "react"; import { MagnifyingGlassIcon, EyeIcon, ArrowDownTrayIcon, PhotoIcon, VideoCameraIcon, MusicalNoteIcon, DocumentIcon, Squares2X2Icon, ListBulletIcon, } from "@heroicons/react/24/outline"; import { MediaFilter, MediaFilterOptions } from "./MediaFilter"; import { MediaSort, SortOptions } from "./MediaSort"; import { MediaPagination } from "./MediaPagination"; import { MediaPreview } from "./MediaPreview"; interface MediaFile { id: string; fileName: string; originalName: string; mimeType: string; size: number; url: string; thumbnailUrl?: string; createdAt: string; downloadCount?: number; viewCount?: number; tags?: string[]; description?: string; dimensions?: { width: number; height: number; }; metadata?: Record; } interface MediaLibraryProps { files?: MediaFile[]; onFileSelect?: (file: MediaFile) => void; onFileUpdate?: (file: MediaFile) => void; onFileDelete?: (fileId: string) => void; selectable?: boolean; multiple?: boolean; className?: string; } type ViewMode = "grid" | "list"; export function MediaLibrary({ files = [], onFileSelect, onFileUpdate, onFileDelete, selectable = false, multiple = false, className = "", }: MediaLibraryProps) { // State management const [filters, setFilters] = useState({ fileTypes: [], dateRange: {}, sizeRange: {}, tags: [], searchTerm: "", }); const [sortOptions, setSortOptions] = useState({ field: "date", direction: "desc", }); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(24); const [viewMode, setViewMode] = useState("grid"); const [selectedFiles, setSelectedFiles] = useState([]); const [previewFile, setPreviewFile] = useState(null); const [isPreviewOpen, setIsPreviewOpen] = useState(false); // Available tags for filtering const availableTags = useMemo(() => { const tagSet = new Set(); files.forEach((file) => { file.tags?.forEach((tag) => tagSet.add(tag)); }); return Array.from(tagSet); }, [files]); // Filter and sort files const filteredAndSortedFiles = useMemo(() => { const filtered = files.filter((file) => { // Search term filter if (filters.searchTerm) { const searchLower = filters.searchTerm.toLowerCase(); if ( !file.originalName.toLowerCase().includes(searchLower) && !file.description?.toLowerCase().includes(searchLower) ) { return false; } } // File type filter if (filters.fileTypes.length > 0) { const hasMatchingType = filters.fileTypes.some((type) => { switch (type) { case "image": return file.mimeType.startsWith("image/"); case "video": return file.mimeType.startsWith("video/"); case "audio": return file.mimeType.startsWith("audio/"); case "document": return ( file.mimeType.includes("pdf") || file.mimeType.includes("document") || file.mimeType.includes("text") ); default: return false; } }); if (!hasMatchingType) return false; } // Date range filter if (filters.dateRange.start || filters.dateRange.end) { const fileDate = new Date(file.createdAt); if ( filters.dateRange.start && fileDate < new Date(filters.dateRange.start) ) { return false; } if ( filters.dateRange.end && fileDate > new Date(filters.dateRange.end) ) { return false; } } // Size range filter if ( filters.sizeRange.min !== undefined && file.size < filters.sizeRange.min ) { return false; } if ( filters.sizeRange.max !== undefined && file.size > filters.sizeRange.max ) { return false; } // Tags filter if (filters.tags.length > 0) { const hasMatchingTag = filters.tags.some((tag) => file.tags?.includes(tag) ); if (!hasMatchingTag) return false; } return true; }); // Sort files filtered.sort((a, b) => { let aValue: string | number; let bValue: string | number; switch (sortOptions.field) { case "name": aValue = a.originalName.toLowerCase(); bValue = b.originalName.toLowerCase(); break; case "size": aValue = a.size; bValue = b.size; break; case "date": aValue = new Date(a.createdAt).getTime(); bValue = new Date(b.createdAt).getTime(); break; case "type": aValue = a.mimeType.toLowerCase(); bValue = b.mimeType.toLowerCase(); break; case "downloads": aValue = a.downloadCount || 0; bValue = b.downloadCount || 0; break; case "views": aValue = a.viewCount || 0; bValue = b.viewCount || 0; break; default: aValue = a.originalName.toLowerCase(); bValue = b.originalName.toLowerCase(); } if (typeof aValue === "string" && typeof bValue === "string") { return sortOptions.direction === "asc" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue); } return sortOptions.direction === "asc" ? (aValue as number) - (bValue as number) : (bValue as number) - (aValue as number); }); return filtered; }, [files, filters, sortOptions]); // Pagination const totalPages = Math.ceil(filteredAndSortedFiles.length / itemsPerPage); const paginatedFiles = filteredAndSortedFiles.slice( (currentPage - 1) * itemsPerPage, currentPage * itemsPerPage ); // Reset page when filters change useEffect(() => { setCurrentPage(1); }, [filters, sortOptions, itemsPerPage]); // File operations const handleFileClick = (file: MediaFile) => { if (selectable) { if (multiple) { setSelectedFiles((prev) => prev.includes(file.id) ? prev.filter((id) => id !== file.id) : [...prev, file.id] ); } else { setSelectedFiles([file.id]); } onFileSelect?.(file); } else { setPreviewFile(file); setIsPreviewOpen(true); } }; const handlePreview = (file: MediaFile) => { setPreviewFile(file); setIsPreviewOpen(true); }; const handleDownload = (file: MediaFile) => { const link = document.createElement("a"); link.href = file.url; link.download = file.originalName; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; // Utility functions const formatFileSize = (bytes: number): string => { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; }; const formatDate = (dateString: string): string => { return new Date(dateString).toLocaleDateString("ko-KR", { year: "numeric", month: "short", day: "numeric", }); }; const getFileIcon = (mimeType: string) => { if (mimeType.startsWith("image/")) return PhotoIcon; if (mimeType.startsWith("video/")) return VideoCameraIcon; if (mimeType.startsWith("audio/")) return MusicalNoteIcon; return DocumentIcon; }; const getThumbnail = (file: MediaFile) => { if (file.mimeType.startsWith("image/")) { return file.thumbnailUrl || file.url; } return file.thumbnailUrl; }; return (
{/* 헤더 - 검색, 필터, 정렬 */}
{/* 검색바 */}
setFilters({ ...filters, searchTerm: e.target.value }) } className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" />
{/* 필터 및 정렬 컨트롤 */}
{/* 뷰 모드 및 결과 정보 */}
{filteredAndSortedFiles.length}개 파일
{/* 파일 목록 */} {paginatedFiles.length === 0 ? (

조건에 맞는 파일이 없습니다.

) : viewMode === "grid" ? (
{paginatedFiles.map((file) => { const FileIcon = getFileIcon(file.mimeType); const thumbnail = getThumbnail(file); const isSelected = selectedFiles.includes(file.id); return (
handleFileClick(file)} > {/* 썸네일 */}
{thumbnail ? ( {file.originalName} { const target = e.target as HTMLImageElement; target.style.display = "none"; target.nextElementSibling?.classList.remove("hidden"); }} /> ) : null}
{/* 파일 정보 */}

{file.originalName}

{formatFileSize(file.size)}

{/* 호버 액션 */}
{/* 선택 표시 */} {selectable && isSelected && (
)}
); })}
) : ( // 리스트 뷰
파일명
크기
형식
업로드 날짜
액션
{paginatedFiles.map((file) => { const FileIcon = getFileIcon(file.mimeType); const isSelected = selectedFiles.includes(file.id); return (
handleFileClick(file)} >

{file.originalName}

{file.description && (

{file.description}

)}
{formatFileSize(file.size)}
{file.mimeType.split("/")[1]?.toUpperCase()}
{formatDate(file.createdAt)}
); })}
)} {/* 페이지네이션 */} {totalPages > 1 && ( )} {/* 미리보기 모달 */} { setIsPreviewOpen(false); setPreviewFile(null); }} onUpdate={onFileUpdate} onDelete={onFileDelete} />
); }