-
{rider.number || '—'}
-
- {fullName(rider) || 'Ohne Name'}
-
+
+
+
+
openTimeDialog()} disabled={rows.length === 0}>
+
+ Zeit erfassen
+
@@ -215,3 +233,10 @@
{/if}
+
+
diff --git a/frontend/src/routes/dashboard/riders/+page.svelte b/frontend/src/routes/dashboard/riders/+page.svelte
new file mode 100644
index 0000000..bdee0c5
--- /dev/null
+++ b/frontend/src/routes/dashboard/riders/+page.svelte
@@ -0,0 +1,143 @@
+
+
+
Fahrer
+
+
+
+
+
Fahrer
+
+ {riders.scoped.length} im Team {teams.active?.name ?? ''}
+
+
+ {#if riders.canManage}
+
+
+ Verwalten
+
+ {/if}
+
+
+
+
+
+
+
+ {#if riders.scoped.length === 0}
+
+
+
+ Noch keine Fahrer
+
+ {#if riders.canManage}
+ Leg sie in den Einstellungen an.
+ {:else}
+ Die Teamleitung legt Fahrer in den Einstellungen an.
+ {/if}
+
+
+
+ {:else if roster.length === 0}
+
+ Keine Treffer.
+
+ {:else}
+
+
+
+
+
+ #
+ Name
+ Event
+ Zeiten
+ Konto
+
+
+
+ {#each roster as r (r.id)}
+ {@const path = riderPath(r)}
+
+
+ {r.number || '—'}
+
+
+ {#if path}
+ {fullName(r) || '— ohne Name —'}
+ {:else}
+ {fullName(r) || '— ohne Name —'}
+ {/if}
+
+
+ {eventName(r.event) ?? '—'}
+
+
+ {times.getByRider(r.id).length}
+
+
+ {#if r.user}
+
+
+ {teams.userLabel(r.user)}
+
+ {:else}
+ —
+ {/if}
+
+
+ {/each}
+
+
+
+
+ {/if}
+
diff --git a/frontend/src/routes/dashboard/settings/+layout.svelte b/frontend/src/routes/dashboard/settings/+layout.svelte
new file mode 100644
index 0000000..612abb8
--- /dev/null
+++ b/frontend/src/routes/dashboard/settings/+layout.svelte
@@ -0,0 +1,57 @@
+
+
+
Einstellungen
+
+
+
Einstellungen
+
+
+ {#each tabs as tab (tab.href)}
+ {@const Icon = tab.icon}
+
+
+ {tab.label}
+
+ {/each}
+
+
+ {@render children?.()}
+
diff --git a/frontend/src/routes/dashboard/settings/+page.svelte b/frontend/src/routes/dashboard/settings/+page.svelte
new file mode 100644
index 0000000..26bb038
--- /dev/null
+++ b/frontend/src/routes/dashboard/settings/+page.svelte
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+
+ Konto
+
+
+
+
+ {#if avatarUrl}
+
+ {:else}
+
+
+
+ {/if}
+
+ Bild
+ (avatarFile = e.currentTarget.files?.[0] ?? null)}
+ />
+
+
+
+
+ Name
+
+
+
+
+
E-Mail
+
+
+ Die E-Mail-Adresse ist der Anmeldename. Ändern kann sie nur die
+ Team-Administration über PocketBase.
+
+
+
+ {#if profileError}
+ {profileError}
+ {:else if profileMessage}
+ {profileMessage}
+ {/if}
+
+
+ {savingProfile ? 'Wird gespeichert …' : 'Speichern'}
+
+
+
+
+
+
+
+
+ Passwort ändern
+
+
+
+
+ Aktuelles Passwort
+
+
+
+ Neues Passwort
+
+
+
+ Wiederholen
+
+
+
+ {#if passwordError}
+ {passwordError}
+ {:else if passwordMessage}
+ {passwordMessage}
+ {/if}
+
+
+ {savingPassword ? 'Wird geändert …' : 'Passwort ändern'}
+
+
+
+
diff --git a/frontend/src/routes/dashboard/settings/flags/+page.svelte b/frontend/src/routes/dashboard/settings/flags/+page.svelte
index 5fb1727..3b607de 100644
--- a/frontend/src/routes/dashboard/settings/flags/+page.svelte
+++ b/frontend/src/routes/dashboard/settings/flags/+page.svelte
@@ -7,6 +7,8 @@
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import * as Dialog from '@/components/ui/dialog'
+ import FlagIcon from '$lib/components/FlagIcon.svelte'
+ import { FLAG_ICON_SUGGESTIONS, iconExists } from '$lib/flagIcons'
import { Plus, Pencil, Trash2, Sparkles } from 'lucide-svelte'
import type { TrailFlagsResponse } from '$lib/types'
@@ -104,10 +106,9 @@
}
-
+
-
Flag-Typen
Wofür sich Marker auf einem Trail setzen lassen.
@@ -149,7 +150,7 @@
{#each flags.scoped as flag (flag.id)}
-
+
{flag.label}
{flag.severity}
@@ -203,10 +204,38 @@
Icon
-
+
+
+
+
+
+
- Name eines Icons von lucide.dev, in Schreibweise wie „TreePine".
+ Jeder Name von lucide.dev
+ ist möglich — geschrieben wie „TreePine" oder „tree-pine".
+ {#if form.icon.trim() && !iconExists(form.icon)}
+ Diesen Namen gibt es dort nicht.
+ {/if}
+
+
Vorschläge:
+
+ {#each FLAG_ICON_SUGGESTIONS as name (name)}
+ (form.icon = name)}
+ >
+
+
+ {/each}
+
{#if error}
{error}
diff --git a/frontend/src/routes/dashboard/settings/riders/+page.svelte b/frontend/src/routes/dashboard/settings/riders/+page.svelte
new file mode 100644
index 0000000..cdd020d
--- /dev/null
+++ b/frontend/src/routes/dashboard/settings/riders/+page.svelte
@@ -0,0 +1,385 @@
+
+
+{#if !canManage}
+
+
+ Fahrer verwaltet, wer das Team führt. Die Fahrer deines Teams siehst du
+ unter Fahrer .
+
+
+{:else}
+
+
+
+ Fahrer des Teams. Ein verknüpftes Konto erlaubt der Person, sich selbst
+ anzumelden.
+
+
+
+ Fahrer anlegen
+
+
+
+ {#if roster.length === 0}
+
+
+ Noch keine Fahrer im Team.
+
+
+ {:else}
+
+
+
+
+
+ #
+ Name
+ Event
+ Konto
+ Aktionen
+
+
+
+ {#each roster as r (r.id)}
+
+
+ {r.number || '—'}
+
+
+ {fullName(r) || '— ohne Name —'}
+
+
+ {eventName(r.event) ?? '—'}
+
+
+ {#if r.user}
+
+
+
+ kann sich einloggen
+
+
+ {teams.userLabel(r.user)}
+
+
+ {:else}
+ —
+ {/if}
+
+
+ {#if r.user}
+ doUnlink(r)}>
+
+
+ {:else}
+ openLogin(r)}>
+
+
+ {/if}
+ openEdit(r)}>
+
+
+ riders.remove(r)}>
+
+
+
+
+ {/each}
+
+
+
+
+ {/if}
+
+{/if}
+
+
+
+
+
+ {editing ? 'Fahrer bearbeiten' : 'Neuer Fahrer'}
+
+
+
+ Startnummer
+
+
+
+
+
Event
+
+
+ {eventName(form.event) ?? 'Keinem Event zugeordnet'}
+
+
+ Keinem Event zugeordnet
+ {#each events.scoped as e (e.id)}
+ {e.name || e.description}
+ {/each}
+
+
+
+ Zeiten hängen am Event. Ohne Zuordnung steht der Fahrer nur im
+ Team-Verzeichnis.
+
+
+ {#if riderError}
+
{riderError}
+ {/if}
+
+
+ (riderDialog = false)}>Abbrechen
+ {editing ? 'Speichern' : 'Anlegen'}
+
+
+
+
+
+
+
+
+ Konto für {loginRider ? fullName(loginRider) : 'Fahrer'}
+
+ Mit einem Konto kann sich die Person selbst anmelden.
+
+
+
+
+ (loginMode = 'link')}
+ >
+ Vorhandenes verknüpfen
+
+ (loginMode = 'create')}
+ >
+ Neues anlegen
+
+
+
+
+ {#if loginMode === 'link'}
+
+
Teammitglied
+
+
+ {linkUserId ? teams.userLabel(linkUserId) : 'Mitglied wählen…'}
+
+
+ {#each linkCandidates as id (id)}
+ {teams.userLabel(id)}
+ {/each}
+
+
+ {#if linkCandidates.length === 0}
+
+ Alle Teammitglieder sind bereits einem Fahrer zugeordnet.
+
+ {/if}
+
+ {:else}
+
+ E-Mail
+
+
+
+
Passwort
+
+
+ Das Konto kommt zugleich ins Team. Gib das Passwort weiter; es lässt
+ sich unter „Profil" ändern.
+
+
+ {/if}
+
+ {#if loginError}
+
{loginError}
+ {/if}
+
+
+
+ (loginDialog = false)}>Abbrechen
+ {#if loginMode === 'link'}
+ Verknüpfen
+ {:else}
+
+ {loginBusy ? 'Wird angelegt …' : 'Konto anlegen'}
+
+ {/if}
+
+
+
diff --git a/frontend/src/routes/dashboard/settings/team/+page.svelte b/frontend/src/routes/dashboard/settings/team/+page.svelte
new file mode 100644
index 0000000..cbb24a9
--- /dev/null
+++ b/frontend/src/routes/dashboard/settings/team/+page.svelte
@@ -0,0 +1,329 @@
+
+
+
+
+
+ Teams, in denen du bist. Das aktive Team bestimmt, welche Events, Fahrer und
+ Trails du siehst.
+
+
(createDialog = true)}>
+
+ Neues Team
+
+
+
+ {#if teams.records.length === 0}
+
+
+
+ Noch in keinem Team
+ Erstelle dein erstes Team.
+ (createDialog = true)}>
+
+ Team erstellen
+
+
+
+ {:else}
+ {#each teams.records as t (t.id)}
+ {@const iAmOwner = t.owner === me}
+ {@const iAmAdmin = iAmOwner || (t.admins ?? []).includes(me ?? '')}
+
+
+
+
+
+ {t.name}
+ {#if t.id === teams.activeId}
+ Aktiv
+ {/if}
+
+
+ {(t.users ?? []).length} Mitglied{(t.users ?? []).length === 1 ? '' : 'er'}
+
+
+
+ {#if t.id !== teams.activeId}
+ teams.setActive(t.id)}>
+ Aktivieren
+
+ {/if}
+ {#if iAmAdmin}
+ openRename(t)}>
+ Umbenennen
+
+ openMember(t)}>
+
+ Mitglied
+
+ {/if}
+ {#if !iAmOwner}
+ doLeave(t)}>
+
+ Verlassen
+
+ {:else}
+ teams.remove(t)}>
+
+
+ {/if}
+
+
+
+
+
+ {#each (t.users ?? []) as userId (userId)}
+ {@const isOwner = userId === t.owner}
+ {@const isAdm = (t.admins ?? []).includes(userId)}
+
+
+ {teams.userLabel(userId)}
+ {#if isOwner}
+
+
+ Owner
+
+ {:else if isAdm}
+
+
+ Admin
+
+ {/if}
+ {#if userId === me}
+ Du
+ {/if}
+
+ {#if iAmOwner && !isOwner}
+
+ {#if isAdm}
+ teams.demoteFromAdmin(t.id, userId)}>
+ Admin entziehen
+
+ {:else}
+ teams.promoteToAdmin(t.id, userId)}>
+ Zu Admin
+
+ {/if}
+ doRemoveMember(t, userId)}>
+
+
+
+ {/if}
+
+ {/each}
+
+
+
+ {/each}
+ {/if}
+
+
+
+
+
+
+ Neues Team
+ Du wirst automatisch Owner.
+
+
+
+ Name
+
+
+ {#if createError}
+
{createError}
+ {/if}
+
+
+ (createDialog = false)}>Abbrechen
+ Erstellen
+
+
+
+
+
+
+
+
+ Team umbenennen
+
+
+
+ Name
+
+
+ {#if renameError}
+
{renameError}
+ {/if}
+
+
+ (renameDialog = false)}>Abbrechen
+ Speichern
+
+
+
+
+
+
+
+
+ Mitglied anlegen
+
+ Legt ein Konto an und nimmt es in „{memberTeam?.name}" auf.
+
+
+
+
+ Name
+
+
+
+ E-Mail
+
+
+
+
Passwort
+
+
+ Gib es der Person weiter; sie kann es unter „Profil" ändern.
+
+
+ {#if memberError}
+
{memberError}
+ {/if}
+
+
+ (memberDialog = false)}>Abbrechen
+
+ {memberBusy ? 'Wird angelegt …' : 'Anlegen'}
+
+
+
+
diff --git a/frontend/src/routes/dashboard/teams/+page.svelte b/frontend/src/routes/dashboard/teams/+page.svelte
deleted file mode 100644
index 26a90bc..0000000
--- a/frontend/src/routes/dashboard/teams/+page.svelte
+++ /dev/null
@@ -1,341 +0,0 @@
-
-
-
Teams – Zeitnahme
-
-
-
-
-
Teams
-
Deine Teams und Mitglieder verwalten
-
-
(createDialog = true)}>
-
- Neues Team
-
-
-
- {#if teams.records.length === 0}
-
-
-
- Noch in keinem Team
- Erstelle dein erstes Team.
- (createDialog = true)}>
-
- Team erstellen
-
-
-
- {:else}
-
- {#each teams.records as t (t.id)}
- {@const iAmOwner = t.owner === me}
- {@const iAmAdmin = iAmOwner || (t.admins ?? []).includes(me ?? '')}
-
-
-
-
-
- {t.name}
- {#if t.id === teams.activeId}
- Aktiv
- {/if}
-
-
- {(t.users ?? []).length} Mitglied{(t.users ?? []).length === 1 ? '' : 'er'}
-
-
-
- {#if t.id !== teams.activeId}
- teams.setActive(t.id)}>
- Aktivieren
-
- {/if}
- {#if iAmAdmin}
- openInvite(t)}>
-
- Mitglied
-
- {/if}
- {#if !iAmOwner}
- doLeave(t)}>
-
- Verlassen
-
- {/if}
- {#if iAmOwner}
- teams.remove(t)}>
-
-
- {/if}
-
-
-
-
-
- {#each (t.users ?? []) as userId}
- {@const isOwner = userId === t.owner}
- {@const isAdm = isAdmin(t, userId)}
-
-
- {userLabel(userId)}
- {#if isOwner}
-
-
- Owner
-
- {:else if isAdm}
-
-
- Admin
-
- {/if}
- {#if userId === me}
- Du
- {/if}
-
- {#if iAmOwner && !isOwner}
-
- {#if isAdm}
- doDemote(t, userId)}>
- Admin entziehen
-
- {:else}
- doPromote(t, userId)}>
- Zu Admin
-
- {/if}
- doRemoveMember(t, userId)}>
-
-
-
- {/if}
-
- {/each}
-
-
-
- {/each}
-
- {/if}
-
-
-
-
-
-
- Neues Team
- Du wirst automatisch Owner.
-
-
-
-
-
-
-
-
-
- Mitglied hinzufügen
-
- Suche einen registrierten User über seine E-Mail-Adresse.
-
-
-
-
-