diff --git a/backend/version.py b/backend/version.py index 090590e..b3e05cf 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,6 +1,6 @@ # Shinkan Jinkendo Version Information -APP_VERSION = "0.8.126" +APP_VERSION = "0.8.128" BUILD_DATE = "2026-05-12" DB_SCHEMA_VERSION = "20260514062" @@ -36,6 +36,13 @@ MODULE_VERSIONS = { } CHANGELOG = [ + { + "version": "0.8.128", + "date": "2026-05-13", + "changes": [ + "Frontend Phase 3: TrainingPlanningModuleApplyModal (Trainingsmodul einfügen) aus Trainingsplanungsseite; gemeinsamer Callback onModuleApplySectionIndexChange.", + ], + }, { "version": "0.8.126", "date": "2026-05-13", diff --git a/frontend/src/components/planning/TrainingPlanningModuleApplyModal.jsx b/frontend/src/components/planning/TrainingPlanningModuleApplyModal.jsx new file mode 100644 index 0000000..8d9ae91 --- /dev/null +++ b/frontend/src/components/planning/TrainingPlanningModuleApplyModal.jsx @@ -0,0 +1,319 @@ +import React from 'react' +import { Link } from 'react-router-dom' +import { trainingVisibilityShortDE } from '../../utils/trainingPlanningPageHelpers' + +/** + * Dialog: Trainingsmodul in die Abschnitte einer Einheit einfügen (Bibliothekskopie). + */ +export default function TrainingPlanningModuleApplyModal({ + open, + busy, + err, + placementLocked, + placementSummary, + sections, + sectionIx, + onSectionIndexChange, + insertSlot, + onInsertSlotChange, + targetItems, + searchQuery, + onSearchQueryChange, + filteredList, + fullList, + selectedModuleId, + onSelectModuleId, + modulePickPreview, + onConfirm, + onCancel, +}) { + if (!open) return null + + const handleBackdropMouseDown = (ev) => { + if (ev.target !== ev.currentTarget || busy) return + onCancel() + } + + return ( +
+ Alle Positionen des gewählten Moduls werden als neue Zeilen eingefügt (Kopie, mit klarer + Herkunft im Ablauf). Die Einheit brauchst du dafür nicht vorher gespeichert zu haben — Speichern am Ende + wie gewohnt. Vollständige Textsuche oder Modulkategorien planen wir serverseitig für + eine spätere Iteration; vorerst steht hier eine{' '} + Schnellsuche über Titel und Freitext-Felder zur Verfügung. +
+ + {err ? ( +{err}
+ ) : null} + + {placementLocked ? ( + <> ++ Aktuelle Einfügeposition: Abschnitt {placementSummary.secTitle}{' '} + / {placementSummary.positionDescription} +
++ {!fullList.length ? 'Keine Module verfügbar oder keine Berechtigung.' : 'Kein Modul entspricht der Suche.'} +
+ ) : ( + filteredList.map((m) => { + const title = ((m.title || '').trim() || `Modul #${m.id}`).trim() + const visLbl = trainingVisibilityShortDE(m.visibility) + const nPos = typeof m.items_count === 'number' ? m.items_count : '—' + const selected = String(m.id) === String(selectedModuleId) + return ( + + ) + }) + )} ++ Übungen und Hinweise laden … +
+ ) : modulePickPreview.err ? ( ++ {modulePickPreview.err} +
+ ) : !modulePickPreview.exercises.length && !modulePickPreview.notes ? ( ++ Keine Übungspositionen in diesem Eintrag gefunden (prüfen, ob Übungen im Modul gültige IDs haben). +
+ ) : ( + <> ++ … und noch {modulePickPreview.exercises.length - 12} weitere Übungen in genau dieser Modulreihenfolge. +
+ ) : null} + {modulePickPreview.notes > 0 ? ( ++ zusätzlich {modulePickPreview.notes}{' '} + {modulePickPreview.notes === 1 ? 'Position mit Hinweis' : 'Positionen mit Hinweisen'}{' '} + (ohne Aufzählung) +
+ ) : null} + > + )} ++ Neue Module kannst du unter{' '} + + Trainingsmodule + {' '} + anlegen. +
+- Alle Positionen des gewählten Moduls werden als neue Zeilen eingefügt (Kopie, mit klarer - Herkunft im Ablauf). Die Einheit brauchst du dafür nicht vorher gespeichert zu haben — Speichern am Ende - wie gewohnt. Vollständige Textsuche oder Modulkategorien planen wir serverseitig für - eine spätere Iteration; vorerst steht hier eine{' '} - Schnellsuche über Titel und Freitext-Felder zur Verfügung. -
- - {moduleApplyErr ? ( -{moduleApplyErr}
- ) : null} - - {moduleApplyPlacementLocked ? ( - <> -- Aktuelle Einfügeposition: Abschnitt {modulePlacementSummary.secTitle}{' '} - / {modulePlacementSummary.positionDescription} -
-- {!moduleApplyList.length ? 'Keine Module verfügbar oder keine Berechtigung.' : 'Kein Modul entspricht der Suche.'} -
- ) : ( - moduleApplyFilteredList.map((m) => { - const title = ((m.title || '').trim() || `Modul #${m.id}`).trim() - const visLbl = trainingVisibilityShortDE(m.visibility) - const nPos = typeof m.items_count === 'number' ? m.items_count : '—' - const selected = String(m.id) === String(moduleApplyModuleId) - return ( - - ) - }) - )} -- Übungen und Hinweise laden … -
- ) : modulePickPreview.err ? ( -- {modulePickPreview.err} -
- ) : !modulePickPreview.exercises.length && !modulePickPreview.notes ? ( -- Keine Übungspositionen in diesem Eintrag gefunden (prüfen, ob Übungen im Modul gültige IDs haben). -
- ) : ( - <> -- … und noch {modulePickPreview.exercises.length - 12} weitere Übungen in genau dieser Modulreihenfolge. -
- ) : null} - {modulePickPreview.notes > 0 ? ( -- zusätzlich {modulePickPreview.notes}{' '} - {modulePickPreview.notes === 1 ? 'Position mit Hinweis' : 'Positionen mit Hinweisen'}{' '} - (ohne Aufzählung) -
- ) : null} - > - )} -- Neue Module kannst du unter{' '} - - Trainingsmodule - {' '} - anlegen. -
-