feat: Ein Fahrer hat einen Namen, nicht zwei
Vorname und Nachname getrennt zu fuehren versprach eine Ordnung, die es hier nie gab: sortiert wird ueber den ganzen Namen, angezeigt wird der ganze Name, gesucht wird ueber beides zugleich. Dafuer musste jede Maske zwei Felder anbieten und jede Anzeige sie wieder zusammensetzen — und wer schlicht "Schorsch" heisst, stand vor der Frage, welches der beiden Felder das ist. Das Feld name gibt es an riders laengst; es lag nur brach, weil die Anwendung firstname und lastname benutzte. Es uebernimmt deren Inhalt — kein neues Feld, keine zweite Spalte, nichts, was auf bestehenden Instanzen erst entstehen muesste. Zurueck geht es nur ungenau: Die Umkehrung trennt am ersten Leerzeichen und raet damit bei jedem Doppelvornamen falsch. Das ist der Preis dafuer, dass die Trennung ueberhaupt verschwindet. fullName heisst jetzt riderName und ist nur noch ein Feldzugriff — die Funktion bleibt als die eine Stelle, an der ein namenloser Fahrer zu einem leeren String wird statt als undefined durch die Oberflaeche zu geistern. 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
eac2455ee3
commit
8c11095b17
9 changed files with 177 additions and 65 deletions
|
|
@ -709,7 +709,7 @@ migrate((app) => {
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"pattern": "",
|
"pattern": "",
|
||||||
"presentable": false,
|
"presentable": true,
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"required": false,
|
"required": false,
|
||||||
"system": false,
|
"system": false,
|
||||||
|
|
@ -764,36 +764,6 @@ migrate((app) => {
|
||||||
"system": false,
|
"system": false,
|
||||||
"type": "autodate"
|
"type": "autodate"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"autogeneratePattern": "",
|
|
||||||
"help": "",
|
|
||||||
"hidden": false,
|
|
||||||
"id": "text2208304744",
|
|
||||||
"max": 0,
|
|
||||||
"min": 0,
|
|
||||||
"name": "firstname",
|
|
||||||
"pattern": "",
|
|
||||||
"presentable": true,
|
|
||||||
"primaryKey": false,
|
|
||||||
"required": false,
|
|
||||||
"system": false,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"autogeneratePattern": "",
|
|
||||||
"help": "",
|
|
||||||
"hidden": false,
|
|
||||||
"id": "text824489398",
|
|
||||||
"max": 0,
|
|
||||||
"min": 0,
|
|
||||||
"name": "lastname",
|
|
||||||
"pattern": "",
|
|
||||||
"presentable": true,
|
|
||||||
"primaryKey": false,
|
|
||||||
"required": false,
|
|
||||||
"system": false,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"cascadeDelete": false,
|
"cascadeDelete": false,
|
||||||
"collectionId": "pbc_1568971955",
|
"collectionId": "pbc_1568971955",
|
||||||
|
|
|
||||||
87
backend/pb_migrations/1754501800_riders_single_name.js
Normal file
87
backend/pb_migrations/1754501800_riders_single_name.js
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
|
||||||
|
// Ein Fahrer hat einen Namen, nicht zwei.
|
||||||
|
//
|
||||||
|
// Vorname und Nachname getrennt zu führen versprach eine Ordnung, die es hier
|
||||||
|
// nie gab: sortiert wird über den ganzen Namen, angezeigt wird der ganze Name,
|
||||||
|
// gesucht wird über beides zugleich. Dafür musste jede Maske zwei Felder
|
||||||
|
// anbieten und jede Anzeige sie wieder zusammensetzen — und wer schlicht
|
||||||
|
// „Schorsch" heißt, stand vor der Frage, welches der beiden Felder das ist.
|
||||||
|
//
|
||||||
|
// Das Feld `name` gibt es an riders längst; es lag nur brach, weil die
|
||||||
|
// Anwendung firstname und lastname benutzte. Es bleibt deshalb, wie es ist,
|
||||||
|
// und bekommt nur den Inhalt der beiden anderen — kein neues Feld, keine
|
||||||
|
// zweite Spalte, nichts, was auf bestehenden Instanzen erst entstehen müsste.
|
||||||
|
//
|
||||||
|
// Beim Zusammenlegen geht nichts verloren: firstname und lastname werden mit
|
||||||
|
// einem Leerzeichen verbunden. Zurück geht es nur ungenau — die Umkehrung
|
||||||
|
// trennt am ersten Leerzeichen und rät damit bei jedem Doppelvornamen falsch.
|
||||||
|
// Das ist der Preis dafür, dass die Trennung überhaupt verschwindet.
|
||||||
|
|
||||||
|
migrate((app) => {
|
||||||
|
const riders = app.findCollectionByNameOrId('riders')
|
||||||
|
|
||||||
|
// Erst zusammenführen, dann die alten Felder abräumen — andersherum wären
|
||||||
|
// die Daten fort, bevor sie jemand liest. Auf einer frischen Datenbank hat
|
||||||
|
// der Snapshot die beiden Felder gar nicht erst.
|
||||||
|
if (riders.fields.getByName('firstname') || riders.fields.getByName('lastname')) {
|
||||||
|
for (const rider of app.findRecordsByFilter('riders', 'id != ""', '', 0, 0)) {
|
||||||
|
const merged = [rider.get('firstname'), rider.get('lastname')]
|
||||||
|
.map((part) => (part || '').trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
|
|
||||||
|
// Ein bereits gefülltes `name` gewinnt: Es wäre von Hand gesetzt
|
||||||
|
// worden und wüsste mehr als die beiden Bruchstücke.
|
||||||
|
if (merged && !(rider.get('name') || '').trim()) {
|
||||||
|
rider.set('name', merged)
|
||||||
|
app.save(rider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
riders.fields.removeByName('firstname')
|
||||||
|
riders.fields.removeByName('lastname')
|
||||||
|
|
||||||
|
// Im Admin-UI steht ein Fahrer jetzt mit Namen statt mit seiner ID da.
|
||||||
|
const name = riders.fields.getByName('name')
|
||||||
|
if (name) name.presentable = true
|
||||||
|
|
||||||
|
app.save(riders)
|
||||||
|
}, (app) => {
|
||||||
|
const riders = app.findCollectionByNameOrId('riders')
|
||||||
|
|
||||||
|
if (!riders.fields.getByName('firstname')) {
|
||||||
|
riders.fields.add(new Field({
|
||||||
|
id: 'text2208304744',
|
||||||
|
name: 'firstname',
|
||||||
|
type: 'text',
|
||||||
|
presentable: true,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if (!riders.fields.getByName('lastname')) {
|
||||||
|
riders.fields.add(new Field({
|
||||||
|
id: 'text824489398',
|
||||||
|
name: 'lastname',
|
||||||
|
type: 'text',
|
||||||
|
presentable: true,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = riders.fields.getByName('name')
|
||||||
|
if (name) name.presentable = false
|
||||||
|
|
||||||
|
app.save(riders)
|
||||||
|
|
||||||
|
// Am ersten Leerzeichen trennen: davor der Vorname, danach der Rest. Aus
|
||||||
|
// „Anna Maria Huber" wird so „Anna" und „Maria Huber".
|
||||||
|
for (const rider of app.findRecordsByFilter('riders', 'id != ""', '', 0, 0)) {
|
||||||
|
const full = (rider.get('name') || '').trim()
|
||||||
|
if (!full) continue
|
||||||
|
|
||||||
|
const cut = full.indexOf(' ')
|
||||||
|
rider.set('firstname', cut === -1 ? full : full.slice(0, cut))
|
||||||
|
rider.set('lastname', cut === -1 ? '' : full.slice(cut + 1).trim())
|
||||||
|
app.save(rider)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
* Zeit-Dialog mit Fahrer und Run vorbelegt.
|
* Zeit-Dialog mit Fahrer und Run vorbelegt.
|
||||||
*/
|
*/
|
||||||
import { getTimeContext } from '$lib/stores/times.svelte'
|
import { getTimeContext } from '$lib/stores/times.svelte'
|
||||||
import { fullName, getRiderContext } from '$lib/stores/riders.svelte'
|
import { riderName, getRiderContext } from '$lib/stores/riders.svelte'
|
||||||
import { getRunContext } from '$lib/stores/runs.svelte'
|
import { getRunContext } from '$lib/stores/runs.svelte'
|
||||||
import { getEventParticipantContext } from '$lib/stores/eventParticipants.svelte'
|
import { getEventParticipantContext } from '$lib/stores/eventParticipants.svelte'
|
||||||
import { ticker } from '$lib/stores/ticker.svelte'
|
import { ticker } from '$lib/stores/ticker.svelte'
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
const eventId = runId ? runs.getById(runId)?.event : undefined
|
const eventId = runId ? runs.getById(runId)?.event : undefined
|
||||||
if (!eventId) {
|
if (!eventId) {
|
||||||
const r = riderId ? riders.getById(riderId) : undefined
|
const r = riderId ? riders.getById(riderId) : undefined
|
||||||
return r ? fullName(r) : 'Unbekannter Fahrer'
|
return r ? riderName(r) : 'Unbekannter Fahrer'
|
||||||
}
|
}
|
||||||
return participants.riderLabel(eventId, riderId)
|
return participants.riderLabel(eventId, riderId)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
* Tabellen noch die Toast-Leiste lösen sie direkt aus.
|
* Tabellen noch die Toast-Leiste lösen sie direkt aus.
|
||||||
*/
|
*/
|
||||||
import { getTimeContext, type TimeStatus } from '$lib/stores/times.svelte'
|
import { getTimeContext, type TimeStatus } from '$lib/stores/times.svelte'
|
||||||
import { fullName, getRiderContext } from '$lib/stores/riders.svelte'
|
import { riderName, getRiderContext } from '$lib/stores/riders.svelte'
|
||||||
import { getRunContext } from '$lib/stores/runs.svelte'
|
import { getRunContext } from '$lib/stores/runs.svelte'
|
||||||
import { getEventParticipantContext } from '$lib/stores/eventParticipants.svelte'
|
import { getEventParticipantContext } from '$lib/stores/eventParticipants.svelte'
|
||||||
import { ticker } from '$lib/stores/ticker.svelte'
|
import { ticker } from '$lib/stores/ticker.svelte'
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { getContext, setContext } from 'svelte'
|
import { getContext, setContext } from 'svelte'
|
||||||
import { api, auth } from './pocketbase.svelte'
|
import { api, auth } from './pocketbase.svelte'
|
||||||
import { getEventContext } from './events.svelte'
|
import { getEventContext } from './events.svelte'
|
||||||
import { getRiderContext } from './riders.svelte'
|
import { getRiderContext, riderName } from './riders.svelte'
|
||||||
import type { EventParticipantsResponse, RidersResponse } from '$lib/types'
|
import type { EventParticipantsResponse, RidersResponse } from '$lib/types'
|
||||||
|
|
||||||
const KEY = Symbol('eventParticipant')
|
const KEY = Symbol('eventParticipant')
|
||||||
|
|
@ -45,7 +45,7 @@ export class EventParticipantStore {
|
||||||
if (Number.isFinite(na) && Number.isFinite(nb)) return na - nb
|
if (Number.isFinite(na) && Number.isFinite(nb)) return na - nb
|
||||||
if (Number.isFinite(na)) return -1
|
if (Number.isFinite(na)) return -1
|
||||||
if (Number.isFinite(nb)) return 1
|
if (Number.isFinite(nb)) return 1
|
||||||
return this.riderName(a.rider).localeCompare(this.riderName(b.rider))
|
return this.nameOf(a.rider).localeCompare(this.nameOf(b.rider))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,16 +94,15 @@ export class EventParticipantStore {
|
||||||
riderLabel(eventId: string, riderId: string | undefined): string {
|
riderLabel(eventId: string, riderId: string | undefined): string {
|
||||||
if (!riderId) return 'Unbekannter Fahrer'
|
if (!riderId) return 'Unbekannter Fahrer'
|
||||||
|
|
||||||
const name = this.riderName(riderId) || 'Unbekannter Fahrer'
|
const name = this.nameOf(riderId) || 'Unbekannter Fahrer'
|
||||||
const number = this.numberOf(eventId, riderId)
|
const number = this.numberOf(eventId, riderId)
|
||||||
|
|
||||||
return number ? `#${number} ${name}` : name
|
return number ? `#${number} ${name}` : name
|
||||||
}
|
}
|
||||||
|
|
||||||
private riderName(riderId: string): string {
|
private nameOf(riderId: string): string {
|
||||||
const r = this.riders.getById(riderId)
|
const r = this.riders.getById(riderId)
|
||||||
if (!r) return ''
|
return r ? riderName(r) : ''
|
||||||
return [r.firstname, r.lastname].filter(Boolean).join(' ') || r.name || ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,20 @@ const KEY = Symbol('rider')
|
||||||
* Die gelten seit 1754501300 je Event und stehen am Teilnehmer.
|
* Die gelten seit 1754501300 je Event und stehen am Teilnehmer.
|
||||||
*/
|
*/
|
||||||
function sortByName(records: RidersResponse[]) {
|
function sortByName(records: RidersResponse[]) {
|
||||||
return [...records].sort((a, b) => fullName(a).localeCompare(fullName(b)))
|
return [...records].sort((a, b) => riderName(a).localeCompare(riderName(b)))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fullName(r: RidersResponse) {
|
/**
|
||||||
const parts = [r.firstname, r.lastname].filter(Boolean)
|
* Der Name eines Fahrers — ein Feld, ein Wert.
|
||||||
return parts.join(' ').trim() || r.name || ''
|
*
|
||||||
|
* Bis 1754501800_riders_single_name.js standen hier Vorname und Nachname
|
||||||
|
* getrennt und mussten überall wieder zusammengesetzt werden. Die Funktion
|
||||||
|
* bleibt trotzdem: Sie ist die eine Stelle, an der ein namenloser Fahrer zu
|
||||||
|
* einem leeren String wird, statt als `undefined` durch die Oberfläche zu
|
||||||
|
* geistern.
|
||||||
|
*/
|
||||||
|
export function riderName(r: RidersResponse) {
|
||||||
|
return (r.name ?? '').trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RiderStore {
|
export class RiderStore {
|
||||||
|
|
@ -84,13 +92,47 @@ export class RiderStore {
|
||||||
return await api.collection('riders').update(id, data)
|
return await api.collection('riders').update(id, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fahrer löschen — und mit ihm die Mitgliedschaft seines Kontos.
|
||||||
|
*
|
||||||
|
* Ohne den zweiten Schritt riss jedes Löschen ein Loch: Der Fahrer war
|
||||||
|
* fort, sein Konto blieb in `teams.users` und stand danach als Zeile ohne
|
||||||
|
* Fahrer im Kader. Aus so einer Zeile führte kein Weg zurück — ein Konto
|
||||||
|
* nachträglich an einen Fahrer zu hängen gibt es bewusst nicht, weil
|
||||||
|
* dieselbe Person sonst zweimal in der Liste stehen könnte. Der Eintrag
|
||||||
|
* war damit unreparierbar.
|
||||||
|
*
|
||||||
|
* Das Konto selbst bleibt bestehen: Löschen darf es allein sein Inhaber
|
||||||
|
* (`users.deleteRule = id = @request.auth.id`), und über einen neuen
|
||||||
|
* Einladungslink kommt es zurück ins Team.
|
||||||
|
*
|
||||||
|
* Der Owner ist ausgenommen. Ihn aus `teams.users` zu nehmen, nähme ihm
|
||||||
|
* die Sicht auf sein eigenes Team — die listRule der meisten Collections
|
||||||
|
* hängt an `team.users`, nicht an `team.owner`.
|
||||||
|
*/
|
||||||
remove(record: RidersResponse) {
|
remove(record: RidersResponse) {
|
||||||
|
const team = this.teams.records.find((t) => t.id === record.team)
|
||||||
|
const userId = record.user || null
|
||||||
|
const dropsMembership = !!userId && !!team && userId !== team.owner
|
||||||
|
|
||||||
|
const text = dropsMembership
|
||||||
|
? `Möchten Sie „${riderName(record)}" wirklich löschen? Alle Teilnahmen und Zeiten dieses Fahrers werden ebenfalls gelöscht. ` +
|
||||||
|
`Das zugehörige Konto verliert seine Mitgliedschaft im Team — gelöscht wird es nicht, es kann über einen Einladungslink zurückkommen.`
|
||||||
|
: `Möchten Sie „${riderName(record)}" wirklich löschen? Alle Teilnahmen und Zeiten dieses Fahrers werden ebenfalls gelöscht.`
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
app.confirm.request({
|
app.confirm.request({
|
||||||
title: 'Fahrer löschen?',
|
title: 'Fahrer löschen?',
|
||||||
text: `Möchten Sie „${fullName(record)}" wirklich löschen? Alle Teilnahmen und Zeiten dieses Fahrers werden ebenfalls gelöscht.`,
|
text,
|
||||||
yes: async () => {
|
yes: async () => {
|
||||||
try {
|
try {
|
||||||
|
// Erst die Mitgliedschaft, dann der Fahrer. Andersherum
|
||||||
|
// stünde bei einem Fehler im zweiten Schritt genau das
|
||||||
|
// verwaiste Konto da, das hier verhindert werden soll.
|
||||||
|
if (dropsMembership) {
|
||||||
|
await this.teams.removeMember(team!.id, userId!)
|
||||||
|
}
|
||||||
|
|
||||||
await api.collection('riders').delete(record.id)
|
await api.collection('riders').delete(record.id)
|
||||||
app.confirm.close()
|
app.confirm.close()
|
||||||
resolve()
|
resolve()
|
||||||
|
|
@ -116,9 +158,14 @@ export class RiderStore {
|
||||||
return this.scoped.filter((r) => r.user === userId)
|
return this.scoped.filter((r) => r.user === userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Bestehendes Konto zuordnen oder die Zuordnung lösen. */
|
/**
|
||||||
async linkUser(riderId: string, userId: string | null) {
|
* Konto an den Fahrer hängen. Nur intern und nur in diese Richtung: Ein
|
||||||
return await this.edit(riderId, { user: userId ?? '' } as Partial<RidersResponse>)
|
* Konto entsteht am Fahrer, ein Lösen der Verbindung gibt es nicht — sonst
|
||||||
|
* stünde dieselbe Person zweimal in der Liste, einmal mit und einmal ohne
|
||||||
|
* Login.
|
||||||
|
*/
|
||||||
|
private async linkUser(riderId: string, userId: string) {
|
||||||
|
return await this.edit(riderId, { user: userId } as Partial<RidersResponse>)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -131,7 +178,7 @@ export class RiderStore {
|
||||||
): Promise<UsersResponse> {
|
): Promise<UsersResponse> {
|
||||||
const user = await this.teams.createMember({
|
const user = await this.teams.createMember({
|
||||||
...data,
|
...data,
|
||||||
name: data.name?.trim() || fullName(rider),
|
name: data.name?.trim() || riderName(rider),
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.linkUser(rider.id, user.id)
|
await this.linkUser(rider.id, user.id)
|
||||||
|
|
@ -142,13 +189,7 @@ export class RiderStore {
|
||||||
get filtered() {
|
get filtered() {
|
||||||
const q = this.searchQuery.toLowerCase().trim()
|
const q = this.searchQuery.toLowerCase().trim()
|
||||||
if (!q) return this.scoped
|
if (!q) return this.scoped
|
||||||
return this.scoped.filter((r) => {
|
return this.scoped.filter((r) => riderName(r).toLowerCase().includes(q))
|
||||||
return (
|
|
||||||
(r.firstname ?? '').toLowerCase().includes(q) ||
|
|
||||||
(r.lastname ?? '').toLowerCase().includes(q) ||
|
|
||||||
(r.name ?? '').toLowerCase().includes(q)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
|
|
||||||
19
frontend/src/lib/types.d.ts
vendored
19
frontend/src/lib/types.d.ts
vendored
|
|
@ -11,6 +11,7 @@ export enum Collections {
|
||||||
Events = "events",
|
Events = "events",
|
||||||
Riders = "riders",
|
Riders = "riders",
|
||||||
Runs = "runs",
|
Runs = "runs",
|
||||||
|
TeamInvites = "team_invites",
|
||||||
Teams = "teams",
|
Teams = "teams",
|
||||||
Times = "times",
|
Times = "times",
|
||||||
TrailComments = "trail_comments",
|
TrailComments = "trail_comments",
|
||||||
|
|
@ -116,9 +117,7 @@ export type EventsRecord = {
|
||||||
export type RidersRecord = {
|
export type RidersRecord = {
|
||||||
avatar?: string
|
avatar?: string
|
||||||
created?: IsoDateString
|
created?: IsoDateString
|
||||||
firstname?: string
|
|
||||||
id: string
|
id: string
|
||||||
lastname?: string
|
|
||||||
name?: string
|
name?: string
|
||||||
team?: RecordIdString
|
team?: RecordIdString
|
||||||
updated?: IsoDateString
|
updated?: IsoDateString
|
||||||
|
|
@ -134,6 +133,18 @@ export type RunsRecord = {
|
||||||
updated?: IsoDateString
|
updated?: IsoDateString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TeamInvitesRecord = {
|
||||||
|
created?: IsoDateString
|
||||||
|
created_by?: RecordIdString
|
||||||
|
expires?: IsoDateString
|
||||||
|
id: string
|
||||||
|
max_uses?: number
|
||||||
|
team: RecordIdString
|
||||||
|
token: string
|
||||||
|
updated?: IsoDateString
|
||||||
|
uses?: number
|
||||||
|
}
|
||||||
|
|
||||||
export type TeamsRecord = {
|
export type TeamsRecord = {
|
||||||
admins?: RecordIdString[]
|
admins?: RecordIdString[]
|
||||||
created?: IsoDateString
|
created?: IsoDateString
|
||||||
|
|
@ -268,6 +279,7 @@ export type EventSeriesResponse<Texpand = unknown> = Required<EventSeriesRecord>
|
||||||
export type EventsResponse<Texpand = unknown> = Required<EventsRecord> & BaseSystemFields<Texpand>
|
export type EventsResponse<Texpand = unknown> = Required<EventsRecord> & BaseSystemFields<Texpand>
|
||||||
export type RidersResponse<Texpand = unknown> = Required<RidersRecord> & BaseSystemFields<Texpand>
|
export type RidersResponse<Texpand = unknown> = Required<RidersRecord> & BaseSystemFields<Texpand>
|
||||||
export type RunsResponse<Texpand = unknown> = Required<RunsRecord> & BaseSystemFields<Texpand>
|
export type RunsResponse<Texpand = unknown> = Required<RunsRecord> & BaseSystemFields<Texpand>
|
||||||
|
export type TeamInvitesResponse<Texpand = unknown> = Required<TeamInvitesRecord> & BaseSystemFields<Texpand>
|
||||||
export type TeamsResponse<Texpand = unknown> = Required<TeamsRecord> & BaseSystemFields<Texpand>
|
export type TeamsResponse<Texpand = unknown> = Required<TeamsRecord> & BaseSystemFields<Texpand>
|
||||||
export type TimesResponse<Texpand = unknown> = Required<TimesRecord> & BaseSystemFields<Texpand>
|
export type TimesResponse<Texpand = unknown> = Required<TimesRecord> & BaseSystemFields<Texpand>
|
||||||
export type TrailCommentsResponse<Texpand = unknown> = Required<TrailCommentsRecord> & BaseSystemFields<Texpand>
|
export type TrailCommentsResponse<Texpand = unknown> = Required<TrailCommentsRecord> & BaseSystemFields<Texpand>
|
||||||
|
|
@ -285,6 +297,7 @@ export type CollectionRecords = {
|
||||||
events: EventsRecord
|
events: EventsRecord
|
||||||
riders: RidersRecord
|
riders: RidersRecord
|
||||||
runs: RunsRecord
|
runs: RunsRecord
|
||||||
|
team_invites: TeamInvitesRecord
|
||||||
teams: TeamsRecord
|
teams: TeamsRecord
|
||||||
times: TimesRecord
|
times: TimesRecord
|
||||||
trail_comments: TrailCommentsRecord
|
trail_comments: TrailCommentsRecord
|
||||||
|
|
@ -301,6 +314,7 @@ export type CollectionResponses = {
|
||||||
events: EventsResponse
|
events: EventsResponse
|
||||||
riders: RidersResponse
|
riders: RidersResponse
|
||||||
runs: RunsResponse
|
runs: RunsResponse
|
||||||
|
team_invites: TeamInvitesResponse
|
||||||
teams: TeamsResponse
|
teams: TeamsResponse
|
||||||
times: TimesResponse
|
times: TimesResponse
|
||||||
trail_comments: TrailCommentsResponse
|
trail_comments: TrailCommentsResponse
|
||||||
|
|
@ -320,6 +334,7 @@ export type TypedPocketBase = PocketBase & {
|
||||||
collection(idOrName: 'events'): RecordService<EventsResponse>
|
collection(idOrName: 'events'): RecordService<EventsResponse>
|
||||||
collection(idOrName: 'riders'): RecordService<RidersResponse>
|
collection(idOrName: 'riders'): RecordService<RidersResponse>
|
||||||
collection(idOrName: 'runs'): RecordService<RunsResponse>
|
collection(idOrName: 'runs'): RecordService<RunsResponse>
|
||||||
|
collection(idOrName: 'team_invites'): RecordService<TeamInvitesResponse>
|
||||||
collection(idOrName: 'teams'): RecordService<TeamsResponse>
|
collection(idOrName: 'teams'): RecordService<TeamsResponse>
|
||||||
collection(idOrName: 'times'): RecordService<TimesResponse>
|
collection(idOrName: 'times'): RecordService<TimesResponse>
|
||||||
collection(idOrName: 'trail_comments'): RecordService<TrailCommentsResponse>
|
collection(idOrName: 'trail_comments'): RecordService<TrailCommentsResponse>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
import { goto } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
import { getEventContext } from '$lib/stores/events.svelte'
|
import { getEventContext } from '$lib/stores/events.svelte'
|
||||||
import { getRunContext } from '$lib/stores/runs.svelte'
|
import { getRunContext } from '$lib/stores/runs.svelte'
|
||||||
import { fullName, getRiderContext } from '$lib/stores/riders.svelte'
|
import { riderName, getRiderContext } from '$lib/stores/riders.svelte'
|
||||||
import { getTimeContext } from '$lib/stores/times.svelte'
|
import { getTimeContext } from '$lib/stores/times.svelte'
|
||||||
import { getEventParticipantContext } from '$lib/stores/eventParticipants.svelte'
|
import { getEventParticipantContext } from '$lib/stores/eventParticipants.svelte'
|
||||||
import { ticker } from '$lib/stores/ticker.svelte'
|
import { ticker } from '$lib/stores/ticker.svelte'
|
||||||
|
|
@ -85,7 +85,7 @@
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head><title>{rider ? fullName(rider) : 'Fahrer'} – {event?.name ?? 'Event'}</title></svelte:head>
|
<svelte:head><title>{rider ? riderName(rider) : 'Fahrer'} – {event?.name ?? 'Event'}</title></svelte:head>
|
||||||
|
|
||||||
{#if !valid}
|
{#if !valid}
|
||||||
<Card>
|
<Card>
|
||||||
|
|
@ -117,7 +117,7 @@
|
||||||
<Badge variant="outline" class="text-lg px-3 py-1">{entry.number}</Badge>
|
<Badge variant="outline" class="text-lg px-3 py-1">{entry.number}</Badge>
|
||||||
{/if}
|
{/if}
|
||||||
<h1 class="text-3xl font-bold tracking-tight">
|
<h1 class="text-3xl font-bold tracking-tight">
|
||||||
{fullName(rider) || 'Ohne Name'}
|
{riderName(rider) || 'Ohne Name'}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
import { page } from '$app/state'
|
import { page } from '$app/state'
|
||||||
import { getEventContext } from '$lib/stores/events.svelte'
|
import { getEventContext } from '$lib/stores/events.svelte'
|
||||||
import { getRunContext } from '$lib/stores/runs.svelte'
|
import { getRunContext } from '$lib/stores/runs.svelte'
|
||||||
import { fullName, getRiderContext } from '$lib/stores/riders.svelte'
|
import { riderName, getRiderContext } from '$lib/stores/riders.svelte'
|
||||||
import { getTimeContext } from '$lib/stores/times.svelte'
|
import { getTimeContext } from '$lib/stores/times.svelte'
|
||||||
import { getEventParticipantContext } from '$lib/stores/eventParticipants.svelte'
|
import { getEventParticipantContext } from '$lib/stores/eventParticipants.svelte'
|
||||||
import { ticker } from '$lib/stores/ticker.svelte'
|
import { ticker } from '$lib/stores/ticker.svelte'
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
rows.push([
|
rows.push([
|
||||||
String(i + 1),
|
String(i + 1),
|
||||||
event ? participants.numberOf(event.id, t.rider) : '',
|
event ? participants.numberOf(event.id, t.rider) : '',
|
||||||
r ? fullName(r) : '',
|
r ? riderName(r) : '',
|
||||||
t.formattedTime ?? '',
|
t.formattedTime ?? '',
|
||||||
String(t.correction ?? 0),
|
String(t.correction ?? 0),
|
||||||
])
|
])
|
||||||
|
|
@ -195,7 +195,7 @@
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell class="font-medium">
|
<Table.Cell class="font-medium">
|
||||||
{#if r}
|
{#if r}
|
||||||
<a href={riderPath(r.id)} class="hover:underline">{fullName(r) || '—'}</a>
|
<a href={riderPath(r.id)} class="hover:underline">{riderName(r) || '—'}</a>
|
||||||
{:else}
|
{:else}
|
||||||
—
|
—
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -233,7 +233,7 @@
|
||||||
</Badge>
|
</Badge>
|
||||||
<span class="truncate">
|
<span class="truncate">
|
||||||
{#if r}
|
{#if r}
|
||||||
<a href={riderPath(r.id)} class="hover:underline">{fullName(r) || '—'}</a>
|
<a href={riderPath(r.id)} class="hover:underline">{riderName(r) || '—'}</a>
|
||||||
{:else}
|
{:else}
|
||||||
—
|
—
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue