Skip to content

Commit

Permalink
vinvoor: show current checkin status
Browse files Browse the repository at this point in the history
  • Loading branch information
Topvennie committed Jul 6, 2024
1 parent 5b5a3a6 commit 7c0519b
Show file tree
Hide file tree
Showing 11 changed files with 606 additions and 479 deletions.
2 changes: 1 addition & 1 deletion vingo/handlers/cards.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func StartCardRegisterAPI(c *fiber.Ctx) error {

if time.Now().Before(registering_end) {
// true if current user is already registering
return c.Status(503).JSON(map[string]bool{"is_current_user": registering_user == user.Id})
return c.Status(503).JSON(map[string]bool{"isCurrentUser": registering_user == user.Id})
}

registering_user = user.Id
Expand Down
1 change: 1 addition & 0 deletions vinvoor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@types/js-cookie": "^3.0.6",
"@types/react-router-dom": "^5.3.3",
"@types/react-router-hash-link": "^2.4.9",
"apexcharts": "^3.50.0",
"js-cookie": "^3.0.5",
"material-ui-confirm": "^3.0.16",
"mdi-material-ui": "^7.9.1",
Expand Down
8 changes: 6 additions & 2 deletions vinvoor/src/cards/Cards.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createContext, Dispatch, SetStateAction, useState } from "react";
import { LoadingSkeleton } from "../components/LoadingSkeleton";
import { useFetch } from "../hooks/useFetch";
import { Card } from "../types/cards";
import { Card, convertCardJSON } from "../types/cards";
import { CardsEmpty } from "./CardsEmpty";
import { CardsTable } from "./CardsTable";

Expand All @@ -17,7 +17,11 @@ export const CardContext = createContext<CardContextProps>({

export const Cards = () => {
const [cards, setCards] = useState<readonly Card[]>([]);
const { loading } = useFetch<readonly Card[]>("cards", setCards);
const { loading } = useFetch<readonly Card[]>(
"cards",
setCards,
convertCardJSON
);

return (
<LoadingSkeleton loading={loading}>
Expand Down
2 changes: 1 addition & 1 deletion vinvoor/src/cards/CardsAdd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const CardsAdd = () => {
.catch((error) => {
if (isResponseNot200Error(error)) {
error.response.json().then((response: CardPostResponse) => {
if (response.is_current_user)
if (response.isCurrentUser)
enqueueSnackbar(requestYou, { variant: "warning" });
else
enqueueSnackbar(requestOther, { variant: "error" });
Expand Down
83 changes: 56 additions & 27 deletions vinvoor/src/overview/Overview.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,74 @@
import { Box, Paper, Switch, Typography } from "@mui/material";
import { useState } from "react";
import Grid from "@mui/material/Grid";
import { createContext, useState } from "react";
import { Tooltip } from "react-tooltip";
import { LoadingSkeleton } from "../components/LoadingSkeleton";
import { useFetch } from "../hooks/useFetch";
import { Scan } from "../types/scans";
import { convertScanJSON, Scan } from "../types/scans";
import { CheckIn } from "./checkin/CheckIn";
import { Heatmap, HeatmapVariant } from "./heatmap/Heatmap";

interface ScanContextProps {
scans: readonly Scan[];
}

export const ScanContext = createContext<ScanContextProps>({
scans: [],
});

export const Overview = () => {
const [scans, setScans] = useState<Scan[]>([]);
const { loading } = useFetch<Scan[]>("scans", setScans);
const [scans, setScans] = useState<readonly Scan[]>([]);
const { loading } = useFetch<readonly Scan[]>(
"scans",
setScans,
convertScanJSON
);
const [checked, setChecked] = useState<boolean>(true);

const dates = scans.map((scan) => new Date(scan.scanTime));

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setChecked(event.target.checked);
};

return (
<LoadingSkeleton loading={loading}>
<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>
<Heatmap
startDate={new Date("2024-01-01")}
endDate={new Date("2024-12-31")}
days={dates}
variant={
checked ? HeatmapVariant.DAYS : HeatmapVariant.MONTHS
}
/>
<Tooltip id="heatmap" />
</Paper>
<ScanContext.Provider value={{ scans }}>
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<CheckIn />
</Grid>
<Grid item xs={12} md={8}>
<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>
<Heatmap
startDate={new Date("2024-01-01")}
endDate={new Date("2024-12-31")}
variant={
checked
? HeatmapVariant.DAYS
: HeatmapVariant.MONTHS
}
/>
<Tooltip id="heatmap" />
</Paper>
</Grid>
</Grid>
</ScanContext.Provider>
</LoadingSkeleton>
);
};

// TODO: Checked in today
// TODO: Current streak
// TODO: Pie chart
42 changes: 42 additions & 0 deletions vinvoor/src/overview/checkin/CheckIn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Alert, AlertTitle } from "@mui/material";
import { EmoticonExcitedOutline, EmoticonFrownOutline } from "mdi-material-ui";
import { useContext } from "react";
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);

const checkedIn = isTheSameDay(
scans[scans.length - 1].scanTime,
new Date()
);

return checkedIn ? (
<Alert
variant="outlined"
severity="success"
iconMapping={{
success: <EmoticonExcitedOutline fontSize="large" />,
}}
>
<AlertTitle>Checked in</AlertTitle>
Thank you for stopping by the kelder!
</Alert>
) : (
<Alert
variant="outlined"
severity="error"
iconMapping={{
error: <EmoticonFrownOutline fontSize="large" />,
}}
>
<AlertTitle>Not checked in</AlertTitle>
We miss you in the kelder!
</Alert>
);
};
96 changes: 39 additions & 57 deletions vinvoor/src/overview/heatmap/Heatmap.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FC } from "react";
import { FC, useContext } from "react";
import { ScanContext } from "../Overview";
import "./heatmap.css";
import {
dateTimeFormat,
Expand Down Expand Up @@ -29,7 +30,6 @@ export enum HeatmapVariant {
}

interface HeatmapProps {
days: readonly Date[];
startDate: Date;
endDate: Date;
variant: HeatmapVariant;
Expand Down Expand Up @@ -102,50 +102,41 @@ const getWeeksInMonth = (values: HeatmapItem[]): { [key: number]: number } => {

const getClassNameForValue = (value: HeatmapItem, variant: HeatmapVariant) => {
if (variant === HeatmapVariant.DAYS) {
if (value.count > 0) {
return `color-active`;
}
if (value.count > 0) return `color-active`;

return `color-inactive`;
} else {
if (value.count <= 5) {
return `color-${value.count}`;
}
if (value.count <= 5) return `color-${value.count}`;

return "color-5";
}
};

const getTooltipDataAttrsForDate = (
value: HeatmapItem,
variant: HeatmapVariant
) => {
return {
"data-tooltip-id": "heatmap",
"data-tooltip-content":
variant === HeatmapVariant.DAYS
? getTooltipDataAttrsForDays(value)
: getTooltipDataAttrsForMonths(value),
};
};

const getTooltipDataAttrsForDays = (value: HeatmapItem) => {
return `${
value.count > 0 ? "Present" : "Absent"
} on ${dateTimeFormat.format(value.date)}`;
};
) => ({
"data-tooltip-id": "heatmap",
"data-tooltip-content":
variant === HeatmapVariant.DAYS
? getTooltipDataAttrsForDays(value)
: getTooltipDataAttrsForMonths(value),
});

const getTooltipDataAttrsForDays = (value: HeatmapItem) =>
`${value.count > 0 ? "Present" : "Absent"} on ${dateTimeFormat.format(
value.date
)}`;

const getTooltipDataAttrsForMonths = (value: HeatmapItem) => {
return `${value.count} scan${
const getTooltipDataAttrsForMonths = (value: HeatmapItem) =>
`${value.count} scan${
value.count !== 1 ? "s" : ""
} on the week of ${dateTimeFormat.format(value.date)}`;
};

export const Heatmap: FC<HeatmapProps> = ({
days,
startDate,
endDate,
variant,
}) => {
export const Heatmap: FC<HeatmapProps> = ({ startDate, endDate, variant }) => {
const { scans } = useContext(ScanContext);
const days = scans.map((scan) => scan.scanTime);

days.forEach((date) => date.setHours(0, 0, 0, 0));
startDate.setHours(0, 0, 0, 0);
endDate.setHours(0, 0, 0, 0);
Expand All @@ -164,18 +155,12 @@ export const Heatmap: FC<HeatmapProps> = ({

let valueIndex = 0;
const renderSquare = (row: number, column: number) => {
if (column === 0 && row < emptyStart) {
return null;
}
if (column === 0 && row < emptyStart) return null;

if (variant === HeatmapVariant.DAYS) {
if (column === columns - 1 && row > emptyEnd) {
return null;
}
}
if (variant === HeatmapVariant.DAYS)
if (column === columns - 1 && row > emptyEnd) return null;

const value = values[valueIndex++];
console.log(value);

const [x, y] = getSquareCoordinates(row);

Expand All @@ -194,23 +179,20 @@ export const Heatmap: FC<HeatmapProps> = ({
);
};

const renderColumn = (column: number) => {
return (
<g key={column} transform={getTransformForColumn(column)}>
{[
...Array(
variant === HeatmapVariant.DAYS
? DAYS_IN_WEEK
: weeksInMonth[column]
).keys(),
].map((row) => renderSquare(row, column))}
</g>
);
};
const renderColumn = (column: number) => (
<g key={column} transform={getTransformForColumn(column)}>
{[
...Array(
variant === HeatmapVariant.DAYS
? DAYS_IN_WEEK
: weeksInMonth[column]
).keys(),
].map((row) => renderSquare(row, column))}
</g>
);

const renderColumns = () => {
return [...Array(columns).keys()].map((column) => renderColumn(column));
};
const renderColumns = () =>
[...Array(columns).keys()].map((column) => renderColumn(column));

const renderMonthLabels = () => {
if (variant === HeatmapVariant.DAYS) {
Expand Down
Loading

0 comments on commit 7c0519b

Please sign in to comment.