diff --git a/frontend/src/routes/dashboard/events/[id]/+page.svelte b/frontend/src/routes/dashboard/events/[id]/+page.svelte index ea61c72..716bff2 100644 --- a/frontend/src/routes/dashboard/events/[id]/+page.svelte +++ b/frontend/src/routes/dashboard/events/[id]/+page.svelte @@ -13,7 +13,6 @@ } from '$lib/stores/eventParticipants.svelte' import { auth } from '$lib/stores/pocketbase.svelte' import TimeDialog from '$lib/components/TimeDialog.svelte' - import * as Select from '@/components/ui/select' import { formatDateRange } from '$lib/time' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' @@ -34,6 +33,7 @@ Users, UserPlus, Check, + Search, X, } from 'lucide-svelte' import type { EventParticipantsResponse, StagesResponse } from '$lib/types' @@ -81,48 +81,110 @@ // --- Teilnehmer ------------------------------------------------------- let riderDialog = $state(false) let editing = $state(null) - let form = $state({ rider: '', number: '' }) + let form = $state({ number: '' }) let formError = $state(null) const available = $derived(event ? participants.available(event.id) : []) + // --- Mehrere auf einmal eintragen ------------------------------------- + // + // Ein Event beginnt mit zwanzig Anmeldungen, nicht mit einer. Zwanzigmal + // denselben Dialog zu oeffnen, einen Namen aus einer Liste zu suchen und + // eine Nummer zu tippen, ist dieselbe Arbeit zwanzigmal — hier steht der + // Kader stattdessen als Liste da, mit der Nummer gleich daneben. + let addDialog = $state(false) + let addQuery = $state('') + let addBusy = $state(false) + let addError = $state(null) + + /** Auswahl und Startnummer je Fahrer, nach Fahrer-ID. */ + let picks = $state>({}) + + const addFiltered = $derived.by(() => { + const q = addQuery.toLowerCase().trim() + return q ? available.filter((r) => riderName(r).toLowerCase().includes(q)) : available + }) + + const picked = $derived(available.filter((r) => picks[r.id]?.chosen)) + function openAdd() { - editing = null - form = { rider: available[0]?.id ?? '', number: '' } - formError = null - riderDialog = true + picks = Object.fromEntries(available.map((r) => [r.id, { chosen: false, number: '' }])) + addQuery = '' + addError = null + addDialog = true + } + + function toggleAll(on: boolean) { + const next = { ...picks } + for (const r of addFiltered) { + next[r.id] = { ...(next[r.id] ?? { number: '' }), chosen: on } + } + picks = next + } + + async function saveParticipants() { + if (!event || addBusy) return + + const chosen = picked + if (chosen.length === 0) { + addError = 'Niemand ausgewählt.' + return + } + + // Erst pruefen, dann schreiben: Eine doppelte Nummer soll nicht erst + // auffallen, wenn die halbe Liste schon eingetragen ist. + const seen = new Map() + for (const r of chosen) { + const number = (picks[r.id]?.number ?? '').trim() + if (!number) continue + + if (seen.has(number)) { + addError = `Startnummer ${number} ist doppelt vergeben (${seen.get(number)} und ${riderName(r)}).` + return + } + if (participants.isNumberTaken(event.id, number)) { + addError = `Startnummer ${number} ist in diesem Event bereits vergeben.` + return + } + seen.set(number, riderName(r)) + } + + addBusy = true + addError = null + try { + for (const r of chosen) { + await participants.add({ + event: event.id, + rider: r.id, + number: (picks[r.id]?.number ?? '').trim(), + }) + } + addDialog = false + } catch (e: any) { + addError = e.message ?? 'Nicht alle Teilnehmer konnten eingetragen werden.' + } finally { + addBusy = false + } } function openEdit(p: EventParticipantsResponse) { editing = p - form = { rider: p.rider, number: p.number ?? '' } + form = { number: p.number ?? '' } formError = null riderDialog = true } async function saveParticipant() { - if (!event) return + if (!event || !editing) return - if (!form.rider) { - formError = 'Bitte einen Fahrer wählen.' - return - } - if (participants.isNumberTaken(event.id, form.number, editing?.id)) { + if (participants.isNumberTaken(event.id, form.number, editing.id)) { formError = `Startnummer ${form.number} ist in diesem Event bereits vergeben.` return } formError = null try { - if (editing) { - await participants.edit(editing.id, { number: form.number.trim() }) - } else { - await participants.add({ - event: event.id, - rider: form.rider, - number: form.number.trim(), - }) - } + await participants.edit(editing.id, { number: form.number.trim() }) riderDialog = false } catch (e: any) { formError = e.message ?? 'Der Teilnehmer konnte nicht gespeichert werden.' @@ -489,36 +551,13 @@ - {editing ? 'Startnummer ändern' : 'Teilnehmer hinzufügen'} + Startnummer ändern - {#if editing} - Die Startnummer gilt nur für „{event?.name}". - {:else} - Aus dem Kader des Teams. Neue Fahrer legt die Teamleitung in den - Einstellungen an. - {/if} + Die Startnummer gilt nur für „{event?.name}".
- {#if !editing} -
- - - - {form.rider - ? riderName(riders.getById(form.rider) ?? ({} as any)) || 'Fahrer wählen…' - : 'Fahrer wählen…'} - - - {#each available as r (r.id)} - {riderName(r) || '— ohne Name —'} - {/each} - - -
- {/if} -
@@ -534,7 +573,100 @@ - + + + + + + + + + + Teilnehmer eintragen + + Aus dem Kader des Teams. Die Startnummer gilt je Event und kann leer + bleiben. + + + +
+ {#if available.length === 0} +

+ Der ganze Kader ist schon eingetragen. +

+ {:else} +
+
+ + +
+ + +
+ +
+ {#each addFiltered as r (r.id)} +
+ + (picks = { + ...picks, + [r.id]: { + number: picks[r.id]?.number ?? '', + chosen: e.currentTarget.checked, + }, + })} + /> + + + (picks = { + ...picks, + [r.id]: { + chosen: picks[r.id]?.chosen ?? false, + number: e.currentTarget.value, + }, + })} + /> +
+ {:else} +

+ Kein Treffer. +

+ {/each} +
+ {/if} + + {#if addError} +

{addError}

+ {/if} +
+ + + +