Skip to content

Commit

Permalink
feat(StatsViewControls): only show meditations view button if there a…
Browse files Browse the repository at this point in the history
…re meditations in the period
  • Loading branch information
benji6 committed Sep 13, 2024
1 parent ffd8e92 commit 296c01b
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 17 deletions.
6 changes: 5 additions & 1 deletion client/src/components/pages/Stats/Month/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,11 @@ function Month({ date, nextDate, prevDate, showNext, showPrevious }: Props) {
)}
</PrevNextControls>
</Paper>
<StatsViewControls onActiveViewChange={setActiveView} />
<StatsViewControls
dateFrom={date}
dateTo={nextDate}
onActiveViewChange={setActiveView}
/>
{view}
</Paper.Group>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ export default function MeditationByMonthForPeriod({
dateFrom,
dateTo,
}: Props) {
const meditationIdsInPeriod = useSelector((state: RootState) =>
eventsSlice.selectors.meditationIdsInPeriod(state, dateFrom, dateTo),
const hasMeditationsInPeriod = useSelector((state: RootState) =>
eventsSlice.selectors.hasMeditationsInPeriod(state, dateFrom, dateTo),
);
const noramlizedTotalSecondsMeditatedByMonth = useSelector(
eventsSlice.selectors.normalizedTotalSecondsMeditatedByMonth,
);
const navigate = useNavigate();

if (!meditationIdsInPeriod.length) return null;
if (!hasMeditationsInPeriod) return null;

const months = eachMonthOfInterval({ start: dateFrom, end: dateTo }).slice(
0,
Expand Down
6 changes: 5 additions & 1 deletion client/src/components/pages/Stats/Year/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,11 @@ function Year({ date, nextDate, prevDate, showNext, showPrevious }: Props) {
)}
</PrevNextControls>
</Paper>
<StatsViewControls onActiveViewChange={setActiveView} />
<StatsViewControls
dateFrom={date}
dateTo={nextDate}
onActiveViewChange={setActiveView}
/>
{view}
</Paper.Group>
);
Expand Down
36 changes: 25 additions & 11 deletions client/src/components/shared/StatsViewControls/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Button, Icon, Paper } from "eri";
import { useEffect, useState } from "react";
import EventIcon from "../EventIcon";
import { RootState } from "../../../store";
import { capitalizeFirstLetter } from "../../../utils";
import eventsSlice from "../../../store/eventsSlice";
import { useSelector } from "react-redux";

export type ActiveView =
| "location"
Expand All @@ -12,11 +15,20 @@ export type ActiveView =
| "weight";

interface Props {
dateFrom: Date;
dateTo: Date;
onActiveViewChange: (activeView: ActiveView) => void;
}

export default function StatsViewControls({ onActiveViewChange }: Props) {
export default function StatsViewControls({
dateFrom,
dateTo,
onActiveViewChange,
}: Props) {
const [activeView, setActiveView] = useState<ActiveView>("mood");
const hasMeditationsInPeriod = useSelector((state: RootState) =>
eventsSlice.selectors.hasMeditationsInPeriod(state, dateFrom, dateTo),
);

useEffect(
() => onActiveViewChange(activeView),
Expand Down Expand Up @@ -54,16 +66,18 @@ export default function StatsViewControls({ onActiveViewChange }: Props) {
icon: <EventIcon eventType="weights" margin="end" />,
},
] as const
).map(({ icon, view }) => (
<Button
key={view}
onClick={() => setActiveView(view)}
variant={activeView === view ? "primary" : "secondary"}
>
{icon}
{capitalizeFirstLetter(view)}
</Button>
))}
)
.filter(({ view }) => hasMeditationsInPeriod || view !== "meditation")
.map(({ icon, view }) => (
<Button
key={view}
onClick={() => setActiveView(view)}
variant={activeView === view ? "primary" : "secondary"}
>
{icon}
{capitalizeFirstLetter(view)}
</Button>
))}
</div>
</Paper>
);
Expand Down
8 changes: 8 additions & 0 deletions client/src/store/eventsSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
getEnvelopingIds,
getIdsInInterval,
getNormalizedWordCloudWords,
hasIdsInInterval,
} from "../utils";
import { MINIMUM_WORD_CLOUD_WORDS } from "../constants";
import { WEEK_OPTIONS } from "../formatters/dateTimeFormatters";
Expand Down Expand Up @@ -529,6 +530,13 @@ export default createSlice({
normalizedMeditationsSelector,
normalizedStateNotEmpty,
),
hasMeditationsInPeriod: createSelector(
normalizedMeditationsSelector,
dateFromSelector,
dateToSelector,
({ allIds }, dateFrom: Date, dateTo: Date) =>
hasIdsInInterval(allIds, dateFrom, dateTo),
),
hasPushUps: createSelector(
normalizedPushUpsSelector,
normalizedStateNotEmpty,
Expand Down
65 changes: 65 additions & 0 deletions client/src/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
getNormalizedWordCloudWords,
getWeatherDisplayData,
getWeekdayIndex,
hasIdsInInterval,
mapRight,
moodToColor,
roundDateDown,
Expand Down Expand Up @@ -669,6 +670,70 @@ describe("utils", () => {
});
});

describe("hasIdsInInterval", () => {
it("throws an error when the dateFrom is after the dateTo", () => {
expect(() =>
hasIdsInInterval(
[],
new Date("2020-09-01T00:00:00"),
new Date("2020-09-01T00:00:00"),
),
).not.toThrow();
expect(() =>
hasIdsInInterval(
[],
new Date("2020-09-01T00:00:01"),
new Date("2020-09-01T00:00:00"),
),
).toThrow(Error("`dateFrom` should not be after `dateTo`"));
});

it("returns false when there are no mood IDs provided", () => {
expect(
hasIdsInInterval(
[],
new Date("2020-09-02T00:00:00"),
new Date("2020-09-03T00:00:00"),
),
).toBe(false);
});

it("returns false when there are no moods within the interval", () => {
expect(
hasIdsInInterval(
["2020-09-01T23:59:59Z", "2020-09-03T00:00:01Z"],
new Date("2020-09-02T00:00:00"),
new Date("2020-09-03T00:00:00"),
),
).toBe(false);
});

it("returns true when all moods are within the interval", () => {
expect(
hasIdsInInterval(
["2020-09-02T00:00:00Z", "2020-09-03T00:00:00Z"],
new Date("2020-09-02T00:00:00"),
new Date("2020-09-03T00:00:00"),
),
).toBe(true);
});

it("returns true when some moods are within the interval", () => {
expect(
hasIdsInInterval(
[
"2020-09-01T23:59:59Z",
"2020-09-02T00:00:00Z",
"2020-09-03T00:00:00Z",
"2020-09-03T00:00:01Z",
],
new Date("2020-09-02T00:00:00"),
new Date("2020-09-03T00:00:00"),
),
).toBe(true);
});
});

test.each(
[
200, 232, 300, 321, 500, 531, 600, 622, 701, 762, 771, 781, 800, 801, 802,
Expand Down
14 changes: 13 additions & 1 deletion client/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,23 @@ export const getIdsInInterval = (
const fromIso = dateFrom.toISOString();
const toIso = dateTo.toISOString();
const i = bisectLeft(ids, fromIso);
const j = bisectLeft(ids, toIso, i);
if (toIso < ids[i]) return [];
const j = bisectLeft(ids, toIso, i);
return ids.slice(i, j + 1);
};

export const hasIdsInInterval = (
ids: string[],
dateFrom: Date,
dateTo: Date,
): boolean => {
if (dateFrom > dateTo) throw Error("`dateFrom` should not be after `dateTo`");
const fromIso = dateFrom.toISOString();
const toIso = dateTo.toISOString();
const i = bisectLeft(ids, fromIso);
return toIso >= ids[i];
};

export const getWeatherDisplayData = ({
isDaytime,
weatherId,
Expand Down

0 comments on commit 296c01b

Please sign in to comment.