Implemented all 9 UI fixes across health charts and activity detail pages. Changes are ready to push to git for the Docker build to pick them up.
This commit is contained in:
@@ -101,26 +101,28 @@ export default function ActivityDetailPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Primary stats */}
|
||||
<div className="grid grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
{/* Stats — all on one row */}
|
||||
<div className="grid grid-cols-5 lg:grid-cols-10 gap-3">
|
||||
<StatCard label="Distance" value={formatDistance(activity.distance_m)} />
|
||||
<StatCard label="Time" value={formatDuration(activity.duration_s)} />
|
||||
<StatCard label="Pace" value={formatPace(activity.avg_speed_ms, activity.sport_type)} />
|
||||
<StatCard label="Elevation ↑" value={formatElevation(activity.elevation_gain_m)} />
|
||||
<StatCard label="Avg HR" value={formatHeartRate(activity.avg_heart_rate)} accent="red" />
|
||||
<StatCard label="Calories" value={activity.calories ? `${Math.round(activity.calories)} kcal` : '--'} />
|
||||
</div>
|
||||
|
||||
{/* Secondary stats */}
|
||||
<div className="grid grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
<StatCard label="Max HR" value={formatHeartRate(activity.max_heart_rate)} />
|
||||
<StatCard label="Elevation ↓" value={formatElevation(activity.elevation_loss_m)} />
|
||||
<StatCard label="Cadence" value={formatCadence(activity.avg_cadence, activity.sport_type)} />
|
||||
<StatCard label="Avg Power" value={activity.avg_power ? `${Math.round(activity.avg_power)} W` : '--'} />
|
||||
<StatCard label="NP" value={activity.normalized_power ? `${Math.round(activity.normalized_power)} W` : '--'} />
|
||||
<StatCard label="Avg Temp" value={activity.avg_temperature_c ? `${activity.avg_temperature_c.toFixed(1)} °C` : '--'} />
|
||||
</div>
|
||||
|
||||
{/* HR Zones */}
|
||||
{activity.hr_zones && Object.values(activity.hr_zones).some(v => v > 0) && (
|
||||
<div className="bg-gray-900 rounded-xl border border-gray-800 p-4">
|
||||
<h3 className="text-sm font-medium text-gray-300 mb-3">Heart Rate Zones</h3>
|
||||
<HRZoneBar zones={activity.hr_zones} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Map with controls */}
|
||||
<div className="bg-gray-900 rounded-xl overflow-hidden border border-gray-800">
|
||||
{/* Map toolbar */}
|
||||
@@ -165,14 +167,6 @@ export default function ActivityDetailPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* HR Zones */}
|
||||
{activity.hr_zones && Object.values(activity.hr_zones).some(v => v > 0) && (
|
||||
<div className="bg-gray-900 rounded-xl border border-gray-800 p-4">
|
||||
<h3 className="text-sm font-medium text-gray-300 mb-3">Heart Rate Zones</h3>
|
||||
<HRZoneBar zones={activity.hr_zones} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Metric timeline */}
|
||||
<div className="bg-gray-900 rounded-xl border border-gray-800 p-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
@@ -207,52 +201,53 @@ export default function ActivityDetailPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Laps */}
|
||||
{laps && laps.length > 0 && (
|
||||
<div className="bg-gray-900 rounded-xl border border-gray-800 p-4">
|
||||
<h3 className="text-sm font-medium text-gray-300 mb-3">Laps</h3>
|
||||
<LapTable laps={laps} sportType={activity.sport_type} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Segments */}
|
||||
{segments && segments.length > 0 && dataPoints && (
|
||||
<div className="bg-gray-900 rounded-xl border border-gray-800 p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-sm font-medium text-gray-300">Segments</h3>
|
||||
<Link to="/segments" className="text-xs text-blue-400 hover:underline">Manage →</Link>
|
||||
</div>
|
||||
{/* Column headers */}
|
||||
<div className="flex items-center gap-3 pb-1.5 border-b border-gray-800 mb-1">
|
||||
<span className="flex-1 text-xs text-gray-600 uppercase tracking-wide">Segment</span>
|
||||
<span className="font-mono text-xs w-14 text-right text-gray-600 uppercase tracking-wide">This run</span>
|
||||
<span className="font-mono text-xs w-14 text-right text-gray-600 uppercase tracking-wide">Best</span>
|
||||
<span className="font-mono text-xs w-14 text-right text-gray-600 uppercase tracking-wide">Δ</span>
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
{segments.map(seg => {
|
||||
const t = segmentTime(dataPoints, seg.start_distance_m, seg.end_distance_m)
|
||||
const best = segmentBests?.find(b => b.segment_id === seg.id)
|
||||
const isNewBest = t != null && best?.best_s != null && t <= best.best_s + 0.5
|
||||
const delta = t != null && best?.best_s != null ? t - best.best_s : null
|
||||
return (
|
||||
<div key={seg.id} className="flex items-center gap-3 py-1.5 border-b border-gray-800/40 text-sm">
|
||||
<span className="flex-1 text-gray-300 text-xs truncate">{seg.name}</span>
|
||||
<span className={`font-mono text-xs w-14 text-right ${isNewBest ? 'text-yellow-400 font-semibold' : 'text-gray-200'}`}>
|
||||
{t != null ? formatDuration(t) : <span className="text-gray-700">--</span>}
|
||||
</span>
|
||||
<span className="font-mono text-xs w-14 text-right text-gray-500">
|
||||
{best?.best_s != null ? formatDuration(best.best_s) : '--'}
|
||||
</span>
|
||||
<span className={`font-mono text-xs w-14 text-right ${
|
||||
isNewBest ? 'text-yellow-400' : delta == null ? 'text-gray-700' : delta <= 0 ? 'text-green-400' : 'text-red-400'
|
||||
}`}>
|
||||
{isNewBest ? '🏆' : delta == null ? '--' : `${delta > 0 ? '+' : ''}${formatDuration(Math.abs(delta))}`}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{/* Laps + Segments side by side */}
|
||||
{((laps && laps.length > 0) || (segments && segments.length > 0 && dataPoints)) && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
{laps && laps.length > 0 && (
|
||||
<div className="bg-gray-900 rounded-xl border border-gray-800 p-4">
|
||||
<h3 className="text-sm font-medium text-gray-300 mb-3">Laps</h3>
|
||||
<LapTable laps={laps} sportType={activity.sport_type} />
|
||||
</div>
|
||||
)}
|
||||
{segments && segments.length > 0 && dataPoints && (
|
||||
<div className="bg-gray-900 rounded-xl border border-gray-800 p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-sm font-medium text-gray-300">Segments</h3>
|
||||
<Link to="/segments" className="text-xs text-blue-400 hover:underline">Manage →</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 pb-1.5 border-b border-gray-800 mb-1">
|
||||
<span className="flex-1 text-xs text-gray-600 uppercase tracking-wide">Segment</span>
|
||||
<span className="font-mono text-xs w-14 text-right text-gray-600 uppercase tracking-wide">This run</span>
|
||||
<span className="font-mono text-xs w-14 text-right text-gray-600 uppercase tracking-wide">Best</span>
|
||||
<span className="font-mono text-xs w-14 text-right text-gray-600 uppercase tracking-wide">Δ</span>
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
{segments.map(seg => {
|
||||
const t = segmentTime(dataPoints, seg.start_distance_m, seg.end_distance_m)
|
||||
const best = segmentBests?.find(b => b.segment_id === seg.id)
|
||||
const isNewBest = t != null && best?.best_s != null && t <= best.best_s + 0.5
|
||||
const delta = t != null && best?.best_s != null ? t - best.best_s : null
|
||||
return (
|
||||
<div key={seg.id} className="flex items-center gap-3 py-1.5 border-b border-gray-800/40 text-sm">
|
||||
<span className="flex-1 text-gray-300 text-xs truncate">{seg.name}</span>
|
||||
<span className={`font-mono text-xs w-14 text-right ${isNewBest ? 'text-yellow-400 font-semibold' : 'text-gray-200'}`}>
|
||||
{t != null ? formatDuration(t) : <span className="text-gray-700">--</span>}
|
||||
</span>
|
||||
<span className="font-mono text-xs w-14 text-right text-gray-500">
|
||||
{best?.best_s != null ? formatDuration(best.best_s) : '--'}
|
||||
</span>
|
||||
<span className={`font-mono text-xs w-14 text-right ${
|
||||
isNewBest ? 'text-yellow-400' : delta == null ? 'text-gray-700' : delta <= 0 ? 'text-green-400' : 'text-red-400'
|
||||
}`}>
|
||||
{isNewBest ? '🏆' : delta == null ? '--' : `${delta > 0 ? '+' : ''}${formatDuration(Math.abs(delta))}`}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user