diff --git a/frontend/src/pages/TrainingPlanningPage.jsx b/frontend/src/pages/TrainingPlanningPage.jsx index 6576d58..dfedb74 100644 --- a/frontend/src/pages/TrainingPlanningPage.jsx +++ b/frontend/src/pages/TrainingPlanningPage.jsx @@ -87,6 +87,28 @@ const sessionAssignDefaults = () => ({ session_assistants_inherit: true, session_assistant_profile_ids: [], }) + +/** Co_trainer_ids aus TrainingGroups (Liste/JSON) → Zahlenliste */ +function normalizeGroupCoTrainerIds(raw) { + if (raw == null) return [] + const arr = Array.isArray(raw) ? raw : [] + const out = [] + for (const x of arr) { + const n = Number(x) + if (Number.isFinite(n) && n >= 1) out.push(n) + } + return out +} + +/** Mitgliederverzeichnis-Einträge ohne effektiven Leitungsträger als Co‑Option */ +function filterDirectoryExcludingLead(directory, excludeLeadPid) { + const ex = + excludeLeadPid != null && excludeLeadPid !== '' && Number.isFinite(Number(excludeLeadPid)) + ? Number(excludeLeadPid) + : null + if (ex == null) return directory + return directory.filter((m) => Number(m.id) !== ex) +} function TrainingPlanningPage() { const { user } = useAuth() const [groups, setGroups] = useState([]) @@ -123,7 +145,6 @@ function TrainingPlanningPage() { const [planScope, setPlanScope] = useState('group') const [assignedToMeOnly, setAssignedToMeOnly] = useState(false) const [clubDirectory, setClubDirectory] = useState([]) - const [meProfile, setMeProfile] = useState(null) const [assignModalOpen, setAssignModalOpen] = useState(false) const [assignDraft, setAssignDraft] = useState({ unit: null, @@ -241,39 +262,41 @@ function TrainingPlanningPage() { const r = (user?.role || '').toLowerCase() if (r === 'admin' || r === 'superadmin') return true if (selectedGroupClubIdMemo == null || !Number.isFinite(selectedGroupClubIdMemo)) return false - const row = (meProfile?.clubs || []).find((c) => Number(c.id) === selectedGroupClubIdMemo) + const row = (user?.clubs || []).find((c) => Number(c.id) === selectedGroupClubIdMemo) return Array.isArray(row?.roles) && row.roles.includes('club_admin') - }, [user?.role, selectedGroupClubIdMemo, meProfile]) + }, [user?.role, user?.clubs, selectedGroupClubIdMemo]) - useEffect(() => { - if (!user?.id) { - setMeProfile(null) - return undefined + const clubAdminClubIdSet = useMemo(() => { + const ids = [] + for (const c of user?.clubs || []) { + if (Array.isArray(c.roles) && c.roles.includes('club_admin')) { + const id = Number(c.id) + if (Number.isFinite(id)) ids.push(id) + } } - let cancelled = false - api - .getCurrentProfile() - .then((p) => { - if (!cancelled) setMeProfile(p) - }) - .catch(() => { - if (!cancelled) setMeProfile(null) - }) - return () => { - cancelled = true - } - }, [user?.id]) + return new Set(ids) + }, [user?.clubs]) useEffect(() => { const gid = parseInt(formData.group_id || selectedGroupId || '0', 10) const gModal = Number.isFinite(gid) && gid >= 1 ? groups.find((x) => x.id === gid) : null const clubForModal = gModal?.club_id != null ? Number(gModal.club_id) : null + + let assignModalClubId = null + if (assignModalOpen && assignDraft.unit?.group_id != null) { + const ug = Number(assignDraft.unit.group_id) + const gAssign = Number.isFinite(ug) ? groups.find((x) => x.id === ug) : null + if (gAssign?.club_id != null) assignModalClubId = Number(gAssign.club_id) + } + const loadClubId = showModal && clubForModal != null && Number.isFinite(clubForModal) ? clubForModal - : planScope === 'club' && canClubOrgTraining && selectedGroupClubIdMemo != null - ? selectedGroupClubIdMemo - : null + : assignModalOpen && assignModalClubId != null && Number.isFinite(assignModalClubId) + ? assignModalClubId + : canClubOrgTraining && selectedGroupClubIdMemo != null && Number.isFinite(selectedGroupClubIdMemo) + ? selectedGroupClubIdMemo + : null if (loadClubId == null || !Number.isFinite(loadClubId)) { setClubDirectory([]) @@ -296,10 +319,11 @@ function TrainingPlanningPage() { } }, [ showModal, + assignModalOpen, + assignDraft.unit, formData.group_id, selectedGroupId, groups, - planScope, canClubOrgTraining, selectedGroupClubIdMemo, ]) @@ -559,7 +583,15 @@ function TrainingPlanningPage() { session_assistants_inherit: fullUnit.assistant_trainer_profile_ids == null || fullUnit.assistant_trainer_profile_ids === undefined, - session_assistant_profile_ids: toNumList(fullUnit.assistant_trainer_profile_ids), + session_assistant_profile_ids: (() => { + const efLead = + fullUnit.effective_lead_trainer_profile_id != null + ? Number(fullUnit.effective_lead_trainer_profile_id) + : null + let xs = toNumList(fullUnit.assistant_trainer_profile_ids) + if (efLead != null && Number.isFinite(efLead)) xs = xs.filter((id) => id !== efLead) + return xs + })(), }) setShowModal(true) } catch (err) { @@ -596,6 +628,14 @@ function TrainingPlanningPage() { } const openTrainerAssignModal = (unit) => { + const effLead = + unit.effective_lead_trainer_profile_id != null + ? Number(unit.effective_lead_trainer_profile_id) + : null + let coIds = toNumList(unit.assistant_trainer_profile_ids) + if (effLead != null && Number.isFinite(effLead)) { + coIds = coIds.filter((id) => id !== effLead) + } setAssignDraft({ unit, lead_trainer_profile_id: @@ -604,7 +644,7 @@ function TrainingPlanningPage() { : '', session_assistants_inherit: unit.assistant_trainer_profile_ids == null || unit.assistant_trainer_profile_ids === undefined, - session_assistant_profile_ids: toNumList(unit.assistant_trainer_profile_ids), + session_assistant_profile_ids: coIds, }) setAssignModalOpen(true) } @@ -701,7 +741,27 @@ function TrainingPlanningPage() { } const updateFormField = (field, value) => { - setFormData((prev) => ({ ...prev, [field]: value })) + setFormData((prev) => { + if (field !== 'lead_trainer_profile_id') return { ...prev, [field]: value } + const ts = typeof value === 'string' ? value.trim() : String(value ?? '').trim() + const strip = new Set() + if (ts !== '') { + const nid = parseInt(ts, 10) + if (Number.isFinite(nid)) strip.add(nid) + } else { + const gidParsed = parseInt(prev.group_id || selectedGroupId || '0', 10) + const gr = + Number.isFinite(gidParsed) && gidParsed >= 1 + ? groups.find((xg) => xg.id === gidParsed) + : null + if (gr?.trainer_id != null) { + const ht = Number(gr.trainer_id) + if (Number.isFinite(ht)) strip.add(ht) + } + } + const assistants = prev.session_assistant_profile_ids.filter((id) => !strip.has(id)) + return { ...prev, lead_trainer_profile_id: value, session_assistant_profile_ids: assistants } + }) } const calendarGridDays = useMemo(() => { @@ -729,6 +789,32 @@ function TrainingPlanningPage() { return new Date(y, mo - 1, 1).toLocaleDateString('de-DE', { month: 'long', year: 'numeric' }) }, [calendarMonthStr]) + const mayConfigureSessionAssignments = useCallback( + (unit) => { + if (!unit) return false + const pid = Number(user?.id) + if (!Number.isFinite(pid)) return false + const r = (user?.role || '').toLowerCase() + if (r === 'admin' || r === 'superadmin') return true + + const gClub = unit.group_club_id != null ? Number(unit.group_club_id) : null + if (Number.isFinite(gClub) && clubAdminClubIdSet.has(gClub)) return true + + const gid = Number(unit.group_id) + const g = groups.find((gr) => gr.id === gid) + if (!g) return false + + const cb = unit.created_by != null ? Number(unit.created_by) : NaN + if (Number.isFinite(cb) && cb === pid) return true + + const ht = g.trainer_id != null ? Number(g.trainer_id) : NaN + if (Number.isFinite(ht) && ht === pid) return true + + return normalizeGroupCoTrainerIds(g.co_trainer_ids).includes(pid) + }, + [user?.id, user?.role, groups, clubAdminClubIdSet] + ) + if (loading) { return (