# Leistungstest Design ## Date: 2026-03-23 ## Status: Approved ## Overview Create a "Leistungstest" (Performance Test) page for creating fitness test templates and assigning them to wrestlers. Each test records exercise results, total time, and rating. Results are tracked over time with progress visualization and leaderboards. ## Design ### Layout Single page with 4 tabs: - **Vorlagen** (πŸ“‹) β€” Create/edit/delete test templates - **Zuweisen** (πŸ“) β€” Assign template to wrestler, record results - **Ergebnisse** (πŸ“Š) β€” View results with progress tracking - **Leaderboard** (πŸ†) β€” Rankings by template Plus: Sidebar navigation item "Leistungstest" ### Tab 1: Vorlagen **Template List:** - Card for each template showing name, exercise list, usage count - Delete button on each card **Create Template Form:** - Name input - Dynamic list of exercises with target reps - Add/remove exercise buttons - Save button ### Tab 2: Zuweisen **Selection:** - Wrestler dropdown (shows names, not IDs) - Template dropdown (shows names, not IDs) **Test Form (when both selected):** - List of exercises from template - For each exercise: target reps input + actual result input - Total time input (minutes) - Overall rating (5 stars) - Notes textarea - Submit button ### Tab 3: Ergebnisse **Filters:** - Wrestler dropdown - Template dropdown **Results Table:** - Columns: Date, Wrestler, Template, Score (%), Rating, Time - Sorted by date (newest first) **Progress Section (when one wrestler + one template selected):** - Shows improvement over time for each exercise - Progress bars with percentage change ### Tab 4: Leaderboard **Selection:** - Template dropdown **Rankings Table:** - Columns: Rank, Wrestler, Score %, Rating, Time - Sorted by score (highest first) - Medal icons for top 3 (πŸ₯‡πŸ₯ˆπŸ₯‰) ## Data Models ### Backend Model: LeistungstestTemplate ```python class LeistungstestTemplate(models.Model): name = CharField(max_length=200) created_at = DateTimeField(auto_now_add=True) updated_at = DateTimeField(auto_now=True) class Meta: ordering = ['-created_at'] ``` ### Backend Model: LeistungstestTemplateExercise ```python class LeistungstestTemplateExercise(models.Model): template = ForeignKey(LeistungstestTemplate, related_name='exercises') exercise = ForeignKey('exercises.Exercise') target_reps = PositiveIntegerField() order = IntegerField(default=0) class Meta: ordering = ['template', 'order'] unique_together = ['template', 'exercise'] ``` ### Backend Model: LeistungstestResult ```python class LeistungstestResult(models.Model): template = ForeignKey(LeistungstestTemplate) wrestler = ForeignKey('wrestlers.Wrestler') total_time_minutes = PositiveIntegerField(null=True, blank=True) rating = PositiveSmallIntegerField(choices=[(1,1),(2,2),(3,3),(4,4),(5,5)], default=3) notes = TextField(blank=True) completed_at = DateTimeField(default=timezone.now) created_at = DateTimeField(auto_now_add=True) class Meta: ordering = ['-completed_at'] indexes = [ Index(fields=['wrestler']), Index(fields=['template']), Index(fields=['completed_at']), ] ``` ### Backend Model: LeistungstestResultItem ```python class LeistungstestResultItem(models.Model): result = ForeignKey(LeistungstestResult, related_name='items') exercise = ForeignKey('exercises.Exercise') target_reps = PositiveIntegerField() actual_reps = PositiveIntegerField() order = IntegerField(default=0) class Meta: ordering = ['result', 'order'] ``` ## API Endpoints ``` # Templates GET /api/v1/leistungstest/templates/ β€” List templates POST /api/v1/leistungstest/templates/ β€” Create template GET /api/v1/leistungstest/templates/{id}/ β€” Get template PATCH /api/v1/leistungstest/templates/{id}/ β€” Update template DELETE /api/v1/leistungstest/templates/{id}/ β€” Delete template # Template Exercises POST /api/v1/leistungstest/template-exercises/ β€” Add exercise to template DELETE /api/v1/leistungstest/template-exercises/{id}/ β€” Remove exercise # Results GET /api/v1/leistungstest/results/ β€” List results (filterable) POST /api/v1/leistungstest/results/ β€” Create result GET /api/v1/leistungstest/results/{id}/ β€” Get result DELETE /api/v1/leistungstest/results/{id}/ β€” Delete result # Leaderboard GET /api/v1/leistungstest/leaderboard/ β€” Get rankings by template ``` ## Response Shapes ### Template Response ```json { "id": 1, "name": "Kraft-Test", "exercises": [ {"id": 1, "exercise": 1, "exercise_name": "KlimmzΓΌge", "target_reps": 20, "order": 0}, {"id": 2, "exercise": 2, "exercise_name": "LiegestΓΌtze", "target_reps": 50, "order": 1} ], "usage_count": 12, "created_at": "2026-03-20T10:00:00Z" } ``` ### Create Result Request ```json { "template": 1, "wrestler": 1, "total_time_minutes": 12, "rating": 4, "notes": "Gute Leistung", "items": [ {"exercise": 1, "target_reps": 20, "actual_reps": 20}, {"exercise": 2, "target_reps": 50, "actual_reps": 48} ] } ``` ### Leaderboard Response ```json { "template": {"id": 1, "name": "Kraft-Test"}, "rankings": [ {"rank": 1, "wrestler": {"id": 2, "name": "Anna S."}, "score_percent": 100, "rating": 5, "time_minutes": 10}, {"rank": 2, "wrestler": {"id": 1, "name": "Max M."}, "score_percent": 96, "rating": 4, "time_minutes": 12} ] } ``` ## Implementation Notes - Wrestler and template dropdowns show names, not IDs (use SelectValue with find) - Score = (sum of actual_reps / sum of target_reps) * 100 - Results table shows score as percentage with progress bar - Leaderboard only shows wrestlers who have done the specific template - Progress tracking shows change in score between first and latest result for same wrestler+template