import React, { useCallback, useEffect, useMemo, useState } from 'react' import { Link } from 'react-router-dom' import { CalendarDays, ClipboardList } from 'lucide-react' import api from '../utils/api' const VIS_LABELS = { private: 'Privat', club: 'Verein', official: 'Offiziell' } const STATUS_LABELS = { draft: 'Entwurf', in_review: 'Prüfung', approved: 'Freigegeben', archived: 'Archiv', } function rowKey(item) { return `${item.exercise_id}-${item.target_club_id}` } function unitPlanTitle(u) { const d = (u.planned_date || '').toString().slice(0, 10) const g = (u.group_name || '').trim() return [d, g].filter(Boolean).join(' · ') || `Einheit #${u.id}` } /** * Dashboard: Übungen aus eigenen Trainingseinheiten, die für den Verein der Gruppe noch nicht freigegeben sind. */ export default function DashboardTrainingVisibilityWidget({ user }) { const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) const [err, setErr] = useState(null) const [selected, setSelected] = useState(() => new Set()) const [busy, setBusy] = useState(false) const [msg, setMsg] = useState(null) const load = useCallback(async () => { if (!user?.id) return setErr(null) setLoading(true) try { const res = await api.getTrainingExerciseClubVisibilityQueue({ limit_units: 100 }) const list = Array.isArray(res?.items) ? res.items : [] setItems(list) setSelected(new Set()) setMsg(null) } catch (e) { setErr(e?.message || String(e)) setItems([]) } finally { setLoading(false) } }, [user?.id]) useEffect(() => { load() }, [load]) const promotableSelected = useMemo(() => { const out = [] for (const k of selected) { const it = items.find((x) => rowKey(x) === k) if (it?.can_promote) out.push(it) } return out }, [items, selected]) const toggle = (key) => { setSelected((prev) => { const n = new Set(prev) if (n.has(key)) n.delete(key) else n.add(key) return n }) } const toggleAllPromotable = () => { const keys = items.filter((i) => i.can_promote).map(rowKey) setSelected((prev) => { if (keys.length && keys.every((k) => prev.has(k))) { return new Set() } return new Set(keys) }) } const promoteSelected = async () => { if (!promotableSelected.length) return setBusy(true) setMsg(null) try { const byClub = new Map() for (const it of promotableSelected) { const cid = it.target_club_id if (!byClub.has(cid)) byClub.set(cid, []) byClub.get(cid).push(it.exercise_id) } let anyFail = false for (const [clubId, ids] of byClub) { const uniq = [...new Set(ids)] const res = await api.bulkPatchExercisesMetadata({ exercise_ids: uniq, visibility: 'club', club_id: clubId, }) if ((res?.failed || []).length) { anyFail = true const f = res.failed[0] setMsg(f?.detail || 'Freigabe teilweise fehlgeschlagen') } } if (!anyFail) setMsg(null) await load() } catch (e) { setMsg(e?.message || String(e)) } finally { setBusy(false) } } if (!user?.id) return null if (loading) { return (

Vereinsfreigaben werden geladen…

) } if (err) { return (

{err}

) } if (!items.length) { return (

Vereinssichtbarkeit in deinen Trainings

Keine Übungen in den abgefragten Einheiten, die noch auf Verein gestellt werden müssten.

) } const allPromo = items.filter((i) => i.can_promote) const allSelected = allPromo.length > 0 && allPromo.every((i) => selected.has(rowKey(i))) return (

Vereinssichtbarkeit in deinen Trainings

Übungen in deinen Einheiten, die für den jeweiligen Verein noch nicht sichtbar sind — auf{' '} Verein setzen oder zur Bearbeitung / Planung springen.

{allPromo.length ? ( ) : null}
{items.map((it) => { const k = rowKey(it) const on = selected.has(k) const visL = VIS_LABELS[it.visibility] || it.visibility const stL = STATUS_LABELS[it.status] || it.status const first = it.units && it.units[0] const restN = (it.units?.length || 0) - 1 const tool = (it.units || []).map(unitPlanTitle).join('\n') const cn = (it.target_club_name || '').trim() return ( 0 ? tool : undefined} > ) })}
Übung Sichtbarkeit Status Verein Kontext
toggle(k)} title={it.can_promote ? 'Zur Freigabe wählen' : 'Keine Berechtigung für diese Freigabe'} aria-label={`${it.title} auswählen`} /> {it.title} {visL} {stL} {cn.length > 28 ? `${cn.slice(0, 28)}…` : cn || '—'} {first ? (
0 ? `Planung (${unitPlanTitle(first)}; +${restN} weitere — siehe Tooltip ganze Zeile)` : `Planung öffnen: ${unitPlanTitle(first)}` } aria-label="Planung öffnen" > {restN > 0 ? ( +{restN} ) : null}
) : ( '—' )}
{items.some((i) => !i.can_promote) ? (

Ausgegraute Kästchen: keine direkte Freigabe-Berechtigung — Vereinsorga kontaktieren oder die Einheit in der Planung speichern (dann ggf. automatische Vereinsfreigabe).

) : null}

Mehrere Termine: Kalender-Icon nutzt den frühesten; „+N“ listet alle Daten und Gruppen im Tooltip.

{msg ? (

{msg}

) : null}

Zur Trainingsplanung · Zeitraum ca. 45 Tage zurück bis 1 Jahr voraus; bis zu 100 Einheiten.

) }