import React, { useCallback, useMemo, useState } from 'react' import { Link } from 'react-router-dom' import api from '../utils/api' const VIS_LABELS = { private: 'Privat', club: 'Verein', official: 'Offiziell', } function collectExerciseRows(sections) { const map = new Map() for (const sec of sections || []) { for (const it of sec.items || []) { if (it.item_type === 'note') continue const id = Number(it.exercise_id) if (!Number.isFinite(id) || id < 1) continue if (!map.has(id)) { map.set(id, it) } } } return [...map.entries()].map(([id, it]) => ({ id, title: it.exercise_title || `Übung #${id}`, visibility: it.exercise_visibility, clubId: it.exercise_club_id != null ? Number(it.exercise_club_id) : null, createdBy: it.exercise_created_by != null ? Number(it.exercise_created_by) : null, status: it.exercise_status, })) } function needsClubForTarget(row, targetClubId) { if (targetClubId == null || !Number.isFinite(Number(targetClubId))) return false const vis = String(row.visibility || 'private').toLowerCase() if (vis === 'official') return false const tc = Number(targetClubId) if (vis === 'private') return true if (vis === 'club') { if (row.clubId == null) return true return row.clubId !== tc } return false } function userMayPromote(user, targetClubId, createdBy) { if (!user || targetClubId == null) return false const role = String(user.role || '').toLowerCase() if (role === 'admin' || role === 'superadmin') return true if (createdBy != null && Number(createdBy) === Number(user.id)) return true const row = (user.clubs || []).find((c) => Number(c.id) === Number(targetClubId)) if (!row || !Array.isArray(row.roles)) return false return row.roles.includes('club_admin') } /** * Listen-Panel im Trainingsplan: Übungen, die für die gewählte Gruppe noch nicht vereinsweit sichtbar sind, * und Freigabe auf „Verein“ (API: PUT / bulk-metadata). */ export default function TrainingPlanExerciseVisibilityPanel({ sections, targetClubId, user, onMetaRefresh, }) { const [busyId, setBusyId] = useState(null) const [bulkBusy, setBulkBusy] = useState(false) const [message, setMessage] = useState(null) const rows = useMemo(() => collectExerciseRows(sections), [sections]) const { pending, okCount } = useMemo(() => { if (targetClubId == null || !Number.isFinite(Number(targetClubId))) { return { pending: [], okCount: 0 } } const pending = [] let okCount = 0 for (const r of rows) { if (needsClubForTarget(r, targetClubId)) pending.push(r) else okCount += 1 } return { pending, okCount } }, [rows, targetClubId]) const promotableIds = useMemo( () => pending.filter((r) => userMayPromote(user, targetClubId, r.createdBy)).map((r) => r.id), [pending, targetClubId, user] ) const applyClubVisibility = useCallback( async (exerciseIds) => { if (!exerciseIds.length || targetClubId == null) return setMessage(null) const res = await api.bulkPatchExercisesMetadata({ exercise_ids: exerciseIds, visibility: 'club', club_id: targetClubId, }) const failed = res?.failed || [] const updatedN = Number(res?.updated_count || 0) if (updatedN > 0 && onMetaRefresh) { await onMetaRefresh() } if (failed.length) { const first = failed[0]?.detail || 'Unbekannter Fehler' setMessage( failed.length === 1 ? String(first) : `${failed.length} Übungen nicht geändert: ${first}` ) } }, [targetClubId, onMetaRefresh] ) const onPromoteOne = useCallback( async (id) => { setBusyId(id) setMessage(null) try { await applyClubVisibility([id]) } catch (e) { setMessage(e?.message || String(e)) } finally { setBusyId(null) } }, [applyClubVisibility] ) const onPromoteAll = useCallback(async () => { if (!promotableIds.length) return setBulkBusy(true) setMessage(null) try { await applyClubVisibility(promotableIds) } catch (e) { setMessage(e?.message || String(e)) } finally { setBulkBusy(false) } }, [applyClubVisibility, promotableIds]) if (!rows.length) return null return (
Übungen mit Sichtbarkeit „Privat“ oder einem anderen Verein sieht das Team bei der Durchführung nicht. Hier können Sie sie auf Verein setzen (gleiche Logik wie beim Speichern der Einheit).
{targetClubId == null || !Number.isFinite(Number(targetClubId)) ? (Wählen Sie eine Trainingsgruppe, um passende Freigaben anzuzeigen.
) : null} {targetClubId != null && Number.isFinite(Number(targetClubId)) && !pending.length && rows.length ? (Alle {rows.length} {rows.length === 1 ? 'Übung ist' : 'Übungen sind'} für diesen Verein in der Durchführung sichtbar (oder offiziell).
) : null} {targetClubId != null && Number.isFinite(Number(targetClubId)) && pending.length ? ( <>Einige Einträge können Sie nicht selbst freigeben: Denken Sie an die Vereinsorga oder speichern Sie die Einheit — bei ausreichender Berechtigung werden private Übungen dann automatisch mitgeführt.
) : null} > ) : null} {message ? ({message}
) : null}