'use client'; /** * Page admin "/admin/media" — gestionnaire central des médias. * * Listing + filtres + upload + détails (visibilité, alt, caption) + suppression. * Tout est gardé dans un seul fichier pour rester lisible — la complexité * grandit-elle, on extraira un MediaDetailsDrawer dédié. */ import { registerPage } from '../../admin/registry.js'; import { useState, useEffect, useCallback, useRef } from 'react'; import { Card, Button, Modal, Input, Textarea, Select } from '@zen/core/shared/components'; import { useToast } from '@zen/core/toast'; import { CloudUploadIcon, Delete02Icon, Copy01Icon } from '@zen/core/shared/icons'; import AdminHeader from '../../admin/components/AdminHeader.js'; import MediaGrid from '../components/MediaGrid.client.js'; import MediaFilters from '../components/MediaFilters.client.js'; const MEDIA_API = '/zen/api/media'; const VISIBILITY_OPTIONS = [ { value: 'private', label: 'Privé' }, { value: 'public', label: 'Public' }, ]; function MediaDetails({ media, onClose, onUpdated, onDeleted, canDelete, canEdit }) { const toast = useToast(); const [visibility, setVisibility] = useState(media.visibility); const [altText, setAltText] = useState(media.alt_text || ''); const [caption, setCaption] = useState(media.caption || ''); const [referenceCount, setReferenceCount] = useState(null); const [saving, setSaving] = useState(false); const [deleting, setDeleting] = useState(false); useEffect(() => { let cancelled = false; fetch(`${MEDIA_API}/${media.id}`, { credentials: 'include' }) .then(r => r.json()) .then(data => { if (!cancelled) setReferenceCount(data.referenceCount ?? 0); }) .catch(() => {}); return () => { cancelled = true; }; }, [media.id]); const url = `/zen/api/media/file/${media.slug}`; const isImage = media.mime_type?.startsWith('image/'); const fullUrl = typeof window !== 'undefined' ? `${window.location.origin}${url}` : url; const handleSave = async () => { setSaving(true); try { const response = await fetch(`${MEDIA_API}/${media.id}`, { method: 'PATCH', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ visibility, altText, caption }), }); const data = await response.json(); if (!response.ok) throw new Error(data.message || 'Échec de la mise à jour'); toast.success('Média mis à jour'); onUpdated?.(data.media); } catch (err) { toast.error(err.message); } finally { setSaving(false); } }; const handleDelete = async () => { if (!confirm('Supprimer définitivement ce média ?')) return; setDeleting(true); try { const response = await fetch(`${MEDIA_API}/${media.id}`, { method: 'DELETE', credentials: 'include', }); const data = await response.json(); if (!response.ok) throw new Error(data.message || 'Échec de la suppression'); toast.success('Média supprimé'); onDeleted?.(media.id); } catch (err) { toast.error(err.message); } finally { setDeleting(false); } }; const handleCopyUrl = async () => { try { await navigator.clipboard.writeText(fullUrl); toast.success('URL copiée'); } catch { toast.error('Impossible de copier'); } }; return ( {canDelete ? ( ) :
}
{canEdit && ( )}
} >
{isImage ? ( // eslint-disable-next-line @next/next/no-img-element {altText ) : (
{media.mime_type}
Ouvrir le fichier
)}
{}} disabled />

{visibility === 'public' ? 'Accessible sans connexion.' : 'Privé : accessible uniquement aux utilisateurs avec la permission media.view.'}

)}