From d01eae078ab785f639b13fe554ce2637717ff502 Mon Sep 17 00:00:00 2001 From: Daniel Michelberger Date: Mon, 7 Sep 2026 23:07:48 +0200 Subject: [PATCH] feat: Wer gedrueckt hat, und Strafzeiten je Zeit oder je Stage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit startedBy und stoppedBy standen laengst im Datensatz und wurden bei jedem Start und Stopp geschrieben — nur sah sie niemand. Bei einer strittigen Messung ist das die erste Frage, also steht es jetzt unter dem Namen an der Zeit. Und die Korrektur: times.correction gab es, sie wird auf die Dauer gerechnet und im CSV exportiert, aber setzen konnte sie kein Mensch. Jetzt zwei Wege dorthin — an der einzelnen Zeit ueber die Spalte, die schon da war, und ueber alle gewerteten Zeiten der Stage auf einmal. Der Wert wird gesetzt und nicht addiert. Zweimal denselben Dialog abzuschicken darf keine doppelte Strafe ergeben. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr --- .../events/[id]/stages/[stageId]/+page.svelte | 159 +++++++++++++++++- 1 file changed, 152 insertions(+), 7 deletions(-) diff --git a/frontend/src/routes/dashboard/events/[id]/stages/[stageId]/+page.svelte b/frontend/src/routes/dashboard/events/[id]/stages/[stageId]/+page.svelte index 4a51a8e..8443f0d 100644 --- a/frontend/src/routes/dashboard/events/[id]/stages/[stageId]/+page.svelte +++ b/frontend/src/routes/dashboard/events/[id]/stages/[stageId]/+page.svelte @@ -12,6 +12,7 @@ import { riderName, getRiderContext } from '$lib/stores/riders.svelte' import { getTimeContext } from '$lib/stores/times.svelte' import { getEventParticipantContext } from '$lib/stores/eventParticipants.svelte' + import { getTeamContext } from '$lib/stores/teams.svelte' import { ticker } from '$lib/stores/ticker.svelte' import { TIME_STATUS_LABEL } from '$lib/time' import { clockSync } from '$lib/stores/clockSync.svelte' @@ -21,13 +22,25 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import * as Table from '@/components/ui/table' - import { ArrowLeft, Clock, Download, Flag, Timer, Trophy, TriangleAlert } from 'lucide-svelte' + import { ArrowLeft, Clock, Download, Flag, Pencil, Timer, Trophy, TriangleAlert } from 'lucide-svelte' + import * as Dialog from '@/components/ui/dialog' + import { Input } from '@/components/ui/input' + import { Label } from '@/components/ui/label' const events = getEventContext() const stages = getStageContext() const riders = getRiderContext() const times = getTimeContext() const participants = getEventParticipantContext() + const teams = getTeamContext() + + // Die Namen der Teammitglieder holen: Ein Team kennt nur die IDs seiner + // Leute, und ohne diesen Zug stuende an jeder Zeit statt "Start: Anna" + // eine abgeschnittene ID. + $effect(() => { + void teams.active + teams.loadMembers() + }) const clock = ticker() @@ -43,6 +56,8 @@ const running = $derived(valid && stage ? times.runningByStage(stage.id, clock.now) : []) const leaderboard = $derived(valid && stage ? times.getLeaderboard(stage.id) : []) const unranked = $derived(valid && stage ? times.unrankedByStage(stage.id) : []) + /** Alle gewerteten Zeiten dieser Stage — Ziel einer Strafe für alle. */ + const stageTimes = $derived(leaderboard) /** * Laufende und gewertete Zeiten in einer Liste: Wer gerade unterwegs ist, @@ -68,6 +83,54 @@ let timeDialog = $state(false) let clockDialog = $state(false) + // --- Strafzeiten ------------------------------------------------------ + // + // Die Korrektur steckt als Sekunden an der Zeit (times.correction) und + // wird auf die Dauer gerechnet. Zwei Wege dorthin: an einer Zeit einzeln, + // das ist der Normalfall, und über alle Zeiten dieser Stage auf einmal — + // etwa wenn eine Schikane für alle nicht zählte. + let penaltyDialog = $state(false) + let penaltyTime = $state(null) + let penaltySeconds = $state(0) + let penaltyBusy = $state(false) + let penaltyError = $state(null) + + function openPenalty(timeId: string | null, current = 0) { + penaltyTime = timeId + penaltySeconds = current + penaltyError = null + penaltyDialog = true + } + + async function savePenalty() { + if (penaltyBusy) return + + penaltyBusy = true + penaltyError = null + try { + if (penaltyTime) { + await times.applyCorrection(penaltyTime, penaltySeconds) + } else { + // Auf die ganze Stage: Die Korrektur wird gesetzt, nicht + // addiert. Zweimal denselben Dialog abzuschicken darf keine + // doppelte Strafe ergeben. + for (const t of stageTimes) { + await times.applyCorrection(t.id, penaltySeconds) + } + } + penaltyDialog = false + } catch (e: any) { + penaltyError = e.message ?? 'Die Korrektur konnte nicht gespeichert werden.' + } finally { + penaltyBusy = false + } + } + + /** Wer hat gedrückt? Der Kontoname, sonst eine gekürzte ID. */ + function userLabel(id: string | undefined) { + return id ? teams.userLabel(id) : null + } + // --- Export ------------------------------------------------------------ function exportCsv() { if (leaderboard.length === 0) return @@ -174,10 +237,18 @@ - - - Zeiten ({leaderboard.length} gewertet{running.length ? `, ${running.length} laufend` : ''}) - +
+ + + Zeiten ({leaderboard.length} gewertet{running.length ? `, ${running.length} laufend` : ''}) + + {#if leaderboard.length > 0} + + {/if} +
{#if rows.length === 0} @@ -221,12 +292,44 @@ {:else} — {/if} + + + {#if userLabel(t.startedBy) || userLabel(t.stoppedBy)} + + {#if t.startedBy && t.stoppedBy && t.startedBy === t.stoppedBy} + Start und Stopp: {userLabel(t.startedBy)} + {:else} + {#if t.startedBy}Start: {userLabel(t.startedBy)}{/if} + {#if t.startedBy && t.stoppedBy} · {/if} + {#if t.stoppedBy}Stopp: {userLabel(t.stoppedBy)}{/if} + {/if} + + {/if} {t.formattedTime} - - {t.correction ? `${t.correction > 0 ? '+' : ''}${t.correction.toFixed(1)}s` : '–'} + + {/each} @@ -273,3 +376,45 @@ + + + + + + + {penaltyTime ? 'Zeitkorrektur' : 'Strafzeit für alle'} + + + {#if penaltyTime} + Wird auf die gemessene Zeit gerechnet. Eine Strafe ist positiv, eine + Gutschrift negativ. + {:else} + Gilt für alle {stageTimes.length} gewerteten Zeiten dieser Stage. Der + Wert wird gesetzt, nicht addiert — zweimal abschicken ergibt keine + doppelte Strafe. + {/if} + + + +
+
+ + +

+ 0 nimmt eine bestehende Korrektur wieder zurück. +

+
+ + {#if penaltyError} +

{penaltyError}

+ {/if} +
+ + + + + +
+