Skip to content

Commit

Permalink
vinvoor: show current streak days
Browse files Browse the repository at this point in the history
  • Loading branch information
Topvennie committed Jul 6, 2024
1 parent 7c0519b commit ed6d70a
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 31 deletions.
1 change: 1 addition & 0 deletions vinvoor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"mdi-material-ui": "^7.9.1",
"notistack": "^3.0.1",
"react": "^18.2.0",
"react-device-detect": "^2.2.3",
"react-dom": "^18.2.0",
"react-router-dom": "^6.23.1",
"react-router-hash-link": "^2.4.3",
Expand Down
46 changes: 29 additions & 17 deletions vinvoor/src/overview/Overview.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Box, Paper, Switch, Typography } from "@mui/material";
import Grid from "@mui/material/Grid";
import { createContext, useState } from "react";
import { BrowserView } from "react-device-detect";
import { Tooltip } from "react-tooltip";
import { LoadingSkeleton } from "../components/LoadingSkeleton";
import { useFetch } from "../hooks/useFetch";
import { convertScanJSON, Scan } from "../types/scans";
import { CheckIn } from "./checkin/CheckIn";
import { Heatmap, HeatmapVariant } from "./heatmap/Heatmap";
import { Streak } from "./streak/Streak";

interface ScanContextProps {
scans: readonly Scan[];
Expand All @@ -23,7 +25,7 @@ export const Overview = () => {
setScans,
convertScanJSON
);
const [checked, setChecked] = useState<boolean>(true);
const [checked, setChecked] = useState<boolean>(false);

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setChecked(event.target.checked);
Expand All @@ -32,25 +34,35 @@ export const Overview = () => {
return (
<LoadingSkeleton loading={loading}>
<ScanContext.Provider value={{ scans }}>
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<Grid
container
spacing={2}
alignItems="center"
justifyContent="space-around"
>
<Grid item xs={8} md={6}>
<CheckIn />
</Grid>
<Grid item xs={12} md={8}>
<Grid item xs={4}>
<Streak />
</Grid>
<Grid item xs={12}>
<Paper elevation={4} sx={{ padding: 2 }}>
<Box
sx={{
display: "flex",
justifyContent: "right",
}}
>
<Typography variant="h6">Months</Typography>
<Switch
checked={checked}
onChange={handleChange}
/>
<Typography variant="h6">Days</Typography>
</Box>
<BrowserView>
<Box
sx={{
display: "flex",
justifyContent: "right",
}}
>
<Typography variant="h6">Months</Typography>
<Switch
checked={checked}
onChange={handleChange}
/>
<Typography variant="h6">Days</Typography>
</Box>
</BrowserView>
<Heatmap
startDate={new Date("2024-01-01")}
endDate={new Date("2024-12-31")}
Expand Down
6 changes: 1 addition & 5 deletions vinvoor/src/overview/checkin/CheckIn.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { Alert, AlertTitle } from "@mui/material";
import { EmoticonExcitedOutline, EmoticonFrownOutline } from "mdi-material-ui";
import { useContext } from "react";
import { isTheSameDay } from "../../util/util";
import { ScanContext } from "../Overview";

const isTheSameDay = (date1: Date, date2: Date) =>
date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate();

export const CheckIn = () => {
const { scans } = useContext(ScanContext);

Expand Down
3 changes: 1 addition & 2 deletions vinvoor/src/overview/heatmap/Heatmap.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FC, useContext } from "react";
import { MILLISECONDS_IN_ONE_DAY, shiftDate } from "../../util/util";
import { ScanContext } from "../Overview";
import "./heatmap.css";
import {
Expand All @@ -13,9 +14,7 @@ import {
getTransformForColumn,
getTransformForMonthLabels,
getWidth,
MILLISECONDS_IN_ONE_DAY,
MONTH_LABELS,
shiftDate,
SQUARE_SIZE,
} from "./utils";

Expand Down
8 changes: 1 addition & 7 deletions vinvoor/src/overview/heatmap/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Exports

import { MILLISECONDS_IN_ONE_DAY } from "../../util/util";
import { HeatmapVariant } from "./Heatmap";

// Constants

export const MILLISECONDS_IN_ONE_DAY = 24 * 60 * 60 * 1000;
export const DAYS_IN_WEEK = 7;
export const WEEKS_IN_MONTH = 5;
export const SQUARE_SIZE = 10;
Expand Down Expand Up @@ -93,12 +93,6 @@ export const getColumnCount = (
}
};

export const shiftDate = (date: Date, numDays: number) => {
const newDate = new Date(date);
newDate.setDate(newDate.getDate() + numDays);
return newDate;
};

// Local functions

const GUTTERSIZE = 5;
Expand Down
97 changes: 97 additions & 0 deletions vinvoor/src/overview/streak/Streak.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Box, Typography } from "@mui/material";
import { useContext } from "react";
import { Scan } from "../../types/scans";
import {
isTheSameDay,
MILLISECONDS_IN_ONE_DAY,
shiftDate,
} from "../../util/util";
import { ScanContext } from "../Overview";

const isWeekendBetween = (date1: Date, date2: Date) => {
const diffDays = Math.floor(
(date2.getTime() - date1.getTime()) / MILLISECONDS_IN_ONE_DAY
);

if (diffDays > 2) return false;

return date1.getDay() === 5 && [1, 6, 7].includes(date2.getDay());
};

const isStreakDay = (date1: Date, date2: Date) => {
console.log(date1, date2);
console.log(shiftDate(date2, 1));
console.log(isTheSameDay(date1, shiftDate(date2, 1)));

if (isTheSameDay(date1, shiftDate(date2, 1))) return true;

if (date1.getDay() === 5 && [1, 6, 7].includes(date2.getDay()))
return isWeekendBetween(date1, date2);

return false;
};

const getStreak = (scans: readonly Scan[]): [boolean, number] => {
let streak = 0;
const isOnStreak =
isTheSameDay(scans[scans.length - 1].scanTime, new Date()) ||
isWeekendBetween(scans[scans.length - 1].scanTime, new Date());

if (isOnStreak) {
let i = scans.length;
streak++;

while (i-- > 1 && isStreakDay(scans[i].scanTime, scans[i - 1].scanTime))
streak++;
} else {
streak = Math.floor(
(new Date().getTime() -
scans[scans.length - 1].scanTime.getTime()) /
MILLISECONDS_IN_ONE_DAY -
1
);
}

return [isOnStreak, streak];
};

export const Streak = () => {
const { scans } = useContext(ScanContext);
const [isOnStreak, streak] = getStreak(scans);

return isOnStreak ? (
<Box display="flex" alignItems="flex-end">
<Typography
variant="h2"
color="primary"
fontWeight="bold"
sx={{ mr: 1 }}
>
{streak}
</Typography>
<Typography variant="body2" color="primary">
day{streak > 1 ? "s" : ""} streak
</Typography>
</Box>
) : (
<Box
display="flex"
alignItems="flex-end"
justifyItems="center"
justifyContent="center"
// sx={{ border: 1, borderColor: "primary.secondary" }}
>
<Typography
variant="h2"
color="error"
fontWeight="bold"
sx={{ mr: 1 }}
>
{streak}
</Typography>
<Typography variant="body2" color="error">
day{streak > 1 ? "s" : ""} absent
</Typography>
</Box>
);
};
17 changes: 17 additions & 0 deletions vinvoor/src/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@ export const randomInt = (lower: number = 0, upper: number = 10000): number => {
return Math.floor(Math.random() * (upper - lower + 1) + lower);
};

// Date functions

export const MILLISECONDS_IN_ONE_DAY = 24 * 60 * 60 * 1000;

export const isTheSameDay = (date1: Date, date2: Date) =>
date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate();

export const shiftDate = (date: Date, numDays: number) => {
const newDate = new Date(date);
newDate.setDate(newDate.getDate() + numDays);
return newDate;
};

// Compare functions

export const equal = (left: any, right: any): boolean => {
if (typeof left !== typeof right) return false;

Expand Down
12 changes: 12 additions & 0 deletions vinvoor/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1795,6 +1795,13 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==

react-device-detect@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/react-device-detect/-/react-device-detect-2.2.3.tgz#97a7ae767cdd004e7c3578260f48cf70c036e7ca"
integrity sha512-buYY3qrCnQVlIFHrC5UcUoAj7iANs/+srdkwsnNjI7anr3Tt7UY6MqNxtMLlr0tMBied0O49UZVK8XKs3ZIiPw==
dependencies:
ua-parser-js "^1.0.33"

react-dom@^18.2.0:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
Expand Down Expand Up @@ -2092,6 +2099,11 @@ typescript@^5.2.2:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa"
integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==

ua-parser-js@^1.0.33:
version "1.0.38"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.38.tgz#66bb0c4c0e322fe48edfe6d446df6042e62f25e2"
integrity sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==

uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
Expand Down

0 comments on commit ed6d70a

Please sign in to comment.