From 1c67a50ce4d82505aa2bcf2f71605c5699e4a6fa Mon Sep 17 00:00:00 2001 From: Lars Date: Sun, 14 Jun 2026 07:19:35 +0200 Subject: [PATCH] Enhance Exercise Progression Graph Panel with Governance Club Management - Introduced functionality to manage governance clubs for superadmins, allowing for better club selection and organization within the Exercise Progression Graph Panel. - Implemented state management for clubs, including sorting and filtering options, to improve user experience and accessibility. - Enhanced the useEffect hook to fetch governance clubs dynamically, ensuring up-to-date club information is available for selection. - Updated the club selection dropdown to categorize clubs into "My Clubs" and "Other Clubs," improving clarity and usability for users. --- .../ExerciseProgressionGraphPanel.jsx | 103 +++++++++++++----- 1 file changed, 75 insertions(+), 28 deletions(-) diff --git a/frontend/src/components/ExerciseProgressionGraphPanel.jsx b/frontend/src/components/ExerciseProgressionGraphPanel.jsx index 1a9eadd..68a3038 100644 --- a/frontend/src/components/ExerciseProgressionGraphPanel.jsx +++ b/frontend/src/components/ExerciseProgressionGraphPanel.jsx @@ -41,9 +41,9 @@ 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 [clubsForGovernanceForms, setClubsForGovernanceForms] = useState([]) const filteredGraphVisOptions = useMemo( () => VIS_OPTIONS.filter((o) => o.value !== 'official' || isSuperadmin), @@ -64,7 +64,36 @@ function ExerciseProgressionGraphPanel( const [metaDescription, setMetaDescription] = useState('') const [metaVisibility, setMetaVisibility] = useState('private') const [metaClubSelect, setMetaClubSelect] = useState('') - const [metaClubManual, setMetaClubManual] = useState('') + + const memberClubIdSet = useMemo( + () => new Set(memberClubs.map((c) => Number(c.id))), + [memberClubs], + ) + + const sortedMemberClubs = useMemo( + () => + [...memberClubs].sort((a, b) => + String(a.name || '').localeCompare(String(b.name || ''), 'de'), + ), + [memberClubs], + ) + + const sortedOtherGovernanceClubs = useMemo(() => { + if (!isSuperadmin || clubsForGovernanceForms.length === 0) return [] + return clubsForGovernanceForms + .filter((c) => !memberClubIdSet.has(Number(c.id))) + .sort((a, b) => String(a.name || '').localeCompare(String(b.name || ''), 'de')) + }, [isSuperadmin, clubsForGovernanceForms, memberClubIdSet]) + + const showGovernanceClubOptgroups = + isSuperadmin && sortedMemberClubs.length > 0 && sortedOtherGovernanceClubs.length > 0 + + const governanceClubSelectOptions = useMemo(() => { + if (isSuperadmin && clubsForGovernanceForms.length > 0) { + return [...sortedMemberClubs, ...sortedOtherGovernanceClubs] + } + return sortedMemberClubs + }, [isSuperadmin, clubsForGovernanceForms.length, sortedMemberClubs, sortedOtherGovernanceClubs]) const [filterAnchorOnly, setFilterAnchorOnly] = useState(!!anchorExerciseId) const [editingEdgeNotes, setEditingEdgeNotes] = useState(null) @@ -129,6 +158,25 @@ function ExerciseProgressionGraphPanel( } }, [refreshGraphs, tenantClubDepKey]) + useEffect(() => { + if (!isSuperadmin) { + setClubsForGovernanceForms([]) + return undefined + } + let cancelled = false + ;(async () => { + try { + const list = await api.listClubs() + if (!cancelled) setClubsForGovernanceForms(Array.isArray(list) ? list : []) + } catch { + if (!cancelled) setClubsForGovernanceForms([]) + } + })() + return () => { + cancelled = true + } + }, [isSuperadmin, tenantClubDepKey]) + useEffect(() => { if (!selectedGraphId) { setSkillProfileData(null) @@ -162,7 +210,6 @@ function ExerciseProgressionGraphPanel( setMetaDescription('') setMetaVisibility('private') setMetaClubSelect('') - setMetaClubManual('') return } const g = graphs.find((x) => x.id === selectedGraphId) @@ -176,7 +223,6 @@ function ExerciseProgressionGraphPanel( const fallback = getDefaultClubIdForGovernanceForms(user) setMetaClubSelect(fallback != null ? String(fallback) : '') } - setMetaClubManual('') } let cancelled = false ;(async () => { @@ -195,14 +241,11 @@ function ExerciseProgressionGraphPanel( 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]) + }, [graphs, selectedGraphId, metaClubSelect, user]) const filteredEdges = useMemo(() => { if (!filterAnchorOnly || anchorExerciseId == null) return edges @@ -593,27 +636,31 @@ function ExerciseProgressionGraphPanel( onChange={(e) => setMetaClubSelect(e.target.value)} > - {memberClubs.map((c) => ( - - ))} + {showGovernanceClubOptgroups ? ( + <> + + {sortedMemberClubs.map((c) => ( + + ))} + + + {sortedOtherGovernanceClubs.map((c) => ( + + ))} + + + ) : ( + governanceClubSelectOptions.map((c) => ( + + )) + )} - {isPlatformAdmin ? ( - <> - - setMetaClubManual(e.target.value)} - /> - - ) : null} ) : null}