feat(layouts): switch track layout mid-practice and view stats per layout

A club can run one Open Practice all season and re-lay the track now and then.
Previously a session's layout was frozen at start and every training lap inherited
it, so a season-long practice stayed on its first layout forever, and changing layout
was hidden under Settings -> Appearance.

- Each practice lap now counts toward the layout active when it was driven; a running
  practice session moves onto a new layout when one is added
- "New layout" strip on Training stats, Race Day and the Dashboard, offered only around
  practice; a running race session blocks layout changes everywhere
- "Stats for" selector (current / older layout / before layouts / all layouts) on
  Training stats and in the driver's mobile profile (?layout= on the public API)
- A layout that has practice laps on it can no longer be deleted
- Fix: the buildDriverTrainingStats wrapper in app.js dropped extra arguments

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
larssand
2026-09-27 21:24:44 +02:00
parent 196437d25b
commit 627179b95a
22 changed files with 454 additions and 44 deletions

View File

@@ -579,7 +579,7 @@ app.get("/public-training/api/:shareToken", (req, res) => {
// neither independently of the general practice timer above, each started at its own time.
const evenLapsSince = publicTrainingProfile.normalizePublicTrainingSince(req.query.evenLapsSince, req.query.evenLapsStartedAgoMs);
const stepTrainingSince = publicTrainingProfile.normalizePublicTrainingSince(req.query.stepTrainingSince, req.query.stepTrainingStartedAgoMs);
const profile = stored ? publicTrainingProfile.buildPublicTrainingProfile(stored.state, String(req.params.shareToken || ""), stored.updatedAt, sinceTimestamp, windowMs, evenLapsSince, stepTrainingSince) : null;
const profile = stored ? publicTrainingProfile.buildPublicTrainingProfile(stored.state, String(req.params.shareToken || ""), stored.updatedAt, sinceTimestamp, windowMs, evenLapsSince, stepTrainingSince, String(req.query.layout || "current")) : null;
if (!profile) {
res.status(404).json({ error: "Profile not found" });
return;