Segments and Av HR update
This commit is contained in:
@@ -153,16 +153,71 @@ function BodyBatteryChart({ bb, hiresValues }) {
|
||||
)
|
||||
}
|
||||
|
||||
function SleepStagesBar({ deep, light, rem, awake }) {
|
||||
const total = (deep || 0) + (light || 0) + (rem || 0) + (awake || 0)
|
||||
if (!total) return null
|
||||
const pct = s => `${((s || 0) / total * 100).toFixed(1)}%`
|
||||
// Sleep timeline bar spanning from sleep_start to sleep_end with proportional stage coloring
|
||||
function SleepTimeline({ sleepStart, sleepEnd, deep, light, rem, awake }) {
|
||||
if (!sleepStart || !sleepEnd) return null
|
||||
const stageSecs = (deep || 0) + (light || 0) + (rem || 0) + (awake || 0)
|
||||
if (!stageSecs) return null
|
||||
|
||||
const startMs = new Date(sleepStart).getTime()
|
||||
const endMs = new Date(sleepEnd).getTime()
|
||||
const windowMs = endMs - startMs
|
||||
if (windowMs <= 0) return null
|
||||
|
||||
// Build stage segments proportional to duration, but rendered across the sleep window
|
||||
const stages = [
|
||||
{ key: 'deep', secs: deep || 0, color: '#6366f1', label: 'Deep' },
|
||||
{ key: 'rem', secs: rem || 0, color: '#8b5cf6', label: 'REM' },
|
||||
{ key: 'light', secs: light || 0, color: '#a78bfa', label: 'Light' },
|
||||
{ key: 'awake', secs: awake || 0, color: '#374151', label: 'Awake' },
|
||||
].filter(s => s.secs > 0)
|
||||
|
||||
// Generate hour tick marks within the sleep window
|
||||
const startHour = new Date(startMs)
|
||||
startHour.setMinutes(0, 0, 0)
|
||||
startHour.setHours(startHour.getHours() + 1)
|
||||
const ticks = []
|
||||
let tick = startHour.getTime()
|
||||
while (tick < endMs) {
|
||||
const pct = Math.min(100, Math.max(0, (tick - startMs) / windowMs * 100))
|
||||
ticks.push({ pct, label: new Date(tick).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' }) })
|
||||
tick += 3600000
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex rounded-full overflow-hidden h-2.5 w-full">
|
||||
<div style={{ width: pct(deep), backgroundColor: '#6366f1' }} />
|
||||
<div style={{ width: pct(rem), backgroundColor: '#8b5cf6' }} />
|
||||
<div style={{ width: pct(light), backgroundColor: '#a78bfa' }} />
|
||||
<div style={{ width: pct(awake), backgroundColor: '#374151' }} />
|
||||
<div className="space-y-1.5">
|
||||
{/* Time bar */}
|
||||
<div className="relative">
|
||||
<div className="flex rounded-md overflow-hidden h-5 w-full">
|
||||
{stages.map((s, i) => (
|
||||
<div
|
||||
key={s.key}
|
||||
style={{ width: `${(s.secs / stageSecs * 100).toFixed(2)}%`, backgroundColor: s.color }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{/* Tick marks */}
|
||||
{ticks.map((t, i) => (
|
||||
<div key={i} className="absolute top-0 h-5 flex flex-col items-center pointer-events-none" style={{ left: `${t.pct}%` }}>
|
||||
<div className="w-px h-full bg-black/40" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Time labels */}
|
||||
<div className="relative h-4">
|
||||
<span className="absolute left-0 text-xs text-gray-500" style={{ transform: 'translateX(-0%)' }}>
|
||||
{new Date(startMs).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}
|
||||
</span>
|
||||
{ticks.map((t, i) => (
|
||||
<span key={i} className="absolute text-xs text-gray-600"
|
||||
style={{ left: `${t.pct}%`, transform: 'translateX(-50%)' }}>
|
||||
{t.label}
|
||||
</span>
|
||||
))}
|
||||
<span className="absolute right-0 text-xs text-gray-500">
|
||||
{new Date(endMs).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -253,11 +308,12 @@ function DailySnapshot({ day, avg30, intradayHr, bodyBattery, bbHires, onOlder,
|
||||
</div>
|
||||
{hasSleepStages ? (
|
||||
<>
|
||||
<SleepStagesBar
|
||||
<SleepTimeline
|
||||
sleepStart={day.sleep_start} sleepEnd={day.sleep_end}
|
||||
deep={day.sleep_deep_s} light={day.sleep_light_s}
|
||||
rem={day.sleep_rem_s} awake={day.sleep_awake_s}
|
||||
/>
|
||||
<div className="flex flex-wrap gap-x-5 gap-y-1.5">
|
||||
<div className="flex flex-wrap gap-x-5 gap-y-1.5 mt-1">
|
||||
{[
|
||||
['Deep', day.sleep_deep_s, '#6366f1'],
|
||||
['REM', day.sleep_rem_s, '#8b5cf6'],
|
||||
|
||||
Reference in New Issue
Block a user