3fefc550fe
- Django backend with DRF (clubs, wrestlers, trainers, exercises, templates, trainings, homework, locations, leistungstest) - Next.js 16 frontend with React, Shadcn UI, Tailwind - JWT authentication - Full CRUD for all entities - Calendar view for trainings - Homework management system - Leistungstest tracking
189 lines
5.2 KiB
Markdown
189 lines
5.2 KiB
Markdown
# Training Log & Progress Analysis Design
|
||
|
||
## Date: 2026-03-23
|
||
|
||
## Status: Approved
|
||
|
||
## Overview
|
||
|
||
Create a "Training Log" page for recording actual exercises performed by wrestlers during training sessions. The system tracks detailed exercise data (reps, sets, time, weight, rating) and provides analysis features including wrestler comparison and progress tracking over time.
|
||
|
||
## Design
|
||
|
||
### Layout
|
||
|
||
Single page with 3 tabs:
|
||
- **Log** (📋) — Quick entry form for new exercise records
|
||
- **Historie** (📜) — Filterable list of all entries
|
||
- **Analyse** (📊) — Summary stats, progress tracking, and wrestler comparison
|
||
|
||
### Tab 1: Log
|
||
|
||
**Entry Form:**
|
||
- Ringer dropdown (required)
|
||
- Training dropdown (optional — links to specific training session)
|
||
- Übung dropdown (required — from exercises)
|
||
- reps input (number, required)
|
||
- sets input (number, default 1)
|
||
- zeit input (minutes, optional)
|
||
- Gewicht input (kg, optional)
|
||
- Bewertung — 5-star rating selector
|
||
- Notizen textarea (optional)
|
||
- Speichern button
|
||
|
||
### Tab 2: Historie
|
||
|
||
**Filter Bar:**
|
||
- Ringer dropdown (filter by wrestler)
|
||
- Datum dropdown (date range)
|
||
- Übung dropdown (filter by exercise)
|
||
- Suchen button
|
||
|
||
**Entry List:**
|
||
- Table showing: Datum, Ringer, Übung, reps×sets, Zeit, Gewicht, Bewertung
|
||
- Clickable rows for potential editing
|
||
- Sorted by date (newest first)
|
||
|
||
### Tab 3: Analyse
|
||
|
||
**Wrestler Selector:**
|
||
- Dropdown to select specific wrestler
|
||
- OR "Alle vergleichen" option
|
||
|
||
**Summary Card:**
|
||
- Gesamt: total entries
|
||
- Verschiedene Übungen: count of unique exercises
|
||
- Wiederholungen: total reps
|
||
- Ø Sätze: average sets per entry
|
||
- Ø Bewertung: average rating
|
||
- Diese Woche: entries this week
|
||
|
||
**Top Übungen Card:**
|
||
- List of most performed exercises with counts
|
||
- Bar visualization
|
||
|
||
**Fortschritt Card:**
|
||
- Shows improvement over time for selected wrestler
|
||
- Progress bars with percentage change
|
||
- Only exercises with measurable progress
|
||
|
||
**Übungsvergleich Card:**
|
||
- Side-by-side comparison of two wrestlers
|
||
- Bar chart showing reps for same exercise types
|
||
- Only shows exercises both wrestlers have done
|
||
|
||
## Data Models
|
||
|
||
### Backend Model: TrainingLogEntry
|
||
|
||
```python
|
||
class TrainingLogEntry(models.Model):
|
||
wrestler = ForeignKey('wrestlers.Wrestler')
|
||
training = ForeignKey('trainings.Training', null=True, blank=True)
|
||
exercise = ForeignKey('exercises.Exercise')
|
||
reps = PositiveIntegerField()
|
||
sets = PositiveIntegerField(default=1)
|
||
time_minutes = PositiveIntegerField(null=True, blank=True)
|
||
weight_kg = DecimalField(null=True, blank=True, max_digits=5, decimal_places=2)
|
||
rating = PositiveIntegerField(choices=[(1,1),(2,2),(3,3),(4,4),(5,5)])
|
||
notes = TextField(blank=True)
|
||
logged_at = DateTimeField(default=timezone.now)
|
||
created_at = DateTimeField(auto_now_add=True)
|
||
updated_at = DateTimeField(auto_now=True)
|
||
|
||
class Meta:
|
||
ordering = ['-logged_at']
|
||
indexes = [
|
||
Index(fields=['wrestler']),
|
||
Index(fields=['exercise']),
|
||
Index(fields=['logged_at']),
|
||
]
|
||
```
|
||
|
||
## API Endpoints
|
||
|
||
```
|
||
GET /api/v1/training-log/ — List entries (filterable)
|
||
POST /api/v1/training-log/ — Create entry
|
||
GET /api/v1/training-log/{id}/ — Get single entry
|
||
PATCH /api/v1/training-log/{id}/ — Update entry
|
||
DELETE /api/v1/training-log/{id}/ — Delete entry
|
||
GET /api/v1/training-log/stats/ — Analysis stats
|
||
GET /api/v1/training-log/compare/ — Comparison data
|
||
```
|
||
|
||
## Response Shapes
|
||
|
||
### List Response (GET /training-log/)
|
||
```json
|
||
{
|
||
"count": 156,
|
||
"results": [
|
||
{
|
||
"id": 1,
|
||
"wrestler": 1,
|
||
"wrestler_name": "Max M.",
|
||
"training": 5,
|
||
"training_date": "2026-03-23",
|
||
"exercise": 1,
|
||
"exercise_name": "Pushups",
|
||
"reps": 50,
|
||
"sets": 3,
|
||
"time_minutes": 2,
|
||
"weight_kg": 10.0,
|
||
"rating": 4,
|
||
"notes": "",
|
||
"logged_at": "2026-03-23T15:30:00Z"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### Stats Response (GET /training-log/stats/?wrestler=1)
|
||
```json
|
||
{
|
||
"total_entries": 156,
|
||
"unique_exercises": 12,
|
||
"total_reps": 4230,
|
||
"avg_sets": 3.2,
|
||
"avg_rating": 3.8,
|
||
"this_week": 23,
|
||
"top_exercises": [
|
||
{"name": "Pushups", "count": 45},
|
||
{"name": "Klimmzüge", "count": 32}
|
||
],
|
||
"progress": {
|
||
"Pushups": {"before": 40, "after": 50, "change_percent": 25},
|
||
"Klimmzüge": {"before": 8, "after": 10, "change_percent": 40}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Compare Response (GET /training-log/compare/?wrestler1=1&wrestler2=2)
|
||
```json
|
||
{
|
||
"wrestler1": {"id": 1, "name": "Max M."},
|
||
"wrestler2": {"id": 2, "name": "Anna S."},
|
||
"exercises": [
|
||
{
|
||
"exercise": "Pushups",
|
||
"wrestler1_avg": 50,
|
||
"wrestler2_avg": 30
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
## Implementation Steps
|
||
|
||
1. **Backend Model** — Create TrainingLogEntry model in homework app (or new training_log app)
|
||
2. **Backend ViewSet** — CRUD endpoints + stats endpoint + compare endpoint
|
||
3. **Frontend API Types** — Add ITrainingLogEntry interface
|
||
4. **Frontend Page** — Create /training-log page with 3 tabs
|
||
|
||
## Notes
|
||
- Training dropdown should only show trainings from the past (can't log future)
|
||
- Weight should accept decimals (e.g., 10.5 kg)
|
||
- Rating is 1-5 stars
|
||
- All timestamps stored in UTC, displayed in local time
|