Files
MileVault/frontend/src/components/ui/Layout.jsx
T
owain 02eccad578
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 5s
Build and push images / build-worker (push) Successful in 5s
Build and push images / build-frontend (push) Successful in 9s
Add segments, YTD stats, route matching fixes, body battery layout, pace fix
- 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>
2026-06-07 12:01:25 +01:00

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>
)
}