import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import { useAuth } from '../context/AuthContext' import api from '../utils/api' import AdminPageNav from '../components/AdminPageNav' import { resolveMediaAssetFileUrl } from '../utils/exerciseMediaUrl' const LC_OPTIONS = [ { value: 'active', label: 'Aktiv' }, { value: 'trash_soft', label: 'Papierkorb (Stufe 1)' }, { value: 'trash_hidden', label: 'Ausgeblendet (Stufe 2)' }, { value: 'all', label: 'Alle (nicht purgiert)' }, ] function lcLabel(code) { const o = LC_OPTIONS.find((x) => x.value === code) return o ? o.label : code } export default function MediaLibraryPage() { const { user } = useAuth() const isPlatformAdmin = user?.role === 'admin' || user?.role === 'superadmin' const [lifecycle, setLifecycle] = useState('active') const [q, setQ] = useState('') const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState('') /** @type {Record} */ const [copyrightDrafts, setCopyrightDrafts] = useState({}) const [busyId, setBusyId] = useState(null) useEffect(() => { let cancelled = false const t = setTimeout(async () => { setLoading(true) setError('') try { const res = await api.listMediaAssets({ lifecycle, q: q.trim(), limit: 100, offset: 0, }) if (cancelled) return setItems(res.items || []) const nx = {} for (const it of res.items || []) { nx[it.id] = it.copyright_notice != null ? String(it.copyright_notice) : '' } setCopyrightDrafts(nx) } catch (e) { if (!cancelled) setError(e.message || String(e)) } finally { if (!cancelled) setLoading(false) } }, 320) return () => { cancelled = true clearTimeout(t) } }, [lifecycle, q]) const refresh = async () => { setLoading(true) setError('') try { const res = await api.listMediaAssets({ lifecycle, q: q.trim(), limit: 100, offset: 0, }) setItems(res.items || []) const nx = {} for (const it of res.items || []) { nx[it.id] = it.copyright_notice != null ? String(it.copyright_notice) : '' } setCopyrightDrafts(nx) } catch (e) { setError(e.message || String(e)) } finally { setLoading(false) } } const saveCopyright = async (id) => { const text = copyrightDrafts[id] if (text === undefined) return setBusyId(id) try { await api.patchMediaAsset(id, { copyright_notice: text }) await refresh() } catch (e) { alert(e.message || String(e)) } finally { setBusyId(null) } } const runLifecycle = async (id, action, confirmMsg) => { if (confirmMsg && !window.confirm(confirmMsg)) return setBusyId(id) try { await api.postMediaAssetLifecycle(id, action) await refresh() } catch (e) { alert(e.message || String(e)) } finally { setBusyId(null) } } return (
{isPlatformAdmin ? : null}

Medienbibliothek

Zu den Übungen {isPlatformAdmin ? ( Administration ) : null}

Sichtbare Medien gemäß deinen Rechten (privat, Verein, offiziell). Papierkorb- und Metadaten-Aktionen wie in der Übungsbearbeitung — hier zentral mit Filter.

setQ(e.target.value)} style={{ minWidth: '220px' }} />
{error ? (

{error}

) : null} {loading && items.length === 0 ?
: null} {!loading && !error && items.length === 0 ? (

Keine Einträge.

) : null}
{items.map((it) => { const mime = (it.mime_type || '').toLowerCase() const isImg = mime.startsWith('image/') const busy = busyId === it.id const lc = (it.lifecycle_state || '').toLowerCase() return (
Datei Sichtbarkeit Status Copyright Aktionen
{isImg ? ( { e.target.style.visibility = 'hidden' }} /> ) : (
{mime.includes('video') ? '▶' : '◆'}
)}
{it.original_filename || `Asset #${it.id}`}
ID {it.id} {it.byte_size != null ? ` · ${Math.round(it.byte_size / 1024)} KB` : ''}
{it.visibility} {lcLabel(lc)}