'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 (
{visibility === 'public' ? 'Accessible sans connexion.' : 'Privé : accessible uniquement aux utilisateurs avec la permission media.view.'}
) : (
Ouvrir le fichier