chore(version): update version and changelog for release 0.8.122
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m11s
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m11s
- Bumped APP_VERSION to 0.8.122 and updated the changelog to reflect new features. - Integrated useExerciseListCatalogsAndQuery hook in ExercisesListPage for improved exercise list management and data fetching. - Enhanced documentation to include new concepts for parallel training streams and their technical specifications. - Updated DOMAIN_MODEL and related technical specs to clarify the structure and functionality of training streams within units.
This commit is contained in:
parent
57a8957c93
commit
4235246cd7
|
|
@ -1,7 +1,7 @@
|
|||
# Shinkan Jinkendo - Fachliches Domänenmodell
|
||||
|
||||
**Version:** 0.4.5
|
||||
**Stand:** 2026-05-12 (Fachlicher Nutzerüberblick: `docs/FACHLICHE_NUTZERFUNKTIONEN.md`)
|
||||
**Version:** 0.4.6
|
||||
**Stand:** 2026-05-14 (Fachlicher Nutzerüberblick: `docs/FACHLICHE_NUTZERFUNKTIONEN.md`)
|
||||
**Basis:** `shinkan_anforderungsdokument_entwurf.md` + Fähigkeitsmatrix
|
||||
|
||||
---
|
||||
|
|
@ -474,6 +474,14 @@ skill_level_definitions (
|
|||
|
||||
**Konkretisierung (037/API):** `POST /api/training-units/from-framework-slot` legt eine geplante Einheit aus dem Slot‑Blueprint an; **`origin_framework_slot_id`** dient als Herkunftsreferenz (**Lineage light**; weiteres Feedback/Lineage‑Konzept: Konzeptpapier Schritt **E**).
|
||||
|
||||
### Parallele Trainingsstreams (Breakout, Entwurf)
|
||||
|
||||
**Fachlich:** Eine Kalender‑**Einheit** kann aus **Phasen** bestehen — z. B. gemeinsamer Block, dann **beliebig viele parallele** „Teilstrecken“ (**Streams**) mit je eigenem Miniplan (Abschnitte/Übungen), erneut gemeinsamer Block. Das ist **nicht** dasselbe wie ein **Rahmenprogramm‑Slot** (Serien‑Session über Wochen): Slots strukturieren **mehrere Einheiten** in einem Programm; **Streams** strukturieren **gleichzeitige** Abläufe **innerhalb einer** Einheit.
|
||||
|
||||
**Sonderfall Stationen:** Rotation kann **innerhalb** einer Stream‑Planung über **Kombinationsübungen** (Methodenprofil/Archetyp) abgebildet werden; hallenweit **synchron** getaktete Rotation ist eine **erweiterte** Ausbaustufe (siehe Fachkonzept).
|
||||
|
||||
**Dokumentation:** `functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md`, Umsetzung `technical/PARALLEL_TRAINING_STREAMS_SPEC.md`.
|
||||
|
||||
---
|
||||
|
||||
## Medien-Archiv & Übungs-Anhänge (Stand 2026-05-07)
|
||||
|
|
|
|||
106
.claude/docs/functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md
Normal file
106
.claude/docs/functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Parallele Trainingsstreams (Breakout) — Fachkonzept
|
||||
|
||||
**Status:** Entwurf zur Abstimmung · **Stand:** 2026-05-14
|
||||
**Ziel:** Planung und Durchführung von Training mit **phasenweise gemeinsamem** Ablauf und **beliebig vielen parallelen Teilstrecken** (Breakout-Sessions), inkl. Sonderfall **rotierende Stationen**.
|
||||
|
||||
**Technische Ausarbeitung:** `.claude/docs/technical/PARALLEL_TRAINING_STREAMS_SPEC.md`
|
||||
**Domänenbegriffe (Überblick):** `.claude/docs/functional/DOMAIN_MODEL.md` (Abschnitt Parallele Streams)
|
||||
|
||||
---
|
||||
|
||||
## 1. Ausgangslage und Problem
|
||||
|
||||
In Kinder- und Breitensport-Training ist ein typischer Ablauf:
|
||||
|
||||
1. **Gemeinsam:** Aufwärmen, Koordination, Ansagen.
|
||||
2. **Getrennt:** Kinder in mehrere Gruppen teilen; **Co-Trainer** leiten jeweils eigene Inhalte **gleichzeitig**.
|
||||
3. **Gemeinsam:** Abschluss, gemeinsame Übungen, Verabschiedung.
|
||||
|
||||
Die aktuelle Shinkan-Planung modelliert pro Termin **eine lineare Folge von Abschnitten und Übungen** pro Einheit. Das genügt nicht, wenn **mehrere gleichzeitige „Unter-Sessions“** mit unterschiedlichen Plänen dokumentiert und auf der Matte geführt werden sollen.
|
||||
|
||||
---
|
||||
|
||||
## 2. Ziele (fachlich)
|
||||
|
||||
| ID | Ziel |
|
||||
|----|------|
|
||||
| PT‑01 | Eine **Kalender-Einheit** bleibt **ein** Termin (eine Halle, eine Gruppe, ein Datum) — kein Splitten in künstlich mehrere Kalendereinträge nur für Parallelität. |
|
||||
| PT‑02 | **Unbegrenzte** Anzahl paralleler **Streams** (Teilstrecken) in einer oder mehreren **Parallelphasen**. |
|
||||
| PT‑03 | **Phasenmodell:** klar erkennbar **Gemeinsam** vs. **Parallel** vs. wieder **Gemeinsam** (auch mehrfach hintereinander möglich). |
|
||||
| PT‑04 | **Rollen:** Leitung (Haupttrainer) und Co-Trainer; Zuordnung der Co-Trainer **soll** an konkrete Streams anschließbar sein (heute: nur flache Liste pro Einheit — siehe technische Spec). |
|
||||
| PT‑05 | **Sonderfall Stationen:** rotierender Ablauf (z. B. Wechsel alle 20 Min.) **inhaltlich** unterscheiden zwischen (a) Rotation **innerhalb** einer Teilstrecke und (b) **synchron** getakteter Hallen-Rotation — siehe §5. |
|
||||
| PT‑06 | **Durchführung:** Trainer können „ihre“ Spur auf dem Gerät abarbeiten; Fortschritt pro Spur nachvollziehbar. |
|
||||
|
||||
**Nicht-Ziel (frühe Stufen):** Echtzeit-Synchronisation mehrerer Geräte; individuelles Athleten-Tracking; automatische Raumbelegung.
|
||||
|
||||
---
|
||||
|
||||
## 3. Begriffe
|
||||
|
||||
| Begriff | Definition |
|
||||
|---------|------------|
|
||||
| **Einheit / Termin** | Geplante `training_unit` für Gruppe und Datum — übergeordneter Rahmen des Abends. |
|
||||
| **Phase** | Organisatorischer Block innerhalb der Einheit: entweder **ganze Gruppe** oder **parallel**. |
|
||||
| **Stream / Teilstrecke** | Innerhalb einer Parallelphase: eine von N **gleichzeitig** stattfindenden Unter-Abläufen mit **eigenem** Miniplan (Abschnitte, Übungen, Notizen — analog heutiger Planung). |
|
||||
| **Synchronisationspunkt** | Fachlich: alle treffen sich wieder (Beginn einer **Gemeinschaftsphase** nach Parallelität). |
|
||||
| **Station (Rotation)** | Inhaltlicher Fokus oder Platz, den Teilnehmer **wechselnd** anlaufen; kann als Kombinations-/Zirkellogik oder als koordinierter Hallenrhythmus modelliert werden (§5). |
|
||||
|
||||
**Abgrenzung „Rahmenprogramm-Slot“:** Ein Slot im **Rahmenprogramm** ist eine **Session in einer Serie** (z. B. Woche 1 vs. Woche 2), **nicht** „Teilgruppe A gleichzeitig mit Teilgruppe B in derselben Stunde“. Parallele Streams sind **innerhalb einer Einheit**, orthogonal zum Rahmen-Slot.
|
||||
|
||||
**Abgrenzung **Kombinationsübung**:** Eine Kombi-Übung bündelt **mehrere Einzelübungen** mit Methodenprofil (Archetyp, ggf. Rotation) **in einem Plan-Item**. Sie ersetzt **nicht** mehrere Trainer mit **jeweils eigenem Gesamtablauf**, kann aber **pro Stream** für Stationslogik genutzt werden.
|
||||
|
||||
---
|
||||
|
||||
## 4. Szenarien
|
||||
|
||||
### 4.1 Klassischer Breakout
|
||||
|
||||
30 Min. gemeinsam → 25 Min. drei parallele Streams (Gruppe an Matte / an Schlagsack / Fußarbeit) → 15 Min. gemeinsam.
|
||||
|
||||
### 4.2 Viele Kinder, mehrere Co-Trainer
|
||||
|
||||
Haupttrainer plant die Gesamtstruktur; jeder Co-Trainer sieht in der Durchführung primär die zugewiesene Teilstrecke.
|
||||
|
||||
### 4.3 Rollierendes Stationssystem
|
||||
|
||||
Alle Gruppen arbeiten an **verschiedenen Schwerpunkten** und **wechseln** nach festem Intervall die Station — entweder **nur innerhalb einer Spur** oder **hallenweit synchron** (offene fachliche Präzisierung in MVP vs. später, §5).
|
||||
|
||||
---
|
||||
|
||||
## 5. Sonderfall: Stationen und Kombinationsübungen
|
||||
|
||||
### 5.1 Variante A — Rotation innerhalb einer Teilstrecke
|
||||
|
||||
Eine Teilgruppe rotiert durch mehrere Übungen (Zeit oder Runden). Das liegt nah an einer **Kombinationsübung** mit Archetyp z. B. „Zirkel / zeitgesteuerte Rotation“ und Parametern (Wechselintervall). **Empfehlung:** Diese Variante über **bestehendes** Kombinationsübungs-Konzept in der jeweiligen **Stream-Planung** abbilden (`planning_method_profile`).
|
||||
|
||||
### 5.2 Variante B — Synchron getaktete Hallen-Rotation
|
||||
|
||||
Alle Streams (oder alle Kinder insgesamt) **wechseln gleichzeitig** zur nächsten Station; Startstation kann pro Teilgruppe **versetzt** sein. Das ist **organisatorisch** schwerer: es braucht entweder **Phasen-Metadaten** (globaler Takt) oder eine explizite **Rot/Matrix**. **Empfehlung:** In einer **zweiten Ausbaustufe** abbilden; MVP kann bei Variante A starten, sofern fachlich ausreichend.
|
||||
|
||||
---
|
||||
|
||||
## 6. Rollen und Verantwortlichkeiten
|
||||
|
||||
- **Leitungstrainer:** Hält den Faden, startet Gemeinschaftsphasen, koordiniert Parallelbeginn/-ende (fachlich; ggf. später UI-Hinweise).
|
||||
- **Co-Trainer:** Verantwortlich für **zugeteilte** Streams; Zuordnung soll **pro Stream** möglich werden (Erweiterung gegenüber reiner Einheits-Co-Trainer-Liste).
|
||||
|
||||
---
|
||||
|
||||
## 7. Offene fachliche Entscheidungen
|
||||
|
||||
1. **MVP Umfang:** Reicht **freie Parallelität** ohne **synchronen** Hallenwechsel (Variante B)?
|
||||
2. **Dauer:** Sollen Phasen oder Streams **Soll-Minuten** tragen (nur Anzeige vs. später Timer)?
|
||||
3. **Vorlagen:** Müssen `training_plan_templates` parallel-fähig werden **vor** oder **mit** der ersten Implementierung?
|
||||
4. **Sichtbarkeit:** Dürfen alle Co-Trainer alle Streams sehen, oder „nur meine Spur“?
|
||||
|
||||
---
|
||||
|
||||
## 8. Verwandte Dokumente
|
||||
|
||||
| Dokument | Bezug |
|
||||
|----------|--------|
|
||||
| `technical/TRAINING_FRAMEWORK_SPEC.md` | Rahmen-Slots = Serien-Sessions, **nicht** Intra-Einheit-Parallelität |
|
||||
| `technical/TRAINING_MODULES_AND_COMBINATION_EXERCISES_SPEC.md` | Kombinationsübungen, Archetypen, Stationslogik **im Item** |
|
||||
| `functional/Shinkan Trainingsmodule Kombinationsuebungen Spezifikation V2.md` | Fachliche Tiefe Kombi |
|
||||
| `docs/FACHLICHE_NUTZERFUNKTIONEN.md` | Nutzerüberblick |
|
||||
| `technical/DATABASE_SCHEMA.md` | Aktueller Stand Tabellen |
|
||||
130
.claude/docs/technical/PARALLEL_TRAINING_STREAMS_SPEC.md
Normal file
130
.claude/docs/technical/PARALLEL_TRAINING_STREAMS_SPEC.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# Parallele Trainingsstreams — Technische Spezifikation (Umsetzung)
|
||||
|
||||
**Status:** Entwurf · **Stand:** 2026-05-14
|
||||
**Fachgrundlage:** `.claude/docs/functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md`
|
||||
|
||||
Dieses Dokument beschreibt die **Umsetzung** auf Basis der **aktuellen Codebasis** (Stand Analyse 2026-05-14): eine `training_unit` mit **`training_unit_sections`** und **`training_unit_section_items`** (Übung/Notiz, optional `planning_method_profile` für Kombinationsübungen, Migration **057**); Rahmen-**Blueprint**-Einheiten mit `framework_slot_id` (**037**); Leitung **`lead_trainer_profile_id`** (**038**); Co-Trainer **`assistant_trainer_profile_ids`** JSONB (**042**); Durchführung **`TrainingUnitRunPage`** (sequentiell über Sektionen).
|
||||
|
||||
---
|
||||
|
||||
## 1. Ist-Stand (relevant)
|
||||
|
||||
| Bereich | Aktuell |
|
||||
|---------|---------|
|
||||
| Planstruktur | **Eine** lineare Liste `training_unit_sections` je `training_unit_id`; Items in `training_unit_section_items`. |
|
||||
| Rahmenprogramm | `training_framework_slots` verweisen auf **Blueprint**-`training_units` — Slots = **Serien-Spalten**, nicht simultane Breakouts in **einer** Halle. |
|
||||
| Kombinationsübung | Ein **Item** kann Kombi sein; `planning_method_profile` = Snapshot; Coaching-UI teilweise (`CombinationPlanBracket` in Run/Peek). |
|
||||
| Trainer-Zuweisung | `lead_trainer_profile_id`, `assistant_trainer_profile_ids` am **`training_units`**-Kopf; **keine** Zuordnung zu „welcher parallelen Spur“. |
|
||||
| Run-Modus | `TrainingUnitRunPage`: sortierte Sektionen/Items, Checkliste, Fortschritt in `sessionStorage` pro Einheit. |
|
||||
|
||||
**Konsequenz:** Parallele Streams erfordern ein **erweitertes konzeptionelles „Gefäß“** unterhalb der Einheit (Phasen und/oder Streams) und eine **Verknüpfung** bestehender Sektionen mit diesem Gefäß — oder eine **Migration** zu einem neuen Pflicht-Container (siehe §3).
|
||||
|
||||
---
|
||||
|
||||
## 2. Zielarchitektur (logisch)
|
||||
|
||||
```
|
||||
training_unit (Kalender-Einheit)
|
||||
├── phase (order, kind: whole_group | parallel, optional Metadaten)
|
||||
│ ├── [whole_group] → sections[] → items[] (wie heute)
|
||||
│ └── [parallel] → stream (order, label, optional trainer_ids[])
|
||||
│ └── sections[] → items[]
|
||||
```
|
||||
|
||||
**Abwärtskompatibilität:** Einheiten **ohne** explizite Phasen/Streams verhalten sich wie heute: **implizit** eine einzige „Gemeinschaftsphase“ mit den vorhandenen Sektionen (Migration: alle bestehenden Sektionen an diese Default-Hülle hängen).
|
||||
|
||||
---
|
||||
|
||||
## 3. Datenmodell — Optionen
|
||||
|
||||
### 3.1 Empfohlen: explizite Phasen + Streams (normalisiert)
|
||||
|
||||
Neue Tabellen (Namen bei Implementierung final festlegen):
|
||||
|
||||
| Tabelle | Zweck |
|
||||
|---------|--------|
|
||||
| `training_unit_phases` | `training_unit_id`, `order_index`, `phase_kind` (`whole_group` \| `parallel`), optional `title`, `guidance_notes`, optional `planned_duration_min` |
|
||||
| `training_unit_parallel_streams` | `phase_id` (FK, nur wenn parent parallel), `order_index`, `title`/`label`, optional `notes`, optional `assigned_trainer_profile_ids` JSONB (oder 1:n-Hilfstabelle) |
|
||||
|
||||
**Anpassung `training_unit_sections`:** Zusätzliche FK-Spalte(n), z. B.:
|
||||
|
||||
- `phase_id` **NULL** und `parallel_stream_id` **NULL** → **Legacy / Default-Einheitsphase** (Migration setzt Default-Phase); oder
|
||||
- genau einer von `phase_id` (whole group) oder `parallel_stream_id` gesetzt.
|
||||
|
||||
**Constraints:** CHECK: nicht beide gesetzt; bei `phase_kind = parallel` Sektionen nur unter `parallel_stream_id`; bei `whole_group` nur unter `phase_id`.
|
||||
|
||||
**Vorteil:** Klare Semantik, Reporting, API-Shape konsistent.
|
||||
|
||||
### 3.2 Minimalvariante (nicht ideal fachlich)
|
||||
|
||||
Nur **`training_unit_parallel_streams`** + `parallel_stream_id` auf Sektionen; Phasen implizit durch „Marker“-Sektionen oder Konvention. **Nicht empfohlen**, erschwert UI und Erklärbarkeit.
|
||||
|
||||
---
|
||||
|
||||
## 4. API
|
||||
|
||||
- **`GET /api/training-units/:id`** (und Listen-Payloads wo vollständiger Plan nötig): verschachtelte Struktur **Phasen → Streams → sections → items** oder flache `sections` mit ausgefüllten `phase_id` / `parallel_stream_id` (Frontend kann normalisieren).
|
||||
- **`PUT/PATCH`:** Atomares Ersetzen der Phasen/Streams/Sektionen analog zu bestehendem `_replace_unit_sections`-Muster; **Validierung** der CHECK-Regeln serverseitig.
|
||||
- **Blueprint / Rahmen:** Blueprint-`training_units` dürfen dieselbe Struktur tragen; `GET` Kalenderliste blendet Blueprints weiter aus (`framework_slot_id IS NOT NULL`).
|
||||
|
||||
**Governance / Mandant:** Unverändert über Einheit → `group_id`; keine neuen Mandanten-Entitäten.
|
||||
|
||||
---
|
||||
|
||||
## 5. Frontend
|
||||
|
||||
### 5.1 Planung (`TrainingPlanningPage`)
|
||||
|
||||
- Darstellung als **vertikale Phasen**: Gemeinschaftsblöcke + Parallelphase mit **N Spalten** (Streams).
|
||||
- **Wiederverwendung:** `TrainingUnitSectionsEditor` **pro Stream** und pro Gemeinschaftsphase — analog zur Wiederverwendung **pro Rahmen-Slot** in `TrainingFrameworkProgramEditPage`.
|
||||
- **Co-Trainer:** UI pro Stream (`assigned_trainer_profile_ids`); Regel zur **Kopfliste** `assistant_trainer_profile_ids` festlegen (z. B. Union aller Stream-Zuweisungen für „Wer ist heute dabei“ + Rückwärtskompatibilität wenn Stream-Felder leer).
|
||||
|
||||
### 5.2 Durchführung (`TrainingUnitRunPage`)
|
||||
|
||||
- Gemeinschaftsphasen: heutiges **lineares** Verhalten.
|
||||
- Parallelphase: **Tabs, Akkordeon oder Swipe** zwischen Streams; Fortschritt **pro Stream** (Storage-Key z. B. `${unitId}:${streamId}`).
|
||||
- Kombi-Items: unverändert `CombinationPlanBracket` / `effectiveComboMethodProfile`.
|
||||
- Optional später: Filter „nur meine Spur“ anhand Session-Profil vs. Stream-Zuweisung.
|
||||
|
||||
### 5.3 Vorlagen (`training_plan_templates`)
|
||||
|
||||
- Erweiterung um **dieselbe** Phasen/Streams-Semantik (Kindtabellen oder serialisiertes JSON — Abgleich mit Kopierlogik aus Vorlage in Einheit).
|
||||
- **Kein** Live-Spiegel: weiterhin Materialisierung beim Anwenden.
|
||||
|
||||
---
|
||||
|
||||
## 6. Bezug Kombinationsübungen
|
||||
|
||||
- **Variante A** (Rotation innerhalb einer Teilstrecke): ein oder mehrere **Items** vom Typ Kombi im jeweiligen Stream; Archetyp und Parameter wie in `TRAINING_MODULES_AND_COMBINATION_EXERCISES_SPEC.md`.
|
||||
- **Variante B** (synchron Hallenweit): erweiterte **Phasen-** oder **Stream-übergreifende** Metadaten — **nicht** in MVP-Zwang; eigenes Teilpaket nach fachlicher Freigabe (`PARALLEL_TRAINING_STREAMS_CONCEPT.md` §5.2).
|
||||
|
||||
---
|
||||
|
||||
## 7. Migration und Risiken
|
||||
|
||||
1. **Datenmigration:** Alle existierenden `training_unit_sections` einer Einheit einer **Default-Phase** `whole_group` zuordnen.
|
||||
2. **API-Versionierung:** Clients, die nur flache `sections` erwarten, müssen angepasst werden (oder Server liefert **beides** kurzzeitig — nur wenn nötig).
|
||||
3. **Performance:** Tiefe Kopien (Rahmen-Slot, Duplikat Einheit) müssen rekursiv Phasen/Streams mitsamt Sektionen/Items kopieren.
|
||||
4. **Tests:** pytest für PUT/GET mit gemischten Phasen; ggf. Playwright-Smoke für Planung/Run.
|
||||
|
||||
---
|
||||
|
||||
## 8. Implementierungsphasen (Vorschlag)
|
||||
|
||||
| Phase | Inhalt |
|
||||
|-------|--------|
|
||||
| **P1** | Schema Phasen + Streams; Migration; GET/PATCH Einheit verschachtelt; Planungs-UI; Run-UI mit Stream-Tabs |
|
||||
| **P2** | Trainer-Zuordnung pro Stream + effektive Anzeige; Vorlagen erweitert |
|
||||
| **P3** | Synchroner Hallen-Takt / Rotationsmatrix (falls fachlich freigegeben) |
|
||||
|
||||
---
|
||||
|
||||
## 9. Verwandte Dokumente
|
||||
|
||||
| Dokument | Bezug |
|
||||
|----------|--------|
|
||||
| `functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md` | Fachziele, Begriffe, Entscheidungsfragen |
|
||||
| `technical/TRAINING_FRAMEWORK_SPEC.md` | Rahmen-Slot vs. Parallelität |
|
||||
| `technical/TRAINING_MODULES_AND_COMBINATION_EXERCISES_SPEC.md` | Kombi, `planning_method_profile` |
|
||||
| `technical/DATABASE_SCHEMA.md`, `backend/migrations/` | DDL-Historie |
|
||||
| `frontend/src/pages/TrainingPlanningPage.jsx`, `TrainingUnitRunPage.jsx`, `TrainingFrameworkProgramEditPage.jsx` | Ist-UI |
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
| `DATABASE_SCHEMA.md` | **Nachgeordnete** Übersicht: Migrationshistorie und Tabellenliste; Detail-DDL primär **hier §2–§3** + SQL unter `backend/migrations/`. |
|
||||
| `functional/DOMAIN_MODEL.md` | Fachliche Begriffe; Kurzverweis auf Progressionsgraph ergänzt. |
|
||||
| `TRAINING_CURRICULUM_AND_GOVERNANCE_CONCEPT.md` | **Was** und **warum** (Bibliothek vs. Instanz, Governance, CURR‑Tabelle). |
|
||||
| `functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md`, `technical/PARALLEL_TRAINING_STREAMS_SPEC.md` | **Parallele Streams / Breakout innerhalb einer Einheit** — orthogonale Domäne zu **Rahmen‑Slots** (Serien‑Sessions). |
|
||||
|
||||
**Konsequenz:** Diese Datei bleibt der **technische Arbeitspool** für Rahmenprogramm Stufe 1–2. Abschnitt **§4** beschreibt explizit den **aktuellen Produktfreigabe-Umfang** und **bekannte Lücken** (damit Trainingsplanung weiter gebaut werden kann ohne falscher Erwartung an „Alternative‑Pakete“ in der UI).
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
| Dokument | Bezug |
|
||||
|----------|--------|
|
||||
| `TRAINING_FRAMEWORK_SPEC.md` | Rahmen-Bibliothek, Slot-Blueprint, Kopiersemantik (`from-framework-slot`) |
|
||||
| `functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md`, `technical/PARALLEL_TRAINING_STREAMS_SPEC.md` | Parallele Teilstrecken **innerhalb einer Einheit**; Kombi-Übungen weiter nutzbar **pro Stream** für Stationsrotation |
|
||||
| `DATABASE_SCHEMA.md` | Aktueller Stand `training_units`, Sektionen, Items |
|
||||
| `functional/DOMAIN_MODEL.md` | Domänenbegriffe (bei Bedarf zu erweitern) |
|
||||
| `EXERCISES_*` (Katalog) | Einzelübungen, Varianten |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Shinkan Jinkendo Version Information
|
||||
|
||||
APP_VERSION = "0.8.121"
|
||||
APP_VERSION = "0.8.122"
|
||||
BUILD_DATE = "2026-05-12"
|
||||
DB_SCHEMA_VERSION = "20260514062"
|
||||
|
||||
|
|
@ -36,6 +36,13 @@ MODULE_VERSIONS = {
|
|||
}
|
||||
|
||||
CHANGELOG = [
|
||||
{
|
||||
"version": "0.8.122",
|
||||
"date": "2026-05-13",
|
||||
"changes": [
|
||||
"Frontend Phase 3 (Teil): useExerciseListCatalogsAndQuery — Katalog-Fetch und Übungslisten-Laden/Keyset-Pagination aus ExercisesListPage in Hook ausgelagert.",
|
||||
],
|
||||
},
|
||||
{
|
||||
"version": "0.8.121",
|
||||
"date": "2026-05-13",
|
||||
|
|
|
|||
120
frontend/src/hooks/useExerciseListCatalogsAndQuery.js
Normal file
120
frontend/src/hooks/useExerciseListCatalogsAndQuery.js
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import { useState, useEffect, useCallback } from 'react'
|
||||
import api from '../utils/api'
|
||||
|
||||
export const EXERCISE_LIST_PAGE_SIZE = 100
|
||||
|
||||
/**
|
||||
* Lädt Kataloge für Filter/Bulk einmalig und hält die Übungsliste (Offset + Keyset „Mehr laden“) synchron zu queryBase.
|
||||
*/
|
||||
export function useExerciseListCatalogsAndQuery({ queryBase, pageTab, tenantClubDepKey }) {
|
||||
const [catalogs, setCatalogs] = useState({
|
||||
focusAreas: [],
|
||||
styleDirections: [],
|
||||
trainingTypes: [],
|
||||
targetGroups: [],
|
||||
skills: [],
|
||||
})
|
||||
const [catalogsReady, setCatalogsReady] = useState(false)
|
||||
const [exercises, setExercises] = useState([])
|
||||
const [listFetching, setListFetching] = useState(false)
|
||||
const [loadingMore, setLoadingMore] = useState(false)
|
||||
const [hasMore, setHasMore] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
;(async () => {
|
||||
try {
|
||||
const [fa, sd, tt, tg, sk] = await Promise.all([
|
||||
api.listFocusAreas(),
|
||||
api.listStyleDirections(),
|
||||
api.listTrainingTypes(),
|
||||
api.listTargetGroups(),
|
||||
api.listSkills(),
|
||||
])
|
||||
if (!cancelled) {
|
||||
setCatalogs({
|
||||
focusAreas: fa,
|
||||
styleDirections: sd,
|
||||
trainingTypes: tt,
|
||||
targetGroups: tg,
|
||||
skills: sk,
|
||||
})
|
||||
setCatalogsReady(true)
|
||||
}
|
||||
} catch (err) {
|
||||
if (!cancelled) {
|
||||
console.error(err)
|
||||
alert('Kataloge konnten nicht geladen werden: ' + err.message)
|
||||
setCatalogsReady(true)
|
||||
}
|
||||
}
|
||||
})()
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!catalogsReady || pageTab !== 'list') return
|
||||
let cancelled = false
|
||||
const run = async () => {
|
||||
setListFetching(true)
|
||||
try {
|
||||
const batch = await api.listExercises({
|
||||
...queryBase,
|
||||
limit: EXERCISE_LIST_PAGE_SIZE,
|
||||
offset: 0,
|
||||
})
|
||||
if (cancelled) return
|
||||
setExercises(batch)
|
||||
setHasMore(batch.length === EXERCISE_LIST_PAGE_SIZE)
|
||||
} catch (err) {
|
||||
if (!cancelled) {
|
||||
console.error('Failed to load data:', err)
|
||||
alert('Fehler beim Laden: ' + err.message)
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) setListFetching(false)
|
||||
}
|
||||
}
|
||||
run()
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [queryBase, catalogsReady, pageTab, tenantClubDepKey])
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
if (loadingMore || !hasMore) return
|
||||
const last = exercises[exercises.length - 1]
|
||||
if (!last?.id || last.updated_at == null) return
|
||||
setLoadingMore(true)
|
||||
try {
|
||||
const batch = await api.listExercises({
|
||||
...queryBase,
|
||||
limit: EXERCISE_LIST_PAGE_SIZE,
|
||||
cursor_updated_at:
|
||||
typeof last.updated_at === 'string'
|
||||
? last.updated_at
|
||||
: new Date(last.updated_at).toISOString(),
|
||||
cursor_id: last.id,
|
||||
})
|
||||
setExercises((prev) => [...prev, ...batch])
|
||||
setHasMore(batch.length === EXERCISE_LIST_PAGE_SIZE)
|
||||
} catch (err) {
|
||||
alert('Fehler: ' + err.message)
|
||||
} finally {
|
||||
setLoadingMore(false)
|
||||
}
|
||||
}, [loadingMore, hasMore, exercises, queryBase])
|
||||
|
||||
return {
|
||||
catalogs,
|
||||
catalogsReady,
|
||||
exercises,
|
||||
setExercises,
|
||||
listFetching,
|
||||
loadingMore,
|
||||
hasMore,
|
||||
loadMore,
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import ExerciseListBulkModal from '../components/exercises/ExerciseListBulkModal
|
|||
import ExerciseListSearchBar from '../components/exercises/ExerciseListSearchBar'
|
||||
import { buildExerciseListFilterChips } from '../utils/exerciseListFilterChips'
|
||||
import { applyDashboardExerciseListUrl, buildExerciseListQueryBase } from '../utils/exerciseListQuery'
|
||||
import { useExerciseListCatalogsAndQuery } from '../hooks/useExerciseListCatalogsAndQuery'
|
||||
import {
|
||||
INITIAL_EXERCISE_LIST_FILTERS,
|
||||
mergeExerciseListPrefsFromApi,
|
||||
|
|
@ -18,7 +19,6 @@ import {
|
|||
|
||||
const ExerciseProgressionGraphPanel = lazy(() => import('../components/ExerciseProgressionGraphPanel'))
|
||||
|
||||
const PAGE_SIZE = 100
|
||||
const BULK_MAX_IDS = 500
|
||||
const EXERCISES_PAGE_TABS = [
|
||||
{ id: 'list', label: 'Liste' },
|
||||
|
|
@ -40,18 +40,6 @@ function ExercisesListPage() {
|
|||
}
|
||||
})
|
||||
|
||||
const [exercises, setExercises] = useState([])
|
||||
const [catalogs, setCatalogs] = useState({
|
||||
focusAreas: [],
|
||||
styleDirections: [],
|
||||
trainingTypes: [],
|
||||
targetGroups: [],
|
||||
skills: [],
|
||||
})
|
||||
const [catalogsReady, setCatalogsReady] = useState(false)
|
||||
const [listFetching, setListFetching] = useState(false)
|
||||
const [loadingMore, setLoadingMore] = useState(false)
|
||||
const [hasMore, setHasMore] = useState(false)
|
||||
const [searchInput, setSearchInput] = useState('')
|
||||
const [aiSearchInput, setAiSearchInput] = useState('')
|
||||
const [debouncedSearch, setDebouncedSearch] = useState('')
|
||||
|
|
@ -115,6 +103,26 @@ function ExercisesListPage() {
|
|||
return () => window.removeEventListener('keydown', onKey)
|
||||
}, [filterModalOpen])
|
||||
|
||||
const queryBase = useMemo(
|
||||
() => buildExerciseListQueryBase(filters, debouncedSearch, debouncedAiSearch, mineOnly),
|
||||
[filters, debouncedSearch, debouncedAiSearch, mineOnly]
|
||||
)
|
||||
|
||||
const {
|
||||
catalogs,
|
||||
catalogsReady,
|
||||
exercises,
|
||||
setExercises,
|
||||
listFetching,
|
||||
loadingMore,
|
||||
hasMore,
|
||||
loadMore,
|
||||
} = useExerciseListCatalogsAndQuery({ queryBase, pageTab, tenantClubDepKey })
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedIds(new Set())
|
||||
}, [queryBase])
|
||||
|
||||
const focusOptions = useMemo(
|
||||
() =>
|
||||
catalogs.focusAreas.map((fa) => ({
|
||||
|
|
@ -191,15 +199,6 @@ function ExercisesListPage() {
|
|||
return [...new Set(titles)].slice(0, 80)
|
||||
}, [exercises])
|
||||
|
||||
const queryBase = useMemo(
|
||||
() => buildExerciseListQueryBase(filters, debouncedSearch, debouncedAiSearch, mineOnly),
|
||||
[filters, debouncedSearch, debouncedAiSearch, mineOnly]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedIds(new Set())
|
||||
}, [queryBase])
|
||||
|
||||
const clubNameById = useMemo(() => {
|
||||
const m = {}
|
||||
for (const c of activeClubMemberships(user?.clubs)) {
|
||||
|
|
@ -257,89 +256,6 @@ function ExercisesListPage() {
|
|||
return base
|
||||
}, [isSuperadmin])
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
;(async () => {
|
||||
try {
|
||||
const [fa, sd, tt, tg, sk] = await Promise.all([
|
||||
api.listFocusAreas(),
|
||||
api.listStyleDirections(),
|
||||
api.listTrainingTypes(),
|
||||
api.listTargetGroups(),
|
||||
api.listSkills(),
|
||||
])
|
||||
if (!cancelled) {
|
||||
setCatalogs({
|
||||
focusAreas: fa,
|
||||
styleDirections: sd,
|
||||
trainingTypes: tt,
|
||||
targetGroups: tg,
|
||||
skills: sk,
|
||||
})
|
||||
setCatalogsReady(true)
|
||||
}
|
||||
} catch (err) {
|
||||
if (!cancelled) {
|
||||
console.error(err)
|
||||
alert('Kataloge konnten nicht geladen werden: ' + err.message)
|
||||
setCatalogsReady(true)
|
||||
}
|
||||
}
|
||||
})()
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!catalogsReady || pageTab !== 'list') return
|
||||
let cancelled = false
|
||||
const run = async () => {
|
||||
setListFetching(true)
|
||||
try {
|
||||
const batch = await api.listExercises({ ...queryBase, limit: PAGE_SIZE, offset: 0 })
|
||||
if (cancelled) return
|
||||
setExercises(batch)
|
||||
setHasMore(batch.length === PAGE_SIZE)
|
||||
} catch (err) {
|
||||
if (!cancelled) {
|
||||
console.error('Failed to load data:', err)
|
||||
alert('Fehler beim Laden: ' + err.message)
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) setListFetching(false)
|
||||
}
|
||||
}
|
||||
run()
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [queryBase, catalogsReady, pageTab, tenantClubDepKey])
|
||||
|
||||
const loadMore = async () => {
|
||||
if (loadingMore || !hasMore) return
|
||||
const last = exercises[exercises.length - 1]
|
||||
if (!last?.id || last.updated_at == null) return
|
||||
setLoadingMore(true)
|
||||
try {
|
||||
const batch = await api.listExercises({
|
||||
...queryBase,
|
||||
limit: PAGE_SIZE,
|
||||
cursor_updated_at:
|
||||
typeof last.updated_at === 'string'
|
||||
? last.updated_at
|
||||
: new Date(last.updated_at).toISOString(),
|
||||
cursor_id: last.id,
|
||||
})
|
||||
setExercises((prev) => [...prev, ...batch])
|
||||
setHasMore(batch.length === PAGE_SIZE)
|
||||
} catch (err) {
|
||||
alert('Fehler: ' + err.message)
|
||||
} finally {
|
||||
setLoadingMore(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (exercise) => {
|
||||
if (!confirm(`Übung "${exercise.title}" wirklich löschen?`)) return
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user