feat: Grund zur Zeitkorrektur, "Strafzeit fuer alle" entfaellt
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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
This commit is contained in:
parent
5c4d2fec4d
commit
b80b1198fc
2 changed files with 42 additions and 39 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<string | null>(null)
|
||||
let penaltySeconds = $state(0)
|
||||
let penaltyReason = $state('')
|
||||
let penaltyBusy = $state(false)
|
||||
let penaltyError = $state<string | null>(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 @@
|
|||
<Trophy class="h-4 w-4 text-primary" />
|
||||
Zeiten ({leaderboard.length} gewertet{running.length ? `, ${running.length} laufend` : ''})
|
||||
</CardTitle>
|
||||
{#if leaderboard.length > 0}
|
||||
<Button variant="outline" size="sm" onclick={() => openPenalty(null)}>
|
||||
<Pencil class="size-4 mr-2" />
|
||||
Strafzeit für alle
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="p-0">
|
||||
|
|
@ -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 @@
|
|||
<Dialog.Root bind:open={penaltyDialog}>
|
||||
<Dialog.Content>
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>
|
||||
{penaltyTime ? 'Zeitkorrektur' : 'Strafzeit für alle'}
|
||||
</Dialog.Title>
|
||||
<Dialog.Title>Zeitkorrektur</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
{#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.
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
|
||||
|
|
@ -470,6 +450,19 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="penalty-reason">Grund</Label>
|
||||
<Input
|
||||
id="penalty-reason"
|
||||
bind:value={penaltyReason}
|
||||
placeholder="z. B. Tor ausgelassen"
|
||||
/>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
Optional, aber nach dem Event ist eine Strafe ohne Grund schwer zu
|
||||
verteidigen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if penaltyError}
|
||||
<p class="text-sm text-destructive">{penaltyError}</p>
|
||||
{/if}
|
||||
|
|
|
|||
Loading…
Reference in a new issue