@@ -135,13 +169,25 @@ function BodyBatteryToday({ bb, hires, sleepStart, sleepEnd }) {
format(new Date(ts), 'HH:mm')}
interval={Math.max(1, Math.floor(data.length / 6))} />
-
+
format(new Date(ts), 'HH:mm')} formatter={v => [`${Math.round(v)}%`, 'Battery']} />
{data.map((d, i) => | )}
+ {dayActivities.map(a => {
+ const start = new Date(a.start_time).getTime()
+ const end = a.duration_s ? start + a.duration_s * 1000 : start
+ const x1 = nearestT(start), x2 = nearestT(end)
+ if (x1 == null || x2 == null) return null
+ const color = sportColor(a.sport_type)
+ return (
+ } />
+ )
+ })}
@@ -587,7 +633,7 @@ export default function DashboardPage() {
}
switch (id) {
case 'weekly': return
- case 'bodyBattery': return
+ case 'bodyBattery': return
case 'vo2maxTrend': return
case 'sleepDetail': return
case 'weight': return
diff --git a/frontend/src/pages/HealthPage.jsx b/frontend/src/pages/HealthPage.jsx
index b695ddb..24fc86d 100644
--- a/frontend/src/pages/HealthPage.jsx
+++ b/frontend/src/pages/HealthPage.jsx
@@ -6,7 +6,7 @@ import {
} from 'recharts'
import { format, subDays, differenceInCalendarDays, parseISO } from 'date-fns'
import api from '../utils/api'
-import { formatSleep, sportIcon } from '../utils/format'
+import { formatSleep, sportIcon, sportColor } from '../utils/format'
import { BB_INFERRED_COLOR, BB_INFERRED_LABEL, bbLevelColor, inferBBType } from '../utils/bodyBattery'
import HrvBadge from '../components/ui/HrvBadge'
import SleepHypnogram from '../components/health/SleepHypnogram'
@@ -186,14 +186,43 @@ function IntradayHrChart({ values }) {
function ActivityRefLabel({ viewBox, icon }) {
if (!viewBox) return null
- const { x, y, width = 0 } = viewBox
+ const { x, y, width = 0, height = 0 } = viewBox
return (
-
+
{icon}
)
}
+// Activity time spans are drawn as a solid coloured band in a reserved strip
+// *below* the battery bars (the negative Y region) so they don't obscure the data.
+const ACTIVITY_BAND_BOTTOM = -18
+
+function ActivityBands({ activities, nearestT, sportColor, sportIcon }) {
+ return (activities || []).map(a => {
+ const start = new Date(a.start_time).getTime()
+ const end = a.duration_s ? start + a.duration_s * 1000 : start
+ const x1 = nearestT(start), x2 = nearestT(end)
+ if (x1 == null || x2 == null) return null
+ const color = sportColor(a.sport_type)
+ return (
+ }
+ />
+ )
+ })
+}
+
function BodyBatteryChart({ bb, hiresValues, sleepStart, sleepEnd, activities }) {
if (!bb) return null
const { charged, drained, start_level, end_level } = bb
@@ -225,6 +254,8 @@ function BodyBatteryChart({ bb, hiresValues, sleepStart, sleepEnd, activities })
return best
}
+ const hasActivities = (activities || []).length > 0
+
return (
Body Battery
@@ -250,7 +281,8 @@ function BodyBatteryChart({ bb, hiresValues, sleepStart, sleepEnd, activities })
format(new Date(ts), 'HH:mm')}
interval={Math.max(1, Math.floor(chartData.length / 6))} />
- v} ticks={[0, 25, 50, 75, 100]} />
format(new Date(ts), 'HH:mm')}
@@ -260,23 +292,7 @@ function BodyBatteryChart({ bb, hiresValues, sleepStart, sleepEnd, activities })
|
))}
- {(activities || []).map(a => {
- const start = new Date(a.start_time).getTime()
- const end = a.duration_s ? start + a.duration_s * 1000 : start
- const x1 = nearestT(start), x2 = nearestT(end)
- if (x1 == null || x2 == null) return null
- return (
- }
- />
- )
- })}
+