02eccad578
- Segments page: new /segments route with auto-generate (1km splits, turn detection, hill detection), manual segment creation, per-segment performance times across matched activities; fixed auth on existing segment endpoints - YTD distance: new /activities/stats/ytd endpoint; Dashboard replaces 'Total distance' with 'Running this year' + 'Cycling this year'; Activities page shows YTD stats row - Weekly chart click: clicking a Dashboard bar navigates to Activities filtered to that week; Activities reads from/to query params with dismissable chip - Route matching: add ±2.5% distance gate + 3% relative DTW threshold (was flat 80m); tighten candidate pre-filter from 80/120% to 95/105% - Body battery layout: HR chart and body battery now side-by-side at same height on large screens instead of stacked full-width - Pace display fix: MetricTimeline clamps GPS speed outliers before computing Y-axis domain; tick formatter guards against v<=0 or v>25 m/s Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
64 lines
2.2 KiB
React
64 lines
2.2 KiB
React
import { Outlet, NavLink, useNavigate } from 'react-router-dom'
|
|
import { useAuthStore } from '../../hooks/useAuth'
|
|
|
|
const nav = [
|
|
{ to: '/', label: 'Dashboard', icon: '📊', exact: true },
|
|
{ to: '/activities', label: 'Activities', icon: '🏃' },
|
|
{ to: '/health', label: 'Health', icon: '❤️' },
|
|
{ to: '/routes', label: 'Routes', icon: '🗺️' },
|
|
{ to: '/segments', label: 'Segments', icon: '📏' },
|
|
{ to: '/records', label: 'Records', icon: '🏆' },
|
|
{ to: '/upload', label: 'Import', icon: '⬆️' },
|
|
{ to: '/profile', label: 'Profile', icon: '⚙️' },
|
|
]
|
|
|
|
export default function Layout() {
|
|
const { user, logout } = useAuthStore()
|
|
const navigate = useNavigate()
|
|
|
|
const handleLogout = () => {
|
|
logout()
|
|
navigate('/login')
|
|
}
|
|
|
|
return (
|
|
<div className="flex h-screen overflow-hidden bg-gray-950">
|
|
<aside className="w-56 flex-shrink-0 bg-gray-900 border-r border-gray-800 flex flex-col">
|
|
<div className="px-4 py-5 border-b border-gray-800">
|
|
<h1 className="text-lg font-bold text-white tracking-tight">
|
|
<span className="text-blue-400">Mile</span>Vault
|
|
</h1>
|
|
{user && <p className="text-xs text-gray-500 mt-0.5">@{user.username}{user.is_admin ? ' · admin' : ''}</p>}
|
|
</div>
|
|
|
|
<nav className="flex-1 py-4 overflow-y-auto">
|
|
{nav.map(({ to, label, icon, exact }) => (
|
|
<NavLink key={to} to={to} end={exact}
|
|
className={({ isActive }) =>
|
|
`flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${
|
|
isActive
|
|
? 'bg-blue-600/20 text-blue-400 border-r-2 border-blue-400'
|
|
: 'text-gray-400 hover:text-gray-100 hover:bg-gray-800'
|
|
}`
|
|
}>
|
|
<span>{icon}</span>
|
|
{label}
|
|
</NavLink>
|
|
))}
|
|
</nav>
|
|
|
|
<div className="px-4 py-4 border-t border-gray-800">
|
|
<button onClick={handleLogout}
|
|
className="w-full text-left text-xs text-gray-500 hover:text-gray-300 transition-colors">
|
|
Sign out
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
|
|
<main className="flex-1 overflow-y-auto">
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
)
|
|
}
|