import { useEffect, useState, useCallback, useRef, useMemo } from 'react' import { Link } from 'react-router-dom' import { LayoutGrid, List, MoreVertical, X, Globe, Users, Lock, CheckCircle2, Archive, CircleDot, FilePenLine, Copyright, Image, Video, FileText, File, Upload, ScrollText, } from 'lucide-react' import { useAuth } from '../context/AuthContext' import api from '../utils/api' import { activeClubMemberships } from '../utils/activeClub' import { resolveMediaAssetFileUrl } from '../utils/exerciseMediaUrl' import RightsDeclarationDialog from '../components/RightsDeclarationDialog' const LC_OPTIONS = [ { value: 'active', label: 'Aktiv' }, { value: 'trash_soft', label: 'Papierkorb (1)' }, { value: 'trash_hidden', label: 'Ausgeblendet (2)' }, { value: 'all', label: 'Alle' }, ] const VIS_OPTIONS = [ { value: 'private', label: 'Privat' }, { value: 'club', label: 'Verein' }, { value: 'official', label: 'Offiziell' }, ] const MEDIA_KIND_OPTIONS = [ { value: 'all', label: 'Alle Typen' }, { value: 'image', label: 'Bild' }, { value: 'video', label: 'Video' }, { value: 'pdf', label: 'PDF' }, { value: 'other', label: 'Sonstiges' }, ] const LC_STATUS_LABELS = { active: 'Aktiv', trash_soft: 'Papierkorb (1)', trash_hidden: 'Ausgeblendet (2)', } function visibilityUiLabel(v) { const o = VIS_OPTIONS.find((x) => x.value === (v || '').toLowerCase()) return o ? o.label : v || '—' } function MediaCardScopeStatus({ visibility, lifecycleState }) { const v = (visibility || 'private').toLowerCase() const lc = (lifecycleState || 'active').toLowerCase() const visLabel = visibilityUiLabel(v) const lcLabel = LC_STATUS_LABELS[lc] || lcLabelFromOptions(lc) const tip = `${visLabel} · ${lcLabel}` let VisIcon = Lock if (v === 'official') VisIcon = Globe else if (v === 'club') VisIcon = Users let LcIcon = FilePenLine if (lc === 'active') LcIcon = CheckCircle2 else if (lc === 'archived' || lc === 'trash_hidden') LcIcon = Archive else if (lc === 'in_review' || lc === 'trash_soft') LcIcon = CircleDot return (
·
) } function lcLabelFromOptions(code) { const o = LC_OPTIONS.find((x) => x.value === code) return o ? o.label : code } function lcLabel(code) { return lcLabelFromOptions(code) } function parseTagsInput(s) { return String(s || '') .split(/[,;\n]+/) .map((x) => x.trim()) .filter(Boolean) } function MediaUsageBlock({ usage, compact }) { const u = usage || { exercises: [], training_units: [] } const ex = u.exercises || [] const tus = u.training_units || [] if (!ex.length && !tus.length) return {compact ? '—' : 'Noch in keiner Übung / Einheit verknüpft.'} return (
{ex.length ? (
Übungen{' '} {ex.map((e) => ( {e.title.length > (compact ? 18 : 40) ? `${e.title.slice(0, compact ? 18 : 40)}…` : e.title} ))}
) : null} {tus.length ? (
Trainings­einheiten{' '} {tus.map((t) => { const label = [t.planned_date, (t.group_name || '').trim()].filter(Boolean).join(' · ') || `Einheit #${t.id}` const short = label.length > (compact ? 20 : 36) ? `${label.slice(0, compact ? 20 : 36)}…` : label return ( {short} ) })}
) : null}
) } function uploaderLabel(it, viewer) { if (!viewer?.show_uploader_meta) return null const n = (it.uploader_name || '').trim() const e = (it.uploader_email || '').trim() if (n) return n if (e) return e return it.uploaded_by_profile_id != null ? `Profil #${it.uploaded_by_profile_id}` : '—' } function MediaThumb({ mediaId, mimeType }) { const url = resolveMediaAssetFileUrl(mediaId) const mime = (mimeType || '').toLowerCase() /* iPhone-Fotos: Browser-Vorschau oft nicht nutzbar */ if (mime.includes('heic') || mime.includes('heif')) { return
HEIC
} if (mime.startsWith('video/')) { return (