From a1a3f2e0a17a6a308ca1b5324428a9b324258052 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 14 May 2026 15:32:21 +0200 Subject: [PATCH] chore(version): update version and changelog for release 0.8.129 - Bumped APP_VERSION to 0.8.129 and updated the changelog to reflect recent changes. - Added the TrainingPlanningTrainerAssignModal component to the TrainingPlanningPage for enhanced trainer assignment functionality. - Implemented new callback functions for managing lead trainer and assistant assignments in the training planning process. --- backend/version.py | 9 +- .../TrainingPlanningTrainerAssignModal.jsx | 155 ++++++++++++ frontend/src/pages/TrainingPlanningPage.jsx | 227 +++++------------- 3 files changed, 218 insertions(+), 173 deletions(-) create mode 100644 frontend/src/components/planning/TrainingPlanningTrainerAssignModal.jsx diff --git a/backend/version.py b/backend/version.py index b3e05cf..9e7a6d1 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,6 +1,6 @@ # Shinkan Jinkendo Version Information -APP_VERSION = "0.8.128" +APP_VERSION = "0.8.129" BUILD_DATE = "2026-05-12" DB_SCHEMA_VERSION = "20260514062" @@ -36,6 +36,13 @@ MODULE_VERSIONS = { } CHANGELOG = [ + { + "version": "0.8.129", + "date": "2026-05-13", + "changes": [ + "Frontend Phase 3: TrainingPlanningTrainerAssignModal (Trainer zuweisen) aus Trainingsplanungsseite; Handler per useCallback.", + ], + }, { "version": "0.8.128", "date": "2026-05-13", diff --git a/frontend/src/components/planning/TrainingPlanningTrainerAssignModal.jsx b/frontend/src/components/planning/TrainingPlanningTrainerAssignModal.jsx new file mode 100644 index 0000000..ac861fc --- /dev/null +++ b/frontend/src/components/planning/TrainingPlanningTrainerAssignModal.jsx @@ -0,0 +1,155 @@ +import React from 'react' + +/** + * Modal: organisatorische Trainer-Zuweisung (Leitung + Co) für eine bestehende Einheit. + */ +export default function TrainingPlanningTrainerAssignModal({ + open, + unit, + leadTrainerProfileId, + onLeadChange, + sessionAssistantsInherit, + onSessionAssistantsInheritChange, + sessionAssistantProfileIds, + onCoTrainerToggle, + clubDirectory, + coTrainerOptions, + saving, + onBackdropRequestClose, + onCancel, + onSave, +}) { + if (!open || !unit) return null + + return ( +
{ + if (!saving) onBackdropRequestClose() + }} + > +
e.stopPropagation()} + style={{ + background: 'var(--surface)', + borderRadius: '12px', + padding: 'clamp(14px, 3vw, 1.75rem)', + maxWidth: 'min(460px, 100%)', + width: '100%', + maxHeight: '90vh', + overflowY: 'auto', + boxSizing: 'border-box', + }} + > +

+ Trainer zuweisen (organisatorisch) +

+

+ {(unit.planned_date || '').toString().slice(0, 10)} + {unit.planned_time_start ? ` · ${String(unit.planned_time_start).slice(0, 5)}` : ''} + {(unit.group_name || '').trim() ? ` · ${(unit.group_name || '').trim()}` : null} +

+
+ + +
+
+ +
+ {!sessionAssistantsInherit ? ( +
+ {coTrainerOptions.map((m) => { + const mid = typeof m.id === 'number' ? m.id : parseInt(String(m.id), 10) + const labelText = `${(m.name || '').trim() || m.email || `Profil ${mid}`}` + const isOn = Number.isFinite(mid) && sessionAssistantProfileIds.includes(mid) + return ( + + ) + })} +
+ ) : null} + {!clubDirectory.length ? ( +

+ Mitgliederverzeichnis konnte nicht geladen werden. +

+ ) : null} +
+ + +
+
+
+ ) +} diff --git a/frontend/src/pages/TrainingPlanningPage.jsx b/frontend/src/pages/TrainingPlanningPage.jsx index f491942..2de2a15 100644 --- a/frontend/src/pages/TrainingPlanningPage.jsx +++ b/frontend/src/pages/TrainingPlanningPage.jsx @@ -11,6 +11,7 @@ import TrainingPlanExerciseVisibilityPanel from '../components/TrainingPlanExerc import PageSectionNav from '../components/PageSectionNav' import TrainingPlanningFrameworkImportModal from '../components/planning/TrainingPlanningFrameworkImportModal' import TrainingPlanningModuleApplyModal from '../components/planning/TrainingPlanningModuleApplyModal' +import TrainingPlanningTrainerAssignModal from '../components/planning/TrainingPlanningTrainerAssignModal' import { defaultSection, normalizeUnitToForm, @@ -912,6 +913,42 @@ function TrainingPlanningPage() { } } + const handleAssignLeadSelectChange = useCallback((v) => { + setAssignDraft((prev) => { + const exclude = [] + const tr = String(v || '').trim() + if (tr !== '') { + const n = parseInt(tr, 10) + if (Number.isFinite(n)) exclude.push(n) + } else if (prev.unit?.effective_lead_trainer_profile_id != null) { + const ef = Number(prev.unit.effective_lead_trainer_profile_id) + if (Number.isFinite(ef)) exclude.push(ef) + } + const exSet = new Set(exclude) + const co = exclude.length + ? prev.session_assistant_profile_ids.filter((x) => !exSet.has(x)) + : prev.session_assistant_profile_ids + return { ...prev, lead_trainer_profile_id: v, session_assistant_profile_ids: co } + }) + }, []) + + const handleAssignAssistantsInheritChange = useCallback((checked) => { + setAssignDraft((prev) => ({ + ...prev, + session_assistants_inherit: checked, + })) + }, []) + + const handleAssignCoTrainerToggle = useCallback((mid) => { + setAssignDraft((prev) => { + const was = prev.session_assistant_profile_ids.includes(mid) + const nextIds = was + ? prev.session_assistant_profile_ids.filter((x) => x !== mid) + : [...prev.session_assistant_profile_ids, mid].sort((a, b) => a - b) + return { ...prev, session_assistant_profile_ids: nextIds } + }) + }, []) + const handleDelete = async (unit) => { if (!confirm(`Trainingseinheit vom ${unit.planned_date} wirklich löschen?`)) return try { @@ -1821,178 +1858,24 @@ function TrainingPlanningPage() { )} - {assignModalOpen && assignDraft.unit ? ( -
{ - if (!assignSaving) setAssignModalOpen(false) - }} - > -
e.stopPropagation()} - style={{ - background: 'var(--surface)', - borderRadius: '12px', - padding: 'clamp(14px, 3vw, 1.75rem)', - maxWidth: 'min(460px, 100%)', - width: '100%', - maxHeight: '90vh', - overflowY: 'auto', - boxSizing: 'border-box', - }} - > -

- Trainer zuweisen (organisatorisch) -

-

- {(assignDraft.unit.planned_date || '').toString().slice(0, 10)} - {assignDraft.unit.planned_time_start - ? ` · ${String(assignDraft.unit.planned_time_start).slice(0, 5)}` - : ''} - {(assignDraft.unit.group_name || '').trim() - ? ` · ${(assignDraft.unit.group_name || '').trim()}` - : null} -

-
- - -
-
- -
- {!assignDraft.session_assistants_inherit ? ( -
- {clubDirectoryForAssignCo.map((m) => { - const mid = typeof m.id === 'number' ? m.id : parseInt(String(m.id), 10) - const labelText = `${(m.name || '').trim() || m.email || `Profil ${mid}`}` - const isOn = Number.isFinite(mid) && assignDraft.session_assistant_profile_ids.includes(mid) - return ( - - ) - })} -
- ) : null} - {!clubDirectory.length ? ( -

- Mitgliederverzeichnis konnte nicht geladen werden. -

- ) : null} -
- - -
-
-
- ) : null} + { + if (!assignSaving) setAssignModalOpen(false) + }} + onCancel={() => setAssignModalOpen(false)} + onSave={saveTrainerAssignModal} + />