Add segments, YTD stats, route matching fixes, body battery layout, pace fix
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

- 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>
This commit is contained in:
2026-06-07 12:01:25 +01:00
parent f0bbe92b2c
commit 02eccad578
13 changed files with 797 additions and 32 deletions
+18 -14
View File
@@ -103,7 +103,7 @@ function BodyBatteryChart({ bb }) {
}))
return (
<div className="bg-gray-900 rounded-xl border border-gray-800 p-5 space-y-4">
<div className="bg-gray-900 rounded-xl border border-gray-800 p-5 space-y-4 h-full">
<h3 className="text-sm font-medium text-gray-300">Body Battery</h3>
<div className="flex items-center gap-8">
@@ -340,22 +340,26 @@ function DailySnapshot({ day, avg30, intradayHr, bodyBattery, onOlder, onNewer,
</div>
</div>
{/* 24-hour heart rate chart */}
{intradayHr?.length > 0 && (
<div className="bg-gray-900 rounded-xl border border-gray-800 p-4">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-gray-300">24-hour Heart Rate</h3>
{day.avg_hr_day && (
<span className="text-xs text-gray-500">avg {Math.round(day.avg_hr_day)} bpm</span>
)}
</div>
<IntradayHrChart values={intradayHr} />
{/* 24-hour heart rate chart + body battery (side by side) */}
{(intradayHr?.length > 0 || bodyBattery) && (
<div className={`grid gap-4 ${intradayHr?.length > 0 && bodyBattery ? 'grid-cols-1 lg:grid-cols-2' : 'grid-cols-1'}`}>
{intradayHr?.length > 0 && (
<div className="bg-gray-900 rounded-xl border border-gray-800 p-4 flex flex-col">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-gray-300">24-hour Heart Rate</h3>
{day.avg_hr_day && (
<span className="text-xs text-gray-500">avg {Math.round(day.avg_hr_day)} bpm</span>
)}
</div>
<div className="flex-1 min-h-0">
<IntradayHrChart values={intradayHr} />
</div>
</div>
)}
<BodyBatteryChart bb={bodyBattery} />
</div>
)}
{/* Body battery */}
<BodyBatteryChart bb={bodyBattery} />
{/* Activity strip */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">