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
8.3 KiB
8.3 KiB
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:
- Form Mode (default, existing behavior): Template + Wrestler selection → manual time/reps entry → save
- Timer Mode (new): Template + Wrestler selection → live timer interface → results saved per wrestler automatically
Mode Toggle
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
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)
// 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
- User selects template (Sheet) + wrestlers (Sheet)
- Clicks "Training starten"
- Timer mode activates, first wrestler is set to "active", timer starts
Per-Exercise Flow
- Trainer sees current exercise (from template)
- Enters actual reps in input field
- Clicks "Start" → exercise timer starts
- Wrestler completes exercise
- Trainer clicks "Done" → exercise marked complete, elapsed time recorded
- Next exercise auto-advances to active state
Per-Wrestler Flow
- All exercises done for current wrestler
- Trainer clicks "Weiter zum nächsten Ringer"
- Result is saved immediately via API:
- POST
/leistungstest/results/with all exercise items total_time_seconds= elapsed time for this wrestler
- POST
- Next wrestler becomes active, timer continues (does NOT reset)
- Repeat until all wrestlers done
Finishing
- Trainer clicks "Training beenden"
- If current wrestler has started but not all exercises done → prompt: "Nicht alle Übungen gemacht. Trotzdem beenden?"
- Confirm → save current wrestler's partial result
- 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 wrestlerGET /leistungstest/results/?template=X— list results
Create Result Payload
{
"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
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
- Timer state + basic timer display (MM:SS ticking)
- Wrestler list panel with status
- Exercise list with reps input + start/done
- Per-wrestler save on "Weiter"
- localStorage persistence
- Pause/Resume
- Training beenden + summary
- Form mode toggle