HRV baseline band from Garmin + dashboard HRV colours + route name on recent activities + sync-now race fix
This commit is contained in:
@@ -8,6 +8,7 @@ import { format, subDays, differenceInCalendarDays, parseISO } from 'date-fns'
|
||||
import api from '../utils/api'
|
||||
import { formatSleep, sportIcon } from '../utils/format'
|
||||
import { BB_INFERRED_COLOR, BB_INFERRED_LABEL, bbLevelColor, inferBBType } from '../utils/bodyBattery'
|
||||
import HrvBadge from '../components/ui/HrvBadge'
|
||||
|
||||
const RANGES = [
|
||||
{ label: '1W', days: 7 },
|
||||
@@ -414,18 +415,6 @@ function SleepStageFallbackBar({ deepS, remS, lightS, awakeS }) {
|
||||
)
|
||||
}
|
||||
|
||||
function HrvBadge({ status }) {
|
||||
if (!status) return null
|
||||
const palette = {
|
||||
balanced: 'text-green-400 bg-green-400/10 border-green-400/30',
|
||||
unbalanced: 'text-yellow-400 bg-yellow-400/10 border-yellow-400/30',
|
||||
low: 'text-orange-400 bg-orange-400/10 border-orange-400/30',
|
||||
poor: 'text-red-400 bg-red-400/10 border-red-400/30',
|
||||
}
|
||||
const cls = palette[status.toLowerCase()] || 'text-gray-400 bg-gray-400/10 border-gray-400/30'
|
||||
return <span className={`text-xs px-2 py-0.5 rounded-full border ${cls}`}>{status}</span>
|
||||
}
|
||||
|
||||
function NavArrow({ onClick, disabled, children }) {
|
||||
return (
|
||||
<button
|
||||
@@ -688,15 +677,24 @@ const statusDot = (statusKey) => (props) => {
|
||||
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 }) {
|
||||
function MetricChart({ data, dataKey, color, formatter, height = 140, selectedDate, onDayClick, connectNulls = false, showDots = false, domain, referenceLines, statusDotKey, bandLowKey, bandHighKey, bandColor = '#9ca3af' }) {
|
||||
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>
|
||||
)
|
||||
// Range band (e.g. Garmin's HRV baseline): Recharts renders an Area as a band
|
||||
// when its dataKey resolves to a [low, high] pair.
|
||||
const hasBand = bandLowKey && bandHighKey
|
||||
const chartData = hasBand
|
||||
? data.map(d => ({
|
||||
...d,
|
||||
__band: (d[bandLowKey] != null && d[bandHighKey] != null) ? [d[bandLowKey], d[bandHighKey]] : null,
|
||||
}))
|
||||
: data
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={height}>
|
||||
<ComposedChart
|
||||
data={data}
|
||||
data={chartData}
|
||||
margin={{ top: 4, right: 4, bottom: 4, left: 0 }}
|
||||
style={{ cursor: onDayClick ? 'pointer' : 'default' }}
|
||||
onClick={evt => {
|
||||
@@ -716,7 +714,11 @@ function MetricChart({ data, dataKey, color, formatter, height = 140, selectedDa
|
||||
<YAxis tick={{ fontSize: 10, fill: '#6b7280' }} axisLine={false} tickLine={false} width={36}
|
||||
tickFormatter={formatter} domain={domain} />
|
||||
<Tooltip contentStyle={tooltipStyle} labelFormatter={d => format(new Date(d), 'MMM d, yyyy')}
|
||||
formatter={v => [formatter ? formatter(v) : v?.toFixed(1)]} />
|
||||
formatter={(v, name) => name === '__band' ? null : [formatter ? formatter(v) : v?.toFixed(1)]} />
|
||||
{hasBand && (
|
||||
<Area type="monotone" dataKey="__band" stroke="none" fill={bandColor} fillOpacity={0.18}
|
||||
connectNulls isAnimationActive={false} legendType="none" activeDot={false} />
|
||||
)}
|
||||
{selectedDate && (
|
||||
<ReferenceLine x={selectedDate} stroke="#60a5fa" strokeWidth={1.5} strokeDasharray="4 2" />
|
||||
)}
|
||||
@@ -1065,17 +1067,14 @@ export default function HealthPage() {
|
||||
<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: '#22c55e' }} /> Balanced</span>
|
||||
<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>
|
||||
<span className="flex items-center gap-1"><span className="w-3 h-2 rounded-sm" style={{ background: '#9ca3af', opacity: 0.5 }} /> Baseline</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 } },
|
||||
]}
|
||||
bandLowKey="hrv_baseline_low" bandHighKey="hrv_baseline_upper" bandColor="#9ca3af"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user