import { useCallback, useEffect, useMemo, useState } from 'react' import { Navigate } from 'react-router-dom' import { useAuth } from '../context/AuthContext' import api from '../utils/api' import AdminPageNav from '../components/AdminPageNav' const CLUB_ROLE_OPTIONS = [ { code: 'club_admin', label: 'Vereinsadmin' }, { code: 'trainer', label: 'Trainer' }, { code: 'division_lead', label: 'Spartenleitung' }, { code: 'content_editor', label: 'Inhalte bearbeiten' }, ] const PORTAL_ROLE_LABEL = { user: 'Nutzer', trainer: 'Portal-Trainer', admin: 'Portal-Administrator', superadmin: 'Super-Administrator', } function clubAdminClubIds(user) { return (user?.clubs || []) .filter((c) => Array.isArray(c.roles) && c.roles.includes('club_admin')) .map((c) => c.id) } function clubSelectOptions(user, allClubs, isPlatformAdmin) { if (!isPlatformAdmin) { const ids = new Set(clubAdminClubIds(user)) return (allClubs || []).filter((c) => ids.has(c.id)) } return allClubs || [] } /** Plattform-Rollen im UI (Tier/Abo entfällt bis auf Weiteres). */ function portalRoleSelectOptions(viewerIsSuperadmin, currentRole) { const base = [ { value: 'user', label: PORTAL_ROLE_LABEL.user }, { value: 'trainer', label: `${PORTAL_ROLE_LABEL.trainer} (Legacy)` }, { value: 'admin', label: PORTAL_ROLE_LABEL.admin }, ] const cur = (currentRole || 'user').toLowerCase() if (viewerIsSuperadmin) base.push({ value: 'superadmin', label: PORTAL_ROLE_LABEL.superadmin }) const values = new Set(base.map((x) => x.value)) if (cur && !values.has(cur)) { base.unshift({ value: cur, label: cur }) } return base } export default function AdminUsersPage() { const { user } = useAuth() const isPlatformAdmin = user?.role === 'admin' || user?.role === 'superadmin' const isSuperadminViewer = user?.role === 'superadmin' const managedClubIds = useMemo(() => clubAdminClubIds(user), [user]) const clubOrgMode = !isPlatformAdmin && managedClubIds.length > 0 const canAccess = isPlatformAdmin || clubOrgMode const [platformUsers, setPlatformUsers] = useState([]) const [clubs, setClubs] = useState([]) const [clubMembers, setClubMembers] = useState([]) const [selectedClubId, setSelectedClubId] = useState( () => managedClubIds[0] ?? null ) const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [portalDraft, setPortalDraft] = useState({}) const [assignModal, setAssignModal] = useState(null) const [assignRoles, setAssignRoles] = useState(['trainer']) const [clubEditModal, setClubEditModal] = useState(null) const [addMemberOpen, setAddMemberOpen] = useState(false) const [newMemberProfileId, setNewMemberProfileId] = useState('') const [newMemberRoles, setNewMemberRoles] = useState(['trainer']) const [pwdModal, setPwdModal] = useState(null) const [pwdNew, setPwdNew] = useState('') const [pwdNew2, setPwdNew2] = useState('') const selectableClubs = useMemo( () => clubSelectOptions(user, clubs, isPlatformAdmin), [user, clubs, isPlatformAdmin] ) useEffect(() => { if (!clubOrgMode) return if (selectedClubId == null || !managedClubIds.includes(selectedClubId)) { setSelectedClubId(managedClubIds[0] ?? null) } }, [clubOrgMode, managedClubIds, selectedClubId]) const loadPlatform = useCallback(async () => { const [u, c] = await Promise.all([api.listAdminUsers(), api.listClubs()]) setPlatformUsers(Array.isArray(u) ? u : []) setClubs(Array.isArray(c) ? c : []) const d = {} for (const row of u || []) { d[row.id] = { role: (row.role || 'user').toLowerCase() } } setPortalDraft(d) }, []) useEffect(() => { if (!canAccess) return if (clubOrgMode) return let cancelled = false ;(async () => { setError('') setLoading(true) try { await loadPlatform() } catch (e) { if (!cancelled) setError(e.message || String(e)) } finally { if (!cancelled) setLoading(false) } })() return () => { cancelled = true } }, [canAccess, clubOrgMode, loadPlatform]) useEffect(() => { if (!canAccess || !clubOrgMode || !selectedClubId) return let cancelled = false ;(async () => { setError('') setLoading(true) try { const [c, m] = await Promise.all([ api.listClubs(), api.listClubMembers(selectedClubId, { includeInactive: true }), ]) if (!cancelled) { setClubs(Array.isArray(c) ? c : []) setClubMembers(Array.isArray(m) ? m : []) } } catch (e) { if (!cancelled) setError(e.message || String(e)) } finally { if (!cancelled) setLoading(false) } })() return () => { cancelled = true } }, [canAccess, clubOrgMode, selectedClubId]) const reloadClubMembers = useCallback(async () => { if (!selectedClubId) return try { const m = await api.listClubMembers(selectedClubId, { includeInactive: true }) setClubMembers(Array.isArray(m) ? m : []) } catch { setClubMembers([]) } }, [selectedClubId]) if (!canAccess) return const selectedClubLabel = selectableClubs.find((c) => c.id === selectedClubId)?.name || 'Verein' const savePortal = async (profileId) => { const dr = portalDraft[profileId] if (!dr) return try { await api.updateProfile(profileId, { role: dr.role }) await loadPlatform() } catch (e) { alert(e.message || String(e)) } } const submitAssignClub = async () => { if (!assignModal) return const clubId = assignModal.clubId const profileId = assignModal.profileId if (!clubId || !assignRoles.length) { alert('Verein und mindestens eine Rolle wählen.') return } try { await api.addClubMember(clubId, { profile_id: profileId, roles: assignRoles }) setAssignModal(null) setAssignRoles(['trainer']) await loadPlatform() } catch (e) { alert(e.message || String(e)) } } const saveClubMembership = async () => { if (!clubEditModal) return const { clubId, profileId, roles, status } = clubEditModal try { await api.updateClubMember(clubId, profileId, { roles, status }) setClubEditModal(null) if (clubOrgMode) await reloadClubMembers() else await loadPlatform() } catch (e) { alert(e.message || String(e)) } } const removeClubMembership = async () => { if (!clubEditModal) return if (!confirm('Mitgliedschaft in diesem Verein wirklich entfernen?')) return try { await api.removeClubMember(clubEditModal.clubId, clubEditModal.profileId) setClubEditModal(null) if (clubOrgMode) await reloadClubMembers() else await loadPlatform() } catch (e) { alert(e.message || String(e)) } } const submitAddClubMember = async () => { const raw = parseInt(String(newMemberProfileId).trim(), 10) if (!Number.isFinite(raw) || raw < 1) { alert('Gültige Profil-ID eingeben.') return } if (!selectedClubId) return if (!newMemberRoles.length) { alert('Mindestens eine Vereinsrolle.') return } try { await api.addClubMember(selectedClubId, { profile_id: raw, roles: newMemberRoles }) setAddMemberOpen(false) setNewMemberProfileId('') setNewMemberRoles(['trainer']) await reloadClubMembers() } catch (e) { alert(e.message || String(e)) } } const submitPasswordReset = async () => { if (!pwdModal) return if (pwdNew.length < 8) { alert('Mindestens 8 Zeichen.') return } if (pwdNew !== pwdNew2) { alert('Die beiden Passwörter stimmen nicht überein.') return } try { await api.managementPasswordReset(pwdModal.profileId, pwdNew) setPwdModal(null) setPwdNew('') setPwdNew2('') alert('Neues Passwort wurde gesetzt.') } catch (e) { alert(e.message || String(e)) } } return (

Nutzer & Vereinsrollen

{clubOrgMode ? (

Du verwaltest nur Mitglieder des ausgewählten Vereins und deren Rollen in diesem Verein. Portal-Rollen und andere Vereine sind hier nicht sichtbar.

) : ( <>

Gesamtübersicht aller Konten und Vereinszuordnungen. Portal-Einstellungen steuern nur den Zugang zur plattformweiten Administration (Kataloge, Hierarchie, globale Nutzerliste usw.); Vereinsarbeit (Trainer, Vereinsadmin …) bleiben Vereinsrollen. Abonnement/Tier ist derzeit nicht freigeschaltet.

Die vier Portal-Zugriffsstufen:
  • Nutzer — Standardnutzer ohne Plattform-Admin-Bereiche.
  • Portal-Trainer (Legacy) — ältere Kennzeichnung auf Profil-Ebene; organisatorisch ist „Trainer“ in der Regel eine Vereinsrolle.
  • Portal-Administrator — Zugang zu allen geschützten /admin-Bereichen (Außer: einige Funktionen nur Superadmin, z. B. bestimmte Medien-/Governance-Aktionen).
  • Super-Administrator — volle Plattform-Governance (u. a. offizielle Medien, Superadmin-Rolle vergeben, harte Lifecycle-Aktionen an Medien).
)} {clubOrgMode && managedClubIds.length > 1 ? (
) : null} {loading ? (

Laden…

) : error ? (
{error}
) : clubOrgMode ? (
{!clubMembers.length ? (

Keine Mitglieder in diesem Verein.

) : ( clubMembers.map((m) => (
{m.name || '—'} #{m.profile_id}
{m.email || '—'}
Status: {m.status} · Verifiziert: {m.email_verified ? 'ja' : 'nein'}
Rollen: {(m.roles || []).join(', ') || '—'}
{m.profile_id !== user?.id ? ( ) : null}
)) )}
) : (
{platformUsers.map((row) => { const portalRoleChoices = portalRoleSelectOptions(isSuperadminViewer, row.role) return (
{row.name || '—'} #{row.id}
{row.email || '—'}
Verifiziert: {row.email_verified ? 'ja' : 'nein'}
Vereinsmitgliedschaften {!row.clubs?.length ? (

Keine Zuordnung.

) : (
    {(row.clubs || []).map((c) => (
  • {c.name} {c.abbreviation ? ` (${c.abbreviation})` : ''} —{' '} {(c.roles || []).join(', ') || '—'} {c.membership_status === 'inactive' ? ( (inaktiv) ) : null}{' '}
  • ))}
)}
) })}
)} {assignModal && (

Verein zuweisen

{assignModal.profileLabel}

Rollen im Verein
{CLUB_ROLE_OPTIONS.map((opt) => ( ))}
)} {addMemberOpen && clubOrgMode ? (

Mitglied hinzufügen

Verein: {selectedClubLabel}

setNewMemberProfileId(e.target.value)} placeholder="z. B. 42" />
Rollen
{CLUB_ROLE_OPTIONS.map((opt) => ( ))}
) : null} {clubEditModal && (

Vereinsmitgliedschaft

{clubEditModal.profileLabel} → {clubEditModal.clubName}

Rollen
{CLUB_ROLE_OPTIONS.map((opt) => ( ))}
)} {pwdModal ? (

Login-Passwort setzen

{pwdModal.label}

Mindestens 8 Zeichen. Das Passwort liegt dem Nutzer nicht automatisch vor — gib es sicher weiter.

setPwdNew(e.target.value)} />
setPwdNew2(e.target.value)} />
) : null}
) }