From b80b1198fc3539d5e780328f330539295cfd7e2b Mon Sep 17 00:00:00 2001
From: Daniel Michelberger
Date: Mon, 7 Sep 2026 23:30:24 +0200
Subject: [PATCH] feat: Grund zur Zeitkorrektur, "Strafzeit fuer alle"
entfaellt
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Das Schema traegt es bereits: times.comment gibt es seit dem ersten
Snapshot und wurde von der Anwendung nie benutzt. Der Grund steht jetzt
dort — optional, aber nach dem Event ist eine Strafe ohne Grund schwer
zu verteidigen. Er haengt im Tooltip an der Korrektur und steht als
eigene Spalte im CSV.
"Strafzeit fuer alle" ist raus. Eine Strafe trifft einen Fahrer; was
fuer alle gleich gilt, ist keine Strafe, sondern eine andere Stage.
Co-Authored-By: Claude Opus 5 (1M context)
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
---
frontend/src/lib/stores/times.svelte.ts | 14 +++-
.../events/[id]/stages/[stageId]/+page.svelte | 67 +++++++++----------
2 files changed, 42 insertions(+), 39 deletions(-)
diff --git a/frontend/src/lib/stores/times.svelte.ts b/frontend/src/lib/stores/times.svelte.ts
index dd340ae..6c445c4 100644
--- a/frontend/src/lib/stores/times.svelte.ts
+++ b/frontend/src/lib/stores/times.svelte.ts
@@ -228,8 +228,18 @@ export class TimeStore {
this.records = next
}
- async applyCorrection(timeId: string, seconds: number) {
- return await api.collection('times').update(timeId, { correction: seconds })
+ /**
+ * Zeitkorrektur setzen, mit optionalem Grund.
+ *
+ * Der Grund steht im vorhandenen Feld `comment`. Eine Strafe ohne Grund
+ * ist nach dem Event nicht mehr zu verteidigen — Pflicht ist er trotzdem
+ * nicht: An der Strecke zaehlt, dass die Zahl stimmt.
+ */
+ async applyCorrection(timeId: string, seconds: number, comment?: string) {
+ return await api.collection('times').update(timeId, {
+ correction: seconds,
+ comment: comment?.trim() ?? '',
+ })
}
async updateStatus(timeId: string, status: TimeStatus) {
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 0c2a57a..f6e03e6 100644
--- a/frontend/src/routes/dashboard/events/[id]/stages/[stageId]/+page.svelte
+++ b/frontend/src/routes/dashboard/events/[id]/stages/[stageId]/+page.svelte
@@ -56,8 +56,6 @@
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,
@@ -97,38 +95,31 @@
// --- 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.
+ // wird auf die Dauer gerechnet; der Grund daneben im Feld `comment`.
+ // Immer an einer einzelnen Zeit — eine Strafe trifft einen Fahrer, und
+ // was fuer alle gleich gilt, ist keine Strafe, sondern eine andere Stage.
let penaltyDialog = $state(false)
let penaltyTime = $state(null)
let penaltySeconds = $state(0)
+ let penaltyReason = $state('')
let penaltyBusy = $state(false)
let penaltyError = $state(null)
- function openPenalty(timeId: string | null, current = 0) {
+ function openPenalty(timeId: string, current = 0, reason = '') {
penaltyTime = timeId
penaltySeconds = current
+ penaltyReason = reason
penaltyError = null
penaltyDialog = true
}
async function savePenalty() {
- if (penaltyBusy) return
+ if (penaltyBusy || !penaltyTime) 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)
- }
- }
+ await times.applyCorrection(penaltyTime, penaltySeconds, penaltyReason)
penaltyDialog = false
} catch (e: any) {
penaltyError = e.message ?? 'Die Korrektur konnte nicht gespeichert werden.'
@@ -146,7 +137,7 @@
function exportCsv() {
if (leaderboard.length === 0) return
- const rows = [['Platz', 'Nummer', 'Name', 'Zeit', 'Korrektur (s)']]
+ const rows = [['Platz', 'Nummer', 'Name', 'Zeit', 'Korrektur (s)', 'Grund']]
leaderboard.forEach((t, i) => {
const r = rider(t.rider)
rows.push([
@@ -155,6 +146,7 @@
r ? riderName(r) : '',
t.formattedTime ?? '',
String(t.correction ?? 0),
+ t.comment ?? '',
])
})
@@ -253,12 +245,6 @@
Zeiten ({leaderboard.length} gewertet{running.length ? `, ${running.length} laufend` : ''})
- {#if leaderboard.length > 0}
-
- {/if}
@@ -379,10 +365,12 @@
class="tabular-nums font-normal {t.correction
? ''
: 'text-muted-foreground'}"
- tooltip="Zeitkorrektur"
+ tooltip={t.comment
+ ? `Zeitkorrektur: ${t.comment}`
+ : 'Zeitkorrektur'}
onclick={(e) => {
e.stopPropagation()
- openPenalty(t.id, t.correction ?? 0)
+ openPenalty(t.id, t.correction ?? 0, t.comment ?? '')
}}
>
{t.correction
@@ -446,18 +434,10 @@
-
- {penaltyTime ? 'Zeitkorrektur' : 'Strafzeit für alle'}
-
+ Zeitkorrektur
- {#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}
+ Wird auf die gemessene Zeit gerechnet. Eine Strafe ist positiv, eine
+ Gutschrift negativ.
@@ -470,6 +450,19 @@
+
+
+
+
+ Optional, aber nach dem Event ist eine Strafe ohne Grund schwer zu
+ verteidigen.
+