Add medals, HRV status dots, smooth segment hover, side-by-side map/timeline, HR zone times
- Silver/bronze medals (not just gold) on route & segment leaderboards - Colour HRV nightly-avg trend dots: orange unbalanced, red low - Project segment-hover dot smoothly along the track line (interpolated) - Show map and activity timeline side by side, half width each - Show time spent in each HR zone next to the percentage Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -649,7 +649,17 @@ function DailySnapshot({ day, snapshotWeight, avg30, intradayHr, bodyBattery, bb
|
||||
|
||||
// ── Trend Charts ────────────────────────────────────────────────────────────
|
||||
|
||||
function MetricChart({ data, dataKey, color, formatter, height = 140, selectedDate, onDayClick, connectNulls = false, showDots = false, domain, referenceLines }) {
|
||||
// Highlight problem days on a trend line by colouring the dot from a status field
|
||||
// (e.g. HRV status): orange = unbalanced, red = low/poor. Other days get no dot.
|
||||
const STATUS_DOT_COLORS = { unbalanced: '#f97316', low: '#ef4444', poor: '#ef4444' }
|
||||
const statusDot = (statusKey) => (props) => {
|
||||
const { cx, cy, payload } = props
|
||||
const color = STATUS_DOT_COLORS[String(payload?.[statusKey] || '').toLowerCase()]
|
||||
if (cx == null || cy == null || !color) return null
|
||||
return <circle cx={cx} cy={cy} r={3.5} fill={color} stroke="#111827" strokeWidth={1} />
|
||||
}
|
||||
|
||||
function MetricChart({ data, dataKey, color, formatter, height = 140, selectedDate, onDayClick, connectNulls = false, showDots = false, domain, referenceLines, statusDotKey }) {
|
||||
const vals = data.filter(d => d[dataKey] != null)
|
||||
if (!vals.length) return (
|
||||
<div className="flex items-center justify-center text-gray-600 text-xs" style={{ height }}>No data</div>
|
||||
@@ -686,7 +696,7 @@ function MetricChart({ data, dataKey, color, formatter, height = 140, selectedDa
|
||||
))}
|
||||
<Area type="monotone" dataKey={dataKey} stroke={color} strokeWidth={2}
|
||||
fill={`url(#grad-${dataKey})`}
|
||||
dot={showDots ? { fill: color, r: 3, strokeWidth: 0 } : false}
|
||||
dot={statusDotKey ? statusDot(statusDotKey) : (showDots ? { fill: color, r: 3, strokeWidth: 0 } : false)}
|
||||
connectNulls={connectNulls} isAnimationActive={false} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
@@ -1017,10 +1027,17 @@ export default function HealthPage() {
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-900 rounded-xl border border-gray-800 p-4">
|
||||
<h3 className="text-sm font-medium text-gray-300 mb-3">HRV (nightly avg)</h3>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-sm font-medium text-gray-300">HRV (nightly avg)</h3>
|
||||
<div className="flex items-center gap-3 text-xs text-gray-500">
|
||||
<span className="flex items-center gap-1"><span className="w-2 h-2 rounded-full" style={{ background: '#f97316' }} /> Unbalanced</span>
|
||||
<span className="flex items-center gap-1"><span className="w-2 h-2 rounded-full" style={{ background: '#ef4444' }} /> Low</span>
|
||||
</div>
|
||||
</div>
|
||||
<MetricChart data={metrics} dataKey="hrv_nightly_avg" color="#8b5cf6"
|
||||
formatter={v => `${Math.round(v)} ms`}
|
||||
selectedDate={selDateForCharts} onDayClick={handleDayClick}
|
||||
statusDotKey="hrv_status"
|
||||
referenceLines={[
|
||||
{ y: 20, stroke: '#f59e0b', strokeDasharray: '3 3', label: { value: 'Low', position: 'insideTopRight', fill: '#f59e0b', fontSize: 9 } },
|
||||
{ y: 60, stroke: '#22c55e', strokeDasharray: '3 3', label: { value: 'Good', position: 'insideTopRight', fill: '#22c55e', fontSize: 9 } },
|
||||
|
||||
Reference in New Issue
Block a user