"use client"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Upload, Search, ImageIcon, Film, Music, FileText, Link2, Trash2, Loader2, Copy, } from "lucide-react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { deleteMediaLibraryItem, getMediaLibrary, parseMediaLibraryList, registerMediaLibrary, type MediaLibraryItem, uploadMediaLibraryFile, } from "@/service/media-library"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Label } from "@/components/ui/label"; const getFileIcon = (type: string) => { switch (type) { case "video": return ; case "audio": return ; case "document": return ; default: return ; } }; function looksLikeImageUrl(url: string): boolean { const path = url.split("?")[0].split("#")[0].toLowerCase(); return /\.(jpe?g|png|gif|webp|svg|bmp|avif)$/i.test(path); } function isDisplayableImage(file: MediaLibraryItem): boolean { return file.file_category === "image" || looksLikeImageUrl(file.public_url); } function LibraryMediaPreview({ file }: { file: MediaLibraryItem }) { const [broken, setBroken] = useState(false); const showImg = isDisplayableImage(file) && !broken && Boolean(file.public_url?.trim()); if (showImg) { return ( // eslint-disable-next-line @next/next/no-img-element -- dynamic API/MinIO URLs {file.original_filename setBroken(true)} /> ); } return (
{getFileIcon(file.file_category)}
); } const getBadgeStyle = (type: string) => { switch (type) { case "video": return "bg-purple-100 text-purple-600"; case "audio": return "bg-green-100 text-green-600"; case "document": return "bg-amber-100 text-amber-800"; default: return "bg-blue-100 text-blue-600"; } }; function formatBytes(n: number | null | undefined): string { if (n == null || Number.isNaN(n)) return "—"; if (n < 1024) return `${n} B`; if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`; return `${(n / (1024 * 1024)).toFixed(1)} MB`; } function formatDate(iso: string): string { try { return new Date(iso).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric", }); } catch { return iso; } } const SOURCE_OPTIONS = [ { value: "all", label: "Semua sumber" }, { value: "article_file", label: "Artikel" }, { value: "cms", label: "Content Website" }, { value: "upload", label: "Upload langsung" }, ]; export default function MediaLibrary() { const [search, setSearch] = useState(""); const [sourceFilter, setSourceFilter] = useState("all"); const [page, setPage] = useState(1); const [items, setItems] = useState([]); const [totalPage, setTotalPage] = useState(1); const [totalCount, setTotalCount] = useState(null); const [loading, setLoading] = useState(true); const [uploading, setUploading] = useState(false); const [registerUrl, setRegisterUrl] = useState(""); const [registerLabel, setRegisterLabel] = useState(""); const [registering, setRegistering] = useState(false); const fileRef = useRef(null); const load = useCallback(async () => { setLoading(true); const res = await getMediaLibrary({ page, limit: 24, q: search.trim() || undefined, source_type: sourceFilter === "all" ? undefined : sourceFilter, }); const { items: rows, meta } = parseMediaLibraryList(res); setItems(rows); setTotalPage(Math.max(1, meta?.totalPage ?? 1)); if (typeof meta?.count === "number") setTotalCount(meta.count); setLoading(false); }, [page, search, sourceFilter]); useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect -- fetch on deps void load(); }, [load]); const stats = useMemo(() => { const images = items.filter((i) => i.file_category === "image").length; const videos = items.filter((i) => i.file_category === "video").length; const audio = items.filter((i) => i.file_category === "audio").length; const docs = items.filter((i) => i.file_category === "document").length; return [ { title: "Total (filter)", value: totalCount ?? "—" }, { title: "Gambar (halaman)", value: images }, { title: "Video (halaman)", value: videos }, { title: "Audio/Dokumen (halaman)", value: audio + docs }, ]; }, [items, totalCount]); const copyLink = async (url: string) => { try { await navigator.clipboard.writeText(url); } catch { window.prompt("Salin URL:", url); } }; const onSelectFiles = async (e: React.ChangeEvent) => { const files = e.target.files; if (!files?.length) return; setUploading(true); for (let i = 0; i < files.length; i++) { const fd = new FormData(); fd.append("file", files[i]); await uploadMediaLibraryFile(fd); } setUploading(false); e.target.value = ""; setPage(1); await load(); }; const onRegisterManual = async () => { const url = registerUrl.trim(); if (!url) return; setRegistering(true); await registerMediaLibrary({ public_url: url, source_type: "upload", source_label: registerLabel.trim() || "manual_register", }); setRegisterUrl(""); setRegisterLabel(""); setRegistering(false); setPage(1); await load(); }; const onDelete = async (id: number) => { if (!confirm("Hapus entri dari Media Library? (file di storage tidak dihapus)")) return; await deleteMediaLibraryItem(id); await load(); }; return (

Media Library

Katalog URL media dari artikel, Content Website, dan upload admin. File fisik tetap satu; yang disimpan di sini adalah tautan publik untuk pencarian dan salin cepat.

Upload ke Media Library

File disimpan di MinIO (prefix cms/media-library) dan URL viewer otomatis didaftarkan di tabel ini.

void onSelectFiles(e)} />

Gambar, video, audio, PDF/DOC/TXT (satu file per request batch di atas)

setRegisterUrl(e.target.value)} />
setRegisterLabel(e.target.value)} />
{stats.map((item, i) => (

{item.value}

{item.title}

))}
setSearch(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { setPage(1); void load(); } }} />
{loading ? (
) : items.length === 0 ? (

Belum ada media.

) : (
{items.map((file) => (

{file.original_filename || "—"}

{file.file_category} {formatBytes(file.size_bytes ?? undefined)}

{file.source_type} {file.source_label ? ` · ${file.source_label}` : ""}

{formatDate(file.created_at)}

))}
)} {totalPage > 1 ? (
Halaman {page} / {totalPage}
) : null}
); }