Skip to content

Commit

Permalink
feat(SleepChartForWeek): add new chart to week stats page
Browse files Browse the repository at this point in the history
  • Loading branch information
benji6 committed Jan 6, 2024
1 parent 29d971b commit 90bbb71
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 12 deletions.
54 changes: 54 additions & 0 deletions client/src/components/pages/Stats/SleepChartForWeek.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Chart, Paper } from "eri";
import { WEEKDAY_LABELS_SHORT } from "../../../constants";
import { addDays } from "date-fns";
import eventsSlice from "../../../store/eventsSlice";
import { formatIsoDateInLocalTimezone } from "../../../utils";
import { formatMinutesAsTimeStringLong } from "../../../formatters/formatMinutesAsTimeString";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";

interface Props {
dateFrom: Date;
}

export default function SleepChartForWeek({ dateFrom }: Props) {
const navigate = useNavigate();
const minutesSleptByDateAwoke = useSelector(
eventsSlice.selectors.minutesSleptByDateAwoke,
);

const data: { dateString: string; minutesSlept: number; weekday: string }[] =
[];
for (let i = 0; i < WEEKDAY_LABELS_SHORT.length; i++) {
const dateString = formatIsoDateInLocalTimezone(addDays(dateFrom, i));
data.push({
dateString,
minutesSlept: minutesSleptByDateAwoke[dateString],
weekday: WEEKDAY_LABELS_SHORT[i],
});
}

if (data.every(({ minutesSlept }) => minutesSlept === undefined)) return null;

return (
<Paper>
<h3>Sleep chart</h3>
<Chart.ColumnChart
aria-label="Chart displaying sleep by day"
data={data.map(({ dateString, minutesSlept, weekday }) => ({
key: dateString,
label: weekday,
onClick: () => navigate(`/stats/days/${dateString}`),
title:
minutesSlept === undefined
? undefined
: formatMinutesAsTimeStringLong(minutesSlept),
y: minutesSlept === undefined ? undefined : minutesSlept / 60,
}))}
rotateXLabels
xAxisTitle="Day"
yAxisTitle="Hours slept"
/>
</Paper>
);
}
2 changes: 2 additions & 0 deletions client/src/components/pages/Stats/Week.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import MoodGradientForPeriod from "./MoodGradientForPeriod";
import MoodSummaryForWeek from "./MoodSummaryForWeek";
import PrevNextControls from "../../shared/PrevNextControls";
import { RootState } from "../../../store";
import SleepChartForWeek from "./SleepChartForWeek";
import { TIME } from "../../../constants";
import WeatherForPeriod from "./WeatherForPeriod";
import WeightChartForPeriod from "./WeightChartForPeriod";
Expand Down Expand Up @@ -118,6 +119,7 @@ function Week({ date, nextDate, prevDate, showNext, showPrevious }: Props) {
</p>
</Paper>
)}
<SleepChartForWeek dateFrom={date} />
<WeightChartForPeriod
centerXAxisLabels
dateFrom={date}
Expand Down
44 changes: 32 additions & 12 deletions client/src/store/eventsSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,19 @@ const dateToSelector = (
_dateFrom: Date,
dateTo: Date,
): Date => dateTo;
const denormalizedSleepsSelector = createSelector(
normalizedSleepsSelector,
denormalize,
);
const minutesSleptByDateAwokeSelector = createSelector(
denormalizedSleepsSelector,
(sleeps) => {
const sleepByDateAwoke = defaultDict(() => 0);
for (const { dateAwoke, minutesSlept } of sleeps)
sleepByDateAwoke[dateAwoke] += minutesSlept;
return { ...sleepByDateAwoke };
},
);

const moodsInPeriodResultFunction = (
{ allIds, byId }: NormalizedMoods,
Expand Down Expand Up @@ -385,7 +398,7 @@ export default createSlice({
denormalize,
),
denormalizedMoods: createSelector(normalizedMoodsSelector, denormalize),
denormalizedSleeps: createSelector(normalizedSleepsSelector, denormalize),
denormalizedSleeps: denormalizedSleepsSelector,
denormalizedWeights: createSelector(normalizedWeightsSelector, denormalize),
envelopingMoodIds: createSelector(
normalizedMoodsSelector,
Expand Down Expand Up @@ -421,22 +434,29 @@ export default createSlice({
normalizedStateNotEmpty,
),
meanDailySleepDurationInPeriod: createSelector(
normalizedSleepsSelector,
minutesSleptByDateAwokeSelector,
dateFromSelector,
dateToSelector,
({ allIds, byId }, dateFrom: Date, dateTo: Date): number | undefined => {
const sleepByDateAwoke = defaultDict(() => 0);
// TODO make this more performant
for (const id of allIds) {
const sleep = byId[id];
const { dateAwoke } = sleep;
const dateAwokeDate = new Date(dateAwoke);
if (dateAwokeDate < dateFrom || dateAwokeDate >= dateTo) continue;
sleepByDateAwoke[dateAwoke] += sleep.minutesSlept;
(
minutesSleptByDateAwoke,
dateFrom: Date,
dateTo: Date,
): number | undefined => {
const dateToString = formatIsoDateInLocalTimezone(dateTo);
const minutesSleptArray: number[] = [];
for (
let date = formatIsoDateInLocalTimezone(dateFrom);
date < dateToString;
date = formatIsoDateInLocalTimezone(addDays(date, 1))
) {
const minutesSlept = minutesSleptByDateAwoke[date];
if (minutesSlept === undefined) continue;
minutesSleptArray.push(minutesSlept);
}
return computeMeanSafe(Object.values(sleepByDateAwoke));
return computeMeanSafe(minutesSleptArray);
},
),
minutesSleptByDateAwoke: minutesSleptByDateAwokeSelector,
meanWeightInPeriod: createSelector(
normalizedWeightsSelector,
dateFromSelector,
Expand Down

0 comments on commit 90bbb71

Please sign in to comment.