# 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