diff --git a/frontend/src/components/ExerciseProgressionGraphPanel.jsx b/frontend/src/components/ExerciseProgressionGraphPanel.jsx index d12022a..1a9eadd 100644 --- a/frontend/src/components/ExerciseProgressionGraphPanel.jsx +++ b/frontend/src/components/ExerciseProgressionGraphPanel.jsx @@ -13,7 +13,7 @@ import { Link, useLocation } from 'react-router-dom' import api from '../utils/api' import SkillProfilePanel from './skills/SkillProfilePanel' import { useAuth } from '../context/AuthContext' -import { activeClubMemberships, getTenantClubDependencyKey } from '../utils/activeClub' +import { activeClubMemberships, getDefaultClubIdForGovernanceForms, getTenantClubDependencyKey } from '../utils/activeClub' import ProgressionGraphEditor from './ProgressionGraphEditor' import ProgressionGraphListCard from './ProgressionGraphListCard' import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../constants/exerciseGovernanceLabels' @@ -41,6 +41,8 @@ function ExerciseProgressionGraphPanel( const { user } = useAuth() const location = useLocation() const isSuperadmin = user?.role === 'superadmin' + const isPlatformAdmin = isSuperadmin || user?.role === 'admin' + const memberClubs = useMemo(() => activeClubMemberships(user?.clubs), [user?.clubs]) const tenantClubDepKey = useMemo(() => getTenantClubDependencyKey(user), [user]) const filteredGraphVisOptions = useMemo( @@ -61,6 +63,8 @@ function ExerciseProgressionGraphPanel( const [metaName, setMetaName] = useState('') const [metaDescription, setMetaDescription] = useState('') const [metaVisibility, setMetaVisibility] = useState('private') + const [metaClubSelect, setMetaClubSelect] = useState('') + const [metaClubManual, setMetaClubManual] = useState('') const [filterAnchorOnly, setFilterAnchorOnly] = useState(!!anchorExerciseId) const [editingEdgeNotes, setEditingEdgeNotes] = useState(null) @@ -157,6 +161,8 @@ function ExerciseProgressionGraphPanel( setMetaName('') setMetaDescription('') setMetaVisibility('private') + setMetaClubSelect('') + setMetaClubManual('') return } const g = graphs.find((x) => x.id === selectedGraphId) @@ -164,6 +170,13 @@ function ExerciseProgressionGraphPanel( setMetaName(g.name || '') setMetaDescription(g.description || '') setMetaVisibility(g.visibility || 'private') + if (g.club_id != null) { + setMetaClubSelect(String(g.club_id)) + } else { + const fallback = getDefaultClubIdForGovernanceForms(user) + setMetaClubSelect(fallback != null ? String(fallback) : '') + } + setMetaClubManual('') } let cancelled = false ;(async () => { @@ -176,7 +189,20 @@ function ExerciseProgressionGraphPanel( return () => { cancelled = true } - }, [selectedGraphId, graphs, refreshEdges]) + }, [selectedGraphId, graphs, refreshEdges, user]) + + const resolveGovernanceClubId = useCallback(() => { + const g = graphs.find((x) => x.id === selectedGraphId) + if (g?.club_id != null) return Number(g.club_id) + + const manual = String(metaClubManual || '').trim() + if (manual && /^\d+$/.test(manual)) return Number(manual) + + const sel = String(metaClubSelect || '').trim() + if (sel && /^\d+$/.test(sel)) return Number(sel) + + return getDefaultClubIdForGovernanceForms(user) + }, [graphs, selectedGraphId, metaClubManual, metaClubSelect, user]) const filteredEdges = useMemo(() => { if (!filterAnchorOnly || anchorExerciseId == null) return edges @@ -226,13 +252,7 @@ function ExerciseProgressionGraphPanel( } } - const resolvePromoteClubId = () => { - const g = graphs.find((x) => x.id === selectedGraphId) - if (g?.club_id != null) return Number(g.club_id) - const memberships = activeClubMemberships(user?.clubs) - const active = memberships.find((c) => c.is_active) || memberships[0] - return active?.club_id != null ? Number(active.club_id) : null - } + const resolvePromoteClubId = resolveGovernanceClubId const handleSaveMeta = async () => { if (!selectedGraphId) return @@ -268,7 +288,9 @@ function ExerciseProgressionGraphPanel( if (promote) { const clubId = resolvePromoteClubId() if (!clubId) { - alert('Kein aktiver Verein — Übungen können nicht auf Verein promoted werden.') + throw new Error( + 'Kein Verein gewählt — bitte unter „Verein zuordnen“ einen Verein auswählen oder den Vereins-Umschalter nutzen.', + ) } else { const ids = privateExercises.map((ex) => ex.id).filter((id) => id != null) const res = await api.bulkPatchExercisesMetadata({ @@ -286,6 +308,11 @@ function ExerciseProgressionGraphPanel( } const promoteClubId = nextVis === 'club' ? resolvePromoteClubId() : null + if (nextVis === 'club' && !promoteClubId) { + throw new Error( + 'Vereins-Sichtbarkeit: Bitte einen Verein unter „Verein zuordnen“ wählen oder den Vereins-Umschalter setzen.', + ) + } await api.updateExerciseProgressionGraph(selectedGraphId, { name, description: metaDescription.trim() || null, @@ -541,7 +568,14 @@ function ExerciseProgressionGraphPanel( + {metaVisibility === 'club' ? ( +