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 (
Sichtbarkeit für den Verein

Ü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 ? ( <>
{okCount > 0 ? ( {okCount} weitere {okCount === 1 ? 'Übung' : 'Übungen'} bereits passend ) : null}
{pending.some((r) => !userMayPromote(user, targetClubId, r.createdBy)) ? (

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}
) }