/** * Today, laid out on a clock: every interval in its real place. * * Three categories (focus / short / long break) so a legend is mandatory, and * each block is also labelled on hover — colour never carries identity alone. */ import { Box, Stack, Tooltip, Typography } from "@mui/material"; import { KIND_LABEL } from "../../lib/format"; import { kindColor, type VizPalette } from "../../lib/viz"; import ChartFrame from "./ChartFrame"; interface Session { id: number; kind: string; status: string; started_at: string; minutes: number; task: string | null; } const LANE = 34; export default function SessionTimeline({ sessions, palette, }: { sessions: Session[]; palette: VizPalette; }) { const done = sessions.filter((item) => item.minutes > 0); if (!done.length) { return ( ); } const hourOf = (iso: string) => { const date = new Date(iso); return date.getHours() + date.getMinutes() / 60; }; const first = Math.floor(Math.min(...done.map((item) => hourOf(item.started_at)))); const last = Math.ceil( Math.max(...done.map((item) => hourOf(item.started_at) + item.minutes / 60)), ); const span = Math.max(last - first, 1); return ( {done.map((item) => { const start = (hourOf(item.started_at) - first) / span; const width = item.minutes / 60 / span; return ( {KIND_LABEL[item.kind] ?? item.kind} · {item.minutes} min {new Date(item.started_at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} {item.task ? ` · ${item.task}` : ""} {item.status === "abandoned" ? " · abandoned" : ""} } > ); })} {String(first).padStart(2, "0")}:00 {String(last).padStart(2, "0")}:00 ); }