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…
{err}
Keine Übungen in den abgefragten Einheiten, die noch auf Verein gestellt werden müssten.
Übungen in deinen Einheiten, die für den jeweiligen Verein noch nicht sichtbar sind — auf{' '} Verein setzen oder zur Bearbeitung / Planung springen.
| Ü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"
>
) : (
'—'
)}
|
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.