Files
WrestleDesk/docs/superpowers/specs/2026-03-24-leistungstest-live-timer-design.md
Andrej Spielmann 3fefc550fe Initial commit: WrestleDesk full project
- 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
2026-03-26 13:24:57 +01:00

256 lines
8.3 KiB
Markdown

# Leistungstest Live-Timer — Specification
## Overview
Add a live-timer mode to the Leistungstest "Zuweisen" tab. Instead of manually entering results for each wrestler, trainers can run through a timed workout session: select a template and multiple wrestlers, then start a live timer that tracks per-wrestler time and reps as they complete exercises in sequence. Results are saved immediately per wrestler.
## Architecture
### Two Modes in the Zuweisen Tab
The Zuweisen tab has two modes, toggled by a "Modus" switcher above the form:
1. **Form Mode** (default, existing behavior): Template + Wrestler selection → manual time/reps entry → save
2. **Timer Mode** (new): Template + Wrestler selection → live timer interface → results saved per wrestler automatically
### Mode Toggle
```tsx
const [inputMode, setInputMode] = useState<"form" | "timer">("form")
```
- Toggle switch above the form
- Form Mode: existing inputs for template, wrestlers, time (min+sec), rating, notes
- Timer Mode: shows "Training starten" button when wrestlers selected
---
## Timer Mode Data Flow
### State
```typescript
interface TimerWrestler {
wrestler: IWrestler
status: "pending" | "active" | "done"
startedAt: number | null // Date.now() when started
exercises: TimerExercise[]
resultId: number | null // created after first wrestler finishes
}
interface TimerExercise {
exerciseId: number
exerciseName: string
targetReps: number // from template
actualReps: string // user input
status: "pending" | "done"
startedAt: number | null
}
interface TimerSession {
templateId: number
wrestlers: TimerWrestler[]
currentWrestlerIndex: number
totalElapsedSeconds: number
isRunning: boolean
}
```
### Session Persistence (localStorage)
```typescript
// Key: "leistungstest_timer_session"
// Saved on every state change
// Restored on page load if session exists and isRunning === false (interrupted)
```
On page load, if an incomplete session is found in localStorage:
- Prompt: "Offenes Training gefunden. Fortsetzen?" → Yes restores session, No clears it
---
## UI Layout — Timer Mode
### Split View
```
┌─────────────────────┬──────────────────────────────────────────┐
│ RINGER │ AKTUELLER RINGER: Anna Schmidt │
│ │ │
│ ○ Max Mustermann │ ┌──────────────┐ [Pausieren] │
│ ● Anna Schmidt │ │ 05:23 │ │
│ ○ Tom Klein │ └──────────────┘ │
│ │ │
│ │ ÜBUNGEN (2/5) │
│ │ ─────────────────────────────────────── │
│ │ Liegestütze Soll: 3x10 │
│ │ [___ Ist-Reps] ▶ START │
│ │ │
│ │ Kniebeugen Soll: 3x15 │
│ │ [___ Ist-Reps] ✅ ERLEDIGT │
│ │ │
│ │ Burpees Soll: 3x8 │
│ │ [___ Ist-Reps] ▶ START │
│ │ │
│ │ ─────────────────────────────────────── │
│ │ [WEITER ZUM NÄCHSTEN RINGER →] │
│ │ [TRAINING BEENDEN] │
└─────────────────────┴──────────────────────────────────────────┘
```
### Components
**Left Panel (wrestler list):**
- Shows all selected wrestlers
- Status indicator: ○ pending, ● active, ✅ done
- Click on a done wrestler to review/edit their results
- Stays visible throughout
**Right Panel — Top (current wrestler + timer):**
- Wrestler name prominently displayed
- Large timer display: MM:SS format, updates every second
- Pause/Resume button
- Total elapsed time (cumulative across all wrestlers)
**Right Panel — Middle (exercise list):**
- List of exercises from template
- Each row shows: exercise name, target reps ("Soll")
- Input field for actual reps
- Start/Done button per exercise
- When exercise is marked done: saves to that wrestler's result (if result exists) or marks pending
**Right Panel — Bottom (actions):**
- "Weiter zum nächsten Ringer" → marks current wrestler done, saves result, advances
- "Training beenden" → final save, exits timer mode, shows summary
---
## Timer Logic
### Starting
1. User selects template (Sheet) + wrestlers (Sheet)
2. Clicks "Training starten"
3. Timer mode activates, first wrestler is set to "active", timer starts
### Per-Exercise Flow
1. Trainer sees current exercise (from template)
2. Enters actual reps in input field
3. Clicks "Start" → exercise timer starts
4. Wrestler completes exercise
5. Trainer clicks "Done" → exercise marked complete, elapsed time recorded
6. Next exercise auto-advances to active state
### Per-Wrestler Flow
1. All exercises done for current wrestler
2. Trainer clicks "Weiter zum nächsten Ringer"
3. Result is saved immediately via API:
- POST `/leistungstest/results/` with all exercise items
- `total_time_seconds` = elapsed time for this wrestler
4. Next wrestler becomes active, timer continues (does NOT reset)
5. Repeat until all wrestlers done
### Finishing
1. Trainer clicks "Training beenden"
2. If current wrestler has started but not all exercises done → prompt: "Nicht alle Übungen gemacht. Trotzdem beenden?"
3. Confirm → save current wrestler's partial result
4. Show summary: wrestlers completed, total time, scores
### Pause/Resume
- "Pausieren" stops the timer
- Timer display shows "PAUSIERT" in orange
- "Fortsetzen" resumes
- Paused time is accumulated in `totalElapsedSeconds`
---
## Backend API
No new endpoints needed. Use existing:
- `POST /leistungstest/results/` — create result for each wrestler
- `GET /leistungstest/results/?template=X` — list results
### Create Result Payload
```json
{
"template": 1,
"wrestler": 5,
"total_time_seconds": 323,
"rating": 3,
"notes": "",
"items": [
{ "exercise": 3, "target_reps": 30, "actual_reps": 28, "order": 0 },
{ "exercise": 7, "target_reps": 45, "actual_reps": 45, "order": 1 }
]
}
```
---
## Form Mode (Existing)
Unchanged behavior. Shows when `inputMode === "form"`:
- Template select (Sheet)
- Wrestler select (Sheet, multi)
- Minutes + seconds inputs
- Rating select
- Notes textarea
- Submit creates single result
---
## File Structure
```
frontend/src/app/(dashboard)/leistungstest/page.tsx
- Add inputMode state
- Add TimerMode component (inline or separate)
- TimerMode: TimerSession, TimerWrestler, TimerExercise types
- localStorage persistence with useEffect
New sub-components within page.tsx:
- TimerMode (full-width layout replacing the form)
- WrestlerListPanel (left side)
- TimerPanel (right side: timer + exercises)
```
---
## Session Persistence
```typescript
const SESSION_KEY = "leistungstest_timer_session"
useEffect(() => {
if (inputMode === "timer" && session) {
localStorage.setItem(SESSION_KEY, JSON.stringify(session))
}
}, [session, inputMode])
useEffect(() => {
if (inputMode === "timer") {
const saved = localStorage.getItem(SESSION_KEY)
if (saved) {
const parsed = JSON.parse(saved)
if (parsed.isRunning === false) {
// show restore prompt
}
}
}
}, [inputMode])
```
---
## Implementation Priority
1. Timer state + basic timer display (MM:SS ticking)
2. Wrestler list panel with status
3. Exercise list with reps input + start/done
4. Per-wrestler save on "Weiter"
5. localStorage persistence
6. Pause/Resume
7. Training beenden + summary
8. Form mode toggle