Files
MileVault/frontend/src/components/activity/LapTable.jsx
T
owain bc437cce92
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 6s
Build and push images / build-worker (push) Successful in 6s
Build and push images / build-frontend (push) Successful in 9s
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>
2026-06-08 19:59:06 +01:00

64 lines
3.2 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { formatDuration, formatDistance, formatPace, formatHeartRate, formatCadence } from '../../utils/format'
const RUNNING_TYPES = new Set(['running', 'hiking', 'walking'])
export default function LapTable({ laps, sportType, lapBests }) {
const showPower = !RUNNING_TYPES.has(sportType?.toLowerCase())
const hasBests = lapBests && Object.keys(lapBests).length > 0
return (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="text-xs text-gray-500 border-b border-gray-800">
<th className="text-left pb-2 font-medium">Lap</th>
<th className="text-right pb-2 font-medium">Distance</th>
<th className="text-right pb-2 font-medium">Time</th>
{hasBests && <th className="text-right pb-2 font-medium">Best</th>}
{hasBests && <th className="text-right pb-2 font-medium">Δ</th>}
<th className="text-right pb-2 font-medium">Pace</th>
<th className="text-right pb-2 font-medium">Avg HR</th>
<th className="text-right pb-2 font-medium">Cadence</th>
{showPower && <th className="text-right pb-2 font-medium">Power</th>}
</tr>
</thead>
<tbody>
{laps.map((lap) => {
const best = hasBests ? lapBests[String(lap.lap_number)] : null
const delta = best != null && lap.duration_s != null ? lap.duration_s - best : null
const isBest = delta != null && delta <= 0.5
return (
<tr key={lap.lap_number} className="border-b border-gray-800/50 hover:bg-gray-800/30 transition-colors">
<td className="py-2 text-gray-400">{lap.lap_number}</td>
<td className="py-2 text-right text-gray-200">{formatDistance(lap.distance_m)}</td>
<td className={`py-2 text-right ${isBest ? 'text-yellow-400 font-semibold' : 'text-gray-200'}`}>{formatDuration(lap.duration_s)}</td>
{hasBests && (
<td className="py-2 text-right font-mono text-gray-500">{best != null ? formatDuration(best) : '--'}</td>
)}
{hasBests && (
<td className={`py-2 text-right font-mono ${
delta == null ? 'text-gray-700' : isBest ? 'text-yellow-400' : delta < 0 ? 'text-green-400' : 'text-red-400'
}`}>
{delta == null ? '--' : isBest ? '🏆' : `${delta > 0 ? '+' : ''}${formatDuration(Math.abs(delta))}`}
</td>
)}
<td className="py-2 text-right text-gray-200">{formatPace(lap.avg_speed_ms, sportType)}</td>
<td className="py-2 text-right">
<span className="text-red-400">{formatHeartRate(lap.avg_heart_rate)}</span>
</td>
<td className="py-2 text-right text-gray-400">
{lap.avg_cadence ? formatCadence(lap.avg_cadence, sportType) : '--'}
</td>
{showPower && (
<td className="py-2 text-right text-gray-400">
{lap.avg_power ? `${Math.round(lap.avg_power)} W` : '--'}
</td>
)}
</tr>
)
})}
</tbody>
</table>
</div>
)
}