stammtisch-hersbruck/frontend/src/routes/dashboard/+page.svelte
Daniel Michelberger 893509be9d refactor: Aus Runs werden Stages, bis in die Datenbank
"Run" hiess in dieser App immer schon der abgesteckte Abschnitt eines
Events, auf dem gefahren und gestoppt wird — also das, was im Rennsport
Stage heisst. "Run" ist daneben der einzelne Durchgang eines Fahrers,
und genau der steht hier als `times`. Zwei Bedeutungen fuer ein Wort, an
einer Stelle, an der beide vorkommen.

Die Migration benennt um statt neu anzulegen: Collection und Feld
behalten ihre IDs, PocketBase benennt Tabelle und Spalte um, die Daten
bleiben stehen. Gesucht wird ueber die Collection-ID und nicht ueber den
Namen, damit sie auch auf einer frischen Datenbank durchlaeuft, deren
Snapshot die Collection bereits `stages` nennt.

Im Frontend wandert `times.run` zu `times.stage`, der Store heisst
stages.svelte.ts, und aus /events/[id]/runs/[runId] wird
/events/[id]/stages/[stageId].

Nicht umbenannt: `running`, `allRunning` und RunningTimesToast. Das sind
laufende Zeiten und keine Stages — dieselben Buchstaben, andere Sache.

Und die Artikel: Der Run war maskulin, die Stage ist feminin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
2026-09-07 22:16:22 +02:00

128 lines
5.8 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts">
import { auth } from '$lib/stores/pocketbase.svelte'
import { getTeamContext } from '$lib/stores/teams.svelte'
import { getEventContext } from '$lib/stores/events.svelte'
import { getRiderContext } from '$lib/stores/riders.svelte'
import { getTimeContext } from '$lib/stores/times.svelte'
import { getStageContext } from '$lib/stores/stages.svelte'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { formatDateRange } from '$lib/time'
import { Calendar, Users, Clock, MapPin } from 'lucide-svelte'
const teams = getTeamContext()
const events = getEventContext()
const stages = getStageContext()
const riders = getRiderContext()
const times = getTimeContext()
// „Aktiv" heißt jetzt: findet heute statt. Für die Startseite ist das
// zusammen mit dem Anstehenden interessant.
const upNext = $derived([...events.active, ...events.upcoming].slice(0, 4))
const stats = $derived({
totalEvents: events.scoped.length,
activeEvents: events.active.length,
totalRiders: riders.scoped.length,
running: times.running.length,
finished: times.finished.length,
})
</script>
<svelte:head>
<title>Dashboard Zeitnahme</title>
</svelte:head>
<div class="space-y-8">
<div>
<h1 class="text-3xl font-bold tracking-tight">Willkommen, {auth.user?.name || auth.user?.email}!</h1>
{#if teams.active}
<p class="mt-2 text-muted-foreground">
Team <span class="font-medium text-foreground">{teams.active.name}</span> · {stats.totalEvents} Events, {stats.totalRiders} Fahrer
</p>
{:else}
<p class="mt-2 text-muted-foreground">Kein Team aktiv. <a href="/dashboard/team" class="underline">Team verwalten</a>.</p>
{/if}
</div>
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<Card>
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-medium text-muted-foreground">Events</CardTitle>
<Calendar class="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div class="text-3xl font-bold">{stats.totalEvents}</div>
<p class="text-xs text-muted-foreground mt-1">{stats.activeEvents} heute</p>
</CardContent>
</Card>
<Card>
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-medium text-muted-foreground">Fahrer</CardTitle>
<Users class="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div class="text-3xl font-bold">{stats.totalRiders}</div>
<p class="text-xs text-muted-foreground mt-1">registriert</p>
</CardContent>
</Card>
<Card>
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-medium text-muted-foreground">Laufende Zeiten</CardTitle>
<Clock class="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div class="text-3xl font-bold">{stats.running}</div>
<p class="text-xs text-muted-foreground mt-1">{stats.finished} abgeschlossen</p>
</CardContent>
</Card>
</div>
<div>
<h2 class="text-xl font-semibold mb-4">Heute und demnächst</h2>
{#if upNext.length === 0}
<Card>
<CardContent class="py-12 text-center">
<Calendar class="h-10 w-10 text-muted-foreground mx-auto mb-3" />
<p class="font-medium">Nichts geplant</p>
<p class="text-sm text-muted-foreground mt-1">
Trag einem Event ein Datum ein, dann steht es hier.
</p>
<Button href="/dashboard/events" variant="outline" class="mt-4">Events ansehen</Button>
</CardContent>
</Card>
{:else}
<div class="grid gap-3 sm:grid-cols-2">
{#each upNext as event}
{@const eventStages = stages.getByEvent(event.id).length}
<Card class="hover:border-primary transition-colors">
<a href="/dashboard/events/{event.id}" class="block">
<CardHeader>
<div class="flex items-start justify-between gap-2">
<CardTitle>{event.name || event.description || 'Unbenannt'}</CardTitle>
{#if event.starts}
<Badge variant="secondary" class="shrink-0">
{formatDateRange(event.starts, event.ends)}
</Badge>
{/if}
</div>
{#if event.location}
<CardDescription class="flex items-center gap-1">
<MapPin class="h-3 w-3" />
{event.location}
</CardDescription>
{/if}
</CardHeader>
<CardContent>
<p class="text-sm text-muted-foreground">{eventStages} Stage{eventStages === 1 ? '' : 's'}</p>
</CardContent>
</a>
</Card>
{/each}
</div>
{/if}
</div>
</div>