Body
Build and push images / validate (push) Successful in 19s
Build and push images / build-backend (push) Successful in 1m15s
Build and push images / build-worker (push) Successful in 1m13s
Build and push images / build-frontend (push) Successful in 51s

This commit is contained in:
2026-06-07 15:26:54 +01:00
parent 568dc31e97
commit da9c1e04cb
15 changed files with 104 additions and 15 deletions
@@ -100,7 +100,7 @@ export default function MetricTimeline({ dataPoints, activeMetrics, metrics, onH
{metric.unit && <span className="text-xs text-gray-600">({metric.unit})</span>}
</div>
<ResponsiveContainer width="100%" height={100}>
<ComposedChart data={chartData} margin={{ top: 2, right: 8, bottom: 2, left: 8 }}>
<ComposedChart data={chartData} margin={{ top: 2, right: 8, bottom: 2, left: 8 }} syncId="activity-metrics">
<CartesianGrid strokeDasharray="3 3" stroke="#1f2937" vertical={false} />
<XAxis
dataKey="distance_m"
+24 -10
View File
@@ -71,19 +71,31 @@ function bbLevelColor(level) {
return '#ef4444'
}
function BodyBatteryChart({ bb }) {
function BodyBatteryChart({ bb, hiresValues }) {
if (!bb) return null
const { charged, drained, start_level, end_level, values } = bb
if (!values?.length && end_level == null) return null
const chartData = (values || []).map(([ts, level, type]) => ({
t: ts,
level,
type: type ?? 4,
bar: 100,
// Background bars use the raw checkpoint type codes to colour activity segments.
const bgData = (values || []).map(([ts, , type]) => ({ t: ts, type: type ?? 4, bar: 100 }))
// Line uses hi-res data when available, otherwise the raw checkpoints.
const lineData = hiresValues?.length
? hiresValues.map(([ts, level]) => ({ t: ts, level }))
: (values || []).map(([ts, level]) => ({ t: ts, level }))
// Merge into a single dataset keyed by timestamp so both series share the same XAxis.
const tsSet = new Set([...bgData.map(d => d.t), ...lineData.map(d => d.t)])
const bgMap = Object.fromEntries(bgData.map(d => [d.t, d]))
const lineMap = Object.fromEntries(lineData.map(d => [d.t, d]))
const chartData = [...tsSet].sort((a, b) => a - b).map(t => ({
t,
bar: bgMap[t]?.bar ?? null,
type: bgMap[t]?.type ?? null,
level: lineMap[t]?.level ?? null,
}))
const presentTypes = [...new Set(chartData.map(d => d.type))]
const presentTypes = [...new Set(bgData.map(d => d.type))]
const levelColor = bbLevelColor(end_level)
return (
@@ -113,12 +125,13 @@ function BodyBatteryChart({ bb }) {
<XAxis dataKey="t" tick={{ fontSize: 9, fill: '#6b7280' }} axisLine={false} tickLine={false}
tickFormatter={ts => format(new Date(ts), 'HH:mm')}
interval={Math.max(1, Math.floor(chartData.length / 6))} />
<YAxis domain={[0, 100]} hide />
<Tooltip contentStyle={tooltipStyle}
labelFormatter={ts => format(new Date(ts), 'HH:mm')}
formatter={(v, name) => name === 'level' ? [`${Math.round(v)}`, 'Battery'] : null} />
<Bar dataKey="bar" isAnimationActive={false} maxBarSize={6}>
{chartData.map((d, i) => (
<Cell key={i} fill={BB_TYPE_COLOR[d.type] ?? '#374151'} fillOpacity={0.8} />
<Cell key={i} fill={d.type != null ? (BB_TYPE_COLOR[d.type] ?? '#374151') : 'transparent'} fillOpacity={0.8} />
))}
</Bar>
<Line type="monotone" dataKey="level" stroke="#e5e7eb" strokeWidth={1.5}
@@ -180,7 +193,7 @@ function NavArrow({ onClick, disabled, children }) {
)
}
function DailySnapshot({ day, avg30, intradayHr, bodyBattery, onOlder, onNewer, hasOlder, hasNewer }) {
function DailySnapshot({ day, avg30, intradayHr, bodyBattery, bbHires, onOlder, onNewer, hasOlder, hasNewer }) {
if (!day) return (
<div className="text-center py-10 text-gray-600">
<p className="text-3xl mb-2">📊</p>
@@ -334,7 +347,7 @@ function DailySnapshot({ day, avg30, intradayHr, bodyBattery, onOlder, onNewer,
</div>
</div>
)}
<BodyBatteryChart bb={bodyBattery} />
<BodyBatteryChart bb={bodyBattery} hiresValues={bbHires} />
</div>
)}
@@ -580,6 +593,7 @@ export default function HealthPage() {
avg30={summary?.avg_30d}
intradayHr={intradayData?.hr_values}
bodyBattery={intradayData?.body_battery}
bbHires={intradayData?.body_battery_hires}
onOlder={goOlder}
onNewer={goNewer}
hasOlder={selectedIdx >= 0 && selectedIdx < allDaysSorted.length - 1}