"use client"; /* eslint-disable @next/next/no-img-element */ import type { MediaKind } from "@prisma/client"; import { FileType2, Grid2x2, ImageIcon, LayoutList, LoaderCircle, Trash2, Upload } from "lucide-react"; import Link from "next/link"; import { useEffect, useMemo, useState } from "react"; import { useFormStatus } from "react-dom"; import { useSearchParams } from "next/navigation"; import type { MediaAssetView } from "@/lib/media"; import { MotionFade } from "@/components/motion-fade"; import { AppCard } from "@/components/ui/app-card"; import { Button } from "@/components/ui/button"; import { CardContent } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table"; import { cn } from "@/lib/utils"; import { createMediaAssetAction, deleteMediaAssetAction } from "@/app/_admin/media/actions"; const copy = { addMedia: "Upload Media File", addMediaDescription: "Datei hochladen und direkt in die Media Library uebernehmen.", addMediaHint: "PNG, JPG, GIF oder PDF bis 5 MB", label: "Bezeichnung", kind: "Typ", image: "Image", document: "Document", upload: "Upload", uploading: "Uploading...", selectFile: "Datei auswaehlen", empty: "Noch keine Bilder vorhanden.", grid: "Grid", list: "List", detailsButton: "Details", open: "Open", delete: "Delete", deleteConfirm: "Delete this media file?", usages: "Verwendungen", source: "Quelle", fileName: "Dateiname", url: "URL", mimeType: "MIME Type", size: "Dateigroesse", createdAt: "Erstellt", close: "Close", details: "Bilddetails", detailsDescription: "Metadaten und Verwendungen der ausgewaehlten Datei.", }; function formatFileSize(size: number | null) { if (!size) { return "—"; } if (size < 1024) { return `${size} B`; } if (size < 1024 * 1024) { return `${(size / 1024).toFixed(1)} KB`; } return `${(size / (1024 * 1024)).toFixed(1)} MB`; } function formatCreatedAt(value: string | Date) { const date = value instanceof Date ? value : new Date(value); return new Intl.DateTimeFormat("de-DE", { dateStyle: "medium", timeStyle: "short", }).format(date); } function getAbsoluteUrl(path: string) { if (/^https?:\/\//.test(path)) { return path; } if (typeof window === "undefined") { return path; } return new URL(path, window.location.origin).toString(); } function UploadSubmitButton() { const { pending } = useFormStatus(); return ( {pending ? : } {pending ? copy.uploading : copy.upload} ); } function MediaUploadDialog({ open, onOpenChange, }: { open: boolean; onOpenChange: (open: boolean) => void; }) { const [kind, setKind] = useState("IMAGE"); const [label, setLabel] = useState(""); const [isLabelDirty, setIsLabelDirty] = useState(false); useEffect(() => { if (!open) { setKind("IMAGE"); setLabel(""); setIsLabelDirty(false); } }, [open]); function handleFileChange(event: React.ChangeEvent) { const file = event.target.files?.[0]; if (!file || isLabelDirty) { return; } setLabel(file.name.replace(/\.[^.]+$/, "")); } return ( {copy.addMedia} {copy.addMediaHint} {copy.addMedia} {copy.addMedia} {copy.addMediaDescription} {copy.label} { setLabel(event.target.value); setIsLabelDirty(true); }} placeholder={copy.label} /> {copy.kind} {[ { value: "IMAGE" as const, label: copy.image, icon: ImageIcon }, { value: "DOCUMENT" as const, label: copy.document, icon: FileType2 }, ].map((option) => { const Icon = option.icon; const isActive = kind === option.value; return ( setKind(option.value)} className={cn( "inline-flex items-center gap-2 rounded-nested border px-4 py-2 text-sm transition-colors", isActive ? "border-input bg-primary text-primary-foreground" : "border-input bg-background text-foreground/75 hover:bg-accent hover:text-accent-foreground", )} > {option.label} ); })} {copy.selectFile} {copy.addMediaHint} ); } function MediaDetailsDialog({ asset, open, onOpenChange, }: { asset: MediaAssetView | null; open: boolean; onOpenChange: (open: boolean) => void; }) { if (!asset) { return null; } const absoluteUrl = getAbsoluteUrl(asset.url); return ( {asset.label} {copy.detailsDescription} {copy.kind} {asset.kind} {copy.source} {asset.source} {copy.fileName} {asset.fileName || "—"} {copy.mimeType} {asset.mimeType || "—"} {copy.size} {formatFileSize(asset.size)} {copy.createdAt} {formatCreatedAt(asset.createdAt)} {asset.usages.length > 0 ? ( {asset.usages.length} {copy.usages} {asset.usages.map((usage) => ( {usage.usageType} / {usage.entityType} / {usage.fieldKey} ))} ) : null} {copy.url} {absoluteUrl} {copy.open} 0} className="w-full sm:w-auto" onClick={(event) => { if (!window.confirm(copy.deleteConfirm)) { event.preventDefault(); } }} > {copy.delete} ); } export function MediaLibraryManager({ mediaAssets, }: { mediaAssets: MediaAssetView[]; }) { const searchParams = useSearchParams(); const [isUploadDialogOpen, setIsUploadDialogOpen] = useState(false); const [selectedAssetId, setSelectedAssetId] = useState(null); const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); const imageAssets = useMemo( () => mediaAssets.filter((asset) => asset.kind === "IMAGE"), [mediaAssets], ); const selectedAsset = imageAssets.find((asset) => asset.id === selectedAssetId) ?? null; useEffect(() => { if (searchParams.has("success")) { setIsUploadDialogOpen(false); setSelectedAssetId(null); } }, [searchParams]); return ( {imageAssets.length > 0 ? ( setViewMode("grid")} className={cn( "inline-flex items-center gap-2 rounded-nested px-3 py-2 text-sm transition-colors", viewMode === "grid" ? "bg-primary text-primary-foreground" : "text-foreground/75 hover:bg-accent hover:text-accent-foreground", )} > {copy.grid} setViewMode("list")} className={cn( "inline-flex items-center gap-2 rounded-nested px-3 py-2 text-sm transition-colors", viewMode === "list" ? "bg-primary text-primary-foreground" : "text-foreground/75 hover:bg-accent hover:text-accent-foreground", )} > {copy.list} ) : null} {imageAssets.length > 0 ? ( viewMode === "grid" ? ( {imageAssets.map((asset, index) => ( setSelectedAssetId(asset.id)} className="group text-left" > ))} ) : ( {imageAssets.map((asset, index) => ( setSelectedAssetId(asset.id)} className="group h-24 w-full overflow-hidden rounded-nested border bg-muted/30 sm:w-24" > {asset.label} {formatFileSize(asset.size)} {formatCreatedAt(asset.createdAt)} setSelectedAssetId(asset.id)} > {copy.detailsButton} {copy.open} ))} ) ) : ( {copy.empty} )} { if (!open) { setSelectedAssetId(null); } }} /> ); }
{copy.addMedia}
{copy.addMediaHint}
{asset.usages.length} {copy.usages}
{usage.usageType} / {usage.entityType} / {usage.fieldKey}
{copy.url}
{absoluteUrl}
{asset.label}
{formatFileSize(asset.size)}
{formatCreatedAt(asset.createdAt)}