stammtisch-hersbruck/frontend/src/routes/dashboard/+layout.svelte
Daniel Michelberger ccc1d931ab fix: Flag-Verwaltung dauerhaft über die Navigation erreichbar machen
/dashboard/settings/flags war nur über einen Link im Leerzustand
verlinkt — sobald ein Team einen Flag-Typ hat, verschwand der einzige
Weg dorthin. Neuer Navigationspunkt "Einstellungen" im Dashboard-Menü.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBMBfip6BAG8VV9raSf6vu
2026-08-06 19:28:54 +02:00

188 lines
7.9 KiB
Svelte

<script lang="ts">
import { auth } from '$lib/stores/pocketbase.svelte'
import { goto } from '$app/navigation'
import { app } from '$lib/stores/app.svelte'
import { setTeamContext } from '$lib/stores/teams.svelte'
import { setEventContext } from '$lib/stores/events.svelte'
import { setRunContext } from '$lib/stores/runs.svelte'
import { setRiderContext } from '$lib/stores/riders.svelte'
import { setTimeContext } from '$lib/stores/times.svelte'
import { setTrailContext } from '$lib/stores/trails.svelte'
import { setTrailVersionContext } from '$lib/stores/trailVersions.svelte'
import { setTrailFlagContext } from '$lib/stores/trailFlags.svelte'
import { setTrailMarkerContext } from '$lib/stores/trailMarkers.svelte'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import Logo from '$lib/components/Logo.svelte'
import TeamSwitcher from '$lib/components/TeamSwitcher.svelte'
import { Home, Calendar, Flag, Timer, LogOut, Menu, X, Moon, Sun, Route, Settings } from 'lucide-svelte'
import { toggleMode, mode } from 'mode-watcher'
import { onDestroy, onMount } from 'svelte'
let { children } = $props()
// Teams MUSS vor den anderen Stores stehen, weil die anderen Stores
// getTeamContext() im Constructor aufrufen
const teams = setTeamContext()
const events = setEventContext()
const runs = setRunContext()
const riders = setRiderContext()
const times = setTimeContext()
const trails = setTrailContext()
const trailVersions = setTrailVersionContext()
const trailFlags = setTrailFlagContext()
const trailMarkers = setTrailMarkerContext()
let isMobileMenuOpen = $state(false)
onMount(async () => {
if (!auth.user) {
goto('/login', { replaceState: true })
return
}
// Teams zuerst laden, weil die anderen Stores nach team filtern
await teams.load()
teams.subscribe()
await Promise.all([
events.load(), runs.load(), riders.load(), times.load(),
trails.load(), trailVersions.load(), trailFlags.load(), trailMarkers.load(),
])
events.subscribe()
runs.subscribe()
riders.subscribe()
times.subscribe()
trails.subscribe()
trailVersions.subscribe()
trailFlags.subscribe()
trailMarkers.subscribe()
})
onDestroy(() => {
teams.destroy()
events.destroy()
runs.destroy()
riders.destroy()
times.destroy()
trails.destroy()
trailVersions.destroy()
trailFlags.destroy()
trailMarkers.destroy()
})
function handleLogout() {
app.confirm.request({
title: 'Abmelden?',
text: 'Möchten Sie sich wirklich abmelden?',
yes: async () => {
await auth.logout()
app.confirm.close()
goto('/login')
},
no: () => app.confirm.close(),
})
}
const nav = [
{ href: '/dashboard', icon: Home, label: 'Dashboard' },
{ href: '/dashboard/events', icon: Calendar, label: 'Events' },
{ href: '/dashboard/trails', icon: Route, label: 'Trails' },
{ href: '/dashboard/timing', icon: Timer, label: 'Zeitnahme' },
{ href: '/dashboard/results', icon: Flag, label: 'Ergebnisse' },
{ href: '/dashboard/settings/flags', icon: Settings, label: 'Einstellungen' },
]
</script>
{#if auth.user}
<div class="min-h-screen bg-background">
<header class="border-b bg-card">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex h-16 items-center justify-between">
<div class="flex items-center gap-2">
<Logo />
<div class="hidden md:block">
<TeamSwitcher />
</div>
<Button variant="ghost" size="icon" onclick={toggleMode} title="Design wechseln">
{#if mode.current === 'dark'}
<Sun class="h-4 w-4" />
{:else}
<Moon class="h-4 w-4" />
{/if}
</Button>
</div>
<nav class="hidden md:flex items-center gap-1">
{#each nav as item}
{@const Icon = item.icon}
<a
href={item.href}
class="inline-flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
>
<Icon class="h-4 w-4" />
<span>{item.label}</span>
</a>
{/each}
</nav>
<div class="flex items-center gap-2">
<div class="hidden md:flex items-center gap-2">
<div class="text-sm text-right">
<p class="font-medium leading-tight">{auth.user.name || auth.user.email}</p>
<p class="text-xs text-muted-foreground leading-tight">{auth.user.email}</p>
</div>
<Button variant="ghost" size="icon" onclick={handleLogout} title="Abmelden">
<LogOut class="h-4 w-4" />
</Button>
</div>
<Button
variant="ghost"
size="icon"
class="md:hidden"
onclick={() => (isMobileMenuOpen = !isMobileMenuOpen)}
title="Menü"
>
{#if isMobileMenuOpen}
<X class="h-4 w-4" />
{:else}
<Menu class="h-4 w-4" />
{/if}
</Button>
</div>
</div>
</div>
{#if isMobileMenuOpen}
<div class="md:hidden border-t">
<nav class="px-2 py-2 space-y-1">
{#each nav as item}
{@const Icon = item.icon}
<a
href={item.href}
onclick={() => (isMobileMenuOpen = false)}
class="flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-accent"
>
<Icon class="h-4 w-4" />
<span>{item.label}</span>
</a>
{/each}
<Separator class="my-2" />
<div class="flex items-center justify-between px-3 py-2">
<div class="text-sm">
<p class="font-medium leading-tight">{auth.user.name || auth.user.email}</p>
<p class="text-xs text-muted-foreground leading-tight">{auth.user.email}</p>
</div>
<Button variant="ghost" size="icon" onclick={handleLogout}>
<LogOut class="h-4 w-4" />
</Button>
</div>
</nav>
</div>
{/if}
</header>
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{@render children?.()}
</main>
</div>
{/if}