Batch 1: dashboard, maps, segments rewrite, health, sync UX
Fixes:
- Dashboard: featured most-recent activity card with map + stats
- Maps default to Street; preferCanvas + larger tile buffer for smoother pan/zoom
- Running cadence as colour-banded dots + 165 spm guide line
- Routes: inline row expansion, rename (PATCH /routes/{id}), podium + deltas, tiled map
- Records: remove reversed pace Y-axis
- Profile: remove resting HR; add goal weight
- Health: snapshot weight carry-forward; VO2 trend axis 30-70;
weight goal line + kg/st-lb toggle + axis max; sleep 8h/avg lines
- Garmin sync progress moved to global store with persistent floating bar
Features:
- Speed-coloured activity route (default) with Speed/Solid toggle
- GPS-geometry segments: draw on map, match across all activities,
1st/2nd/3rd leaderboard + podium badges (replaces old distance segments)
- Lap bests: best time per lap across a route + delta column
- Body Battery: highlight activity time windows
Schema: users.goal_weight_kg ALTER; new segments/segment_efforts tables.
Removes RouteSegment, the Segments page, and segment-bests endpoints.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,11 +4,21 @@ import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContaine
|
||||
import { startOfWeek, format, subWeeks, eachWeekOfInterval, subDays, addDays } from 'date-fns'
|
||||
import api from '../utils/api'
|
||||
import StatCard from '../components/ui/StatCard'
|
||||
import ActivityMap from '../components/activity/ActivityMap'
|
||||
import {
|
||||
formatDuration, formatDistance, formatPace, formatHeartRate,
|
||||
formatDuration, formatDistance, formatPace, formatHeartRate, formatElevation,
|
||||
formatDate, sportIcon, formatSleep,
|
||||
} from '../utils/format'
|
||||
|
||||
function Stat({ label, value }) {
|
||||
return (
|
||||
<div className="bg-gray-900 px-4 py-3 flex flex-col justify-center">
|
||||
<p className="text-xs text-gray-500">{label}</p>
|
||||
<p className="text-lg font-semibold text-white">{value}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function bbLevelColor(level) {
|
||||
if (level == null) return '#6b7280'
|
||||
if (level >= 75) return '#3b82f6'
|
||||
@@ -154,6 +164,7 @@ export default function DashboardPage() {
|
||||
})
|
||||
|
||||
const latest = healthSummary?.latest
|
||||
const featured = recentActivities?.[0]
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
@@ -203,6 +214,37 @@ export default function DashboardPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Featured most-recent activity */}
|
||||
{featured && (
|
||||
<div className="bg-gray-900 rounded-xl border border-gray-800 overflow-hidden">
|
||||
<div className="flex items-center justify-between px-4 py-3 border-b border-gray-800">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<span className="text-xl">{sportIcon(featured.sport_type)}</span>
|
||||
<div className="min-w-0">
|
||||
<Link to={`/activities/${featured.id}`} className="text-sm font-semibold text-white hover:text-blue-400 transition-colors truncate block">
|
||||
{featured.name}
|
||||
</Link>
|
||||
<p className="text-xs text-gray-500">{formatDate(featured.start_time)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Link to={`/activities/${featured.id}`} className="text-xs text-blue-400 hover:underline flex-shrink-0">Open →</Link>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3">
|
||||
<div className="lg:col-span-2 h-64 bg-gray-950">
|
||||
{featured.polyline
|
||||
? <ActivityMap polyline={featured.polyline} sportType={featured.sport_type} colorMode="solid" />
|
||||
: <div className="flex items-center justify-center h-full text-gray-600 text-sm">No GPS track</div>}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 lg:grid-cols-1 gap-px bg-gray-800/50">
|
||||
<Stat label="Distance" value={formatDistance(featured.distance_m)} />
|
||||
<Stat label="Elevation ↑" value={formatElevation(featured.elevation_gain_m)} />
|
||||
<Stat label="Moving time" value={formatDuration(featured.duration_s)} />
|
||||
<Stat label="Calories" value={featured.calories ? `${Math.round(featured.calories)} kcal` : '--'} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Recent activities */}
|
||||
<div className="bg-gray-900 rounded-xl border border-gray-800 p-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
|
||||
Reference in New Issue
Block a user