init
This commit is contained in:
121 files changed
+17641
No files matched your search
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Pomodoros per day — magnitude over time, so: bars, one hue, baseline at zero.
|
||||
*
|
||||
* The goal line is the only reference mark; days that cleared it are the darker
|
||||
* ramp step, which keeps "did I hit the goal" readable without a second colour.
|
||||
*/
|
||||
import { Box, Tooltip, Typography, useTheme } from "@mui/material";
|
||||
|
||||
import { shortDate } from "../../lib/format";
|
||||
import type { VizPalette } from "../../lib/viz";
|
||||
import ChartFrame from "./ChartFrame";
|
||||
|
||||
interface Day {
|
||||
date: string;
|
||||
pomodoros: number;
|
||||
focus_minutes: number;
|
||||
goal_met: boolean;
|
||||
}
|
||||
|
||||
const HEIGHT = 160;
|
||||
|
||||
export default function FocusBars({
|
||||
days,
|
||||
goal,
|
||||
palette,
|
||||
title = "Pomodoros per day",
|
||||
}: {
|
||||
days: Day[];
|
||||
goal: number;
|
||||
palette: VizPalette;
|
||||
title?: string;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const max = Math.max(goal, ...days.map((day) => day.pomodoros), 1);
|
||||
const goalY = HEIGHT - (goal / max) * HEIGHT;
|
||||
const slotWidth = 100 / Math.max(days.length, 1);
|
||||
|
||||
return (
|
||||
<ChartFrame
|
||||
title={title}
|
||||
caption={`Last ${days.length} days · goal ${goal}/day`}
|
||||
>
|
||||
<Box sx={{ position: "relative", height: HEIGHT, display: "flex", alignItems: "flex-end", gap: "2px" }}>
|
||||
{goal > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: `${goalY}px`,
|
||||
borderTop: `1px dashed ${palette.grid}`,
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{ position: "absolute", right: 0, top: -18, color: "text.secondary" }}
|
||||
>
|
||||
goal
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{days.map((day) => {
|
||||
const height = (day.pomodoros / max) * HEIGHT;
|
||||
return (
|
||||
<Tooltip
|
||||
key={day.date}
|
||||
title={
|
||||
<Box>
|
||||
<Typography variant="caption" sx={{ display: "block", fontWeight: 600 }}>
|
||||
{day.date}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ display: "block" }}>
|
||||
{day.pomodoros} pomodoros · {day.focus_minutes} min
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
flex: `0 0 calc(${slotWidth}% - 2px)`,
|
||||
height: Math.max(height, day.pomodoros > 0 ? 4 : 2),
|
||||
minHeight: 2,
|
||||
borderRadius: "4px 4px 0 0",
|
||||
bgcolor:
|
||||
day.pomodoros === 0
|
||||
? theme.palette.action.disabledBackground
|
||||
: day.goal_met
|
||||
? palette.sequential[4]
|
||||
: palette.sequential[2],
|
||||
transition: "height .3s",
|
||||
cursor: "default",
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", mt: 0.5 }}>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{days.length ? shortDate(days[0].date) : ""}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{days.length ? shortDate(days[days.length - 1].date) : ""}
|
||||
</Typography>
|
||||
</Box>
|
||||
</ChartFrame>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user