"use client"; /* eslint-disable @next/next/no-img-element */ import { useEffect, useMemo, useState, type ChangeEvent } from "react"; import type { MediaFile } from "@/lib/admin/media"; type SortKey = "date" | "name" | "size"; type FilterKey = "all" | "images" | "pdf"; type ApiResponse = { message?: string; files?: MediaFile[]; }; function formatBytes(size: number) { if (size < 1024) return `${size} B`; if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`; return `${(size / 1024 / 1024).toFixed(1)} MB`; } function sortFiles(files: MediaFile[], sort: SortKey) { return [...files].sort((left, right) => { if (sort === "name") return left.name.localeCompare(right.name); if (sort === "size") return right.size - left.size; return right.uploadedAt.localeCompare(left.uploadedAt); }); } export default function MediaManager({ initialFiles }: { initialFiles: MediaFile[] }) { const [files, setFiles] = useState(initialFiles); const [filter, setFilter] = useState("all"); const [sort, setSort] = useState("date"); const [query, setQuery] = useState(""); const [toast, setToast] = useState(""); const [error, setError] = useState(""); const [pending, setPending] = useState(false); const [preview, setPreview] = useState(null); const [zoom, setZoom] = useState(1); useEffect(() => { function closeOnEscape(event: KeyboardEvent) { if (event.key === "Escape") { setPreview(null); setZoom(1); } } window.addEventListener("keydown", closeOnEscape); return () => window.removeEventListener("keydown", closeOnEscape); }, []); const visibleFiles = useMemo(() => { const normalizedQuery = query.trim().toLowerCase(); const filtered = files.filter((file) => { if (filter === "images" && !file.isImage) return false; if (filter === "pdf" && !file.isPdf) return false; if (!normalizedQuery) return true; return file.name.toLowerCase().includes(normalizedQuery) || file.extension.toLowerCase().includes(normalizedQuery); }); return sortFiles(filtered, sort); }, [files, filter, query, sort]); async function upload(event: ChangeEvent) { const file = event.currentTarget.files?.[0]; if (!file) return; setPending(true); setToast(""); setError(""); const form = new FormData(); form.set("file", file); try { const response = await fetch("/api/admin/media", { method: "POST", body: form }); const result = await response.json() as ApiResponse; if (!response.ok) throw new Error(result.message ?? "Upload fehlgeschlagen."); if (result.files) setFiles(result.files); setToast(result.message ?? "Datei erfolgreich hochgeladen."); event.currentTarget.value = ""; } catch (err) { setError(err instanceof Error ? err.message : "Upload fehlgeschlagen."); } finally { setPending(false); } } async function refresh() { const response = await fetch("/api/admin/media", { method: "GET" }); const result = await response.json() as ApiResponse; if (response.ok && result.files) setFiles(result.files); } async function remove(file: MediaFile) { if (!window.confirm(`Datei wirklich löschen?\n\n${file.name}`)) return; setToast(""); setError(""); try { const response = await fetch("/api/admin/media", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: file.name }), }); const result = await response.json() as ApiResponse; if (!response.ok) throw new Error(result.message ?? "Datei konnte nicht gelöscht werden."); if (result.files) setFiles(result.files); setToast(result.message ?? "Datei gelöscht."); } catch (err) { setError(err instanceof Error ? err.message : "Datei konnte nicht gelöscht werden."); } } async function copyUrl(file: MediaFile) { try { await navigator.clipboard.writeText(file.url); setToast("Relative URL wurde kopiert."); setError(""); } catch { setError("URL konnte nicht kopiert werden."); } } function openPreview(file: MediaFile) { if (file.isImage) { setPreview(file); setZoom(1); return; } window.open(file.url, "_blank", "noopener,noreferrer"); } return ( <> {toast &&

{toast}

} {error &&

{error}

}

Uploads

{visibleFiles.length === 0 ? (

Noch keine Medien vorhanden

Hochgeladene Bilder und PDF-Dateien erscheinen hier mit Vorschau, Metadaten und Aktionen.

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

{file.name}

Typ
{file.extension}
Größe
{formatBytes(file.size)}
Upload
{new Date(file.uploadedAt).toLocaleString("de-DE")}
Download
))}
)}
{preview && (
{preview.name}
)} ); }