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. +

+
+ {#if penaltyError}

{penaltyError}

{/if}