From 9a208e3ba5fb2016b67c6806114a5cefea1647fe Mon Sep 17 00:00:00 2001 From: Topvennie Date: Wed, 17 Jul 2024 15:26:43 +0200 Subject: [PATCH] vinvoor: show a scan overview --- vinvoor/src/cards/CardsTableBody.tsx | 4 +- vinvoor/src/cards/CardsTableHead.tsx | 4 +- vinvoor/src/main.tsx | 5 +++ vinvoor/src/navbar/NavBar.tsx | 6 ++- vinvoor/src/scans/Scans.tsx | 36 +++++++++++++++ vinvoor/src/scans/ScansBody.tsx | 44 +++++++++++++++++++ .../ScansTableHead.tsx} | 6 +-- vinvoor/src/types/cards.ts | 2 +- vinvoor/src/types/scans.ts | 35 +++++++++++++++ 9 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 vinvoor/src/scans/Scans.tsx create mode 100644 vinvoor/src/scans/ScansBody.tsx rename vinvoor/src/{leaderboard/LeaderboardTableHead.tsx => scans/ScansTableHead.tsx} (69%) diff --git a/vinvoor/src/cards/CardsTableBody.tsx b/vinvoor/src/cards/CardsTableBody.tsx index 0f15dbc..1d01ef7 100644 --- a/vinvoor/src/cards/CardsTableBody.tsx +++ b/vinvoor/src/cards/CardsTableBody.tsx @@ -11,7 +11,7 @@ import { import { useConfirm } from "material-ui-confirm"; import { useSnackbar } from "notistack"; import { ChangeEvent, FC, MouseEvent, useContext } from "react"; -import { Card, CardsHeadCells, convertCardJSON } from "../types/cards"; +import { Card, cardsHeadCells, convertCardJSON } from "../types/cards"; import { getApi, patchApi } from "../util/fetch"; import { CardContext } from "./Cards"; @@ -95,7 +95,7 @@ export const CardsTableBody: FC = ({ > - {CardsHeadCells.map((headCell) => ( + {cardsHeadCells.map((headCell) => ( = ({ onChange={onSelectAllClick} /> - {CardsHeadCells.map((headCell) => ( + {cardsHeadCells.map((headCell) => ( , }, + { + path: "scans", + element: , + }, { path: "cards", element: , diff --git a/vinvoor/src/navbar/NavBar.tsx b/vinvoor/src/navbar/NavBar.tsx index a660fff..a07435d 100644 --- a/vinvoor/src/navbar/NavBar.tsx +++ b/vinvoor/src/navbar/NavBar.tsx @@ -1,6 +1,9 @@ import LeaderboardIcon from "@mui/icons-material/Leaderboard"; import { AppBar, Box, Container, Toolbar } from "@mui/material"; -import { CreditCardMultipleOutline } from "mdi-material-ui"; +import { + CreditCardMultipleOutline, + CreditCardScanOutline, +} from "mdi-material-ui"; import { useContext } from "react"; import { DarkModeToggle } from "../components/DarkModeToggle"; import { UserContext } from "../user/UserProvider"; @@ -15,6 +18,7 @@ export interface PageIcon { } const navBarPages: PageIcon[] = [ + { page: "Scans", icon: }, { page: "Cards", icon: }, { page: "Leaderboard", icon: }, ]; diff --git a/vinvoor/src/scans/Scans.tsx b/vinvoor/src/scans/Scans.tsx new file mode 100644 index 0000000..ced950f --- /dev/null +++ b/vinvoor/src/scans/Scans.tsx @@ -0,0 +1,36 @@ +import { Paper, Table, TableContainer } from "@mui/material"; +import { useState } from "react"; +import { LoadingSkeleton } from "../components/LoadingSkeleton"; +import { useFetch } from "../hooks/useFetch"; +import { Card, convertCardJSON } from "../types/cards"; +import { convertScanJSON, Scan } from "../types/scans"; +import { ScansTableBody } from "./ScansBody"; +import { ScansTableHead } from "./ScansTableHead"; + +export const Scans = () => { + const [scans, setScans] = useState([]); + const [cards, setCards] = useState([]); + const { loading: loadingScans } = useFetch( + "scans", + setScans, + convertScanJSON + ); + const { loading: loadingCards } = useFetch( + "cards", + setCards, + convertCardJSON + ); + + return ( + + + + + + +
+
+
+
+ ); +}; diff --git a/vinvoor/src/scans/ScansBody.tsx b/vinvoor/src/scans/ScansBody.tsx new file mode 100644 index 0000000..d46b451 --- /dev/null +++ b/vinvoor/src/scans/ScansBody.tsx @@ -0,0 +1,44 @@ +import { TableBody, TableCell, Typography } from "@mui/material"; +import { TableRow } from "mdi-material-ui"; +import { FC, useEffect, useState } from "react"; +import { Card } from "../types/cards"; +import { + mergeScansCards, + Scan, + ScanCard, + scanCardHeadCells, +} from "../types/scans"; + +interface ScansTableBodyProps { + scans: readonly Scan[]; + cards: readonly Card[]; +} + +export const ScansTableBody: FC = ({ scans, cards }) => { + const [scanCards, setScanCards] = useState([]); + + useEffect(() => { + setScanCards(mergeScansCards(scans, cards)); + }, [scans, cards]); + + return ( + + {scanCards.map((scanCard) => ( + + {scanCardHeadCells.map((headCell) => ( + + + {headCell.convert && + headCell.convert(scanCard[headCell.id])} + + + ))} + + ))} + + ); +}; diff --git a/vinvoor/src/leaderboard/LeaderboardTableHead.tsx b/vinvoor/src/scans/ScansTableHead.tsx similarity index 69% rename from vinvoor/src/leaderboard/LeaderboardTableHead.tsx rename to vinvoor/src/scans/ScansTableHead.tsx index 9f44f14..1ca025b 100644 --- a/vinvoor/src/leaderboard/LeaderboardTableHead.tsx +++ b/vinvoor/src/scans/ScansTableHead.tsx @@ -1,11 +1,11 @@ import { TableCell, TableHead, TableRow, Typography } from "@mui/material"; -import { leaderboardHeadCells } from "../types/leaderboard"; +import { scanCardHeadCells } from "../types/scans"; -export const LeaderboardTableHead = () => { +export const ScansTableHead = () => { return ( - {leaderboardHeadCells.map((headCell) => ( + {scanCardHeadCells.map((headCell) => ( {headCell.label} diff --git a/vinvoor/src/types/cards.ts b/vinvoor/src/types/cards.ts index a44bae7..e406160 100644 --- a/vinvoor/src/types/cards.ts +++ b/vinvoor/src/types/cards.ts @@ -24,7 +24,7 @@ export const convertCardJSON = (cardsJSON: CardJSON[]): Card[] => createdAt: new Date(CardJSON.createdAt), })); -export const CardsHeadCells: readonly TableHeadCell[] = [ +export const cardsHeadCells: readonly TableHeadCell[] = [ { id: "name", label: "Name", diff --git a/vinvoor/src/types/scans.ts b/vinvoor/src/types/scans.ts index 313d943..eb7af45 100644 --- a/vinvoor/src/types/scans.ts +++ b/vinvoor/src/types/scans.ts @@ -1,3 +1,6 @@ +import { Card } from "./cards"; +import { TableHeadCell } from "./general"; + interface ScanJSON { scanTime: string; card: string; @@ -8,6 +11,11 @@ export interface Scan { card: string; } +export interface ScanCard { + scanTime: Date; + card?: Card; +} + export const convertScanJSON = (scansJSON: ScanJSON[]): Scan[] => scansJSON .map((scanJSON) => ({ @@ -15,3 +23,30 @@ export const convertScanJSON = (scansJSON: ScanJSON[]): Scan[] => card: scanJSON.card, })) .sort((a, b) => a.scanTime.getTime() - b.scanTime.getTime()); + +export const mergeScansCards = ( + scans: readonly Scan[], + cards: readonly Card[] +): readonly ScanCard[] => + scans.map((scan) => ({ + scanTime: scan.scanTime, + card: cards.find((card) => card.serial === scan.card), + })); + +export const scanCardHeadCells: readonly TableHeadCell[] = [ + { + id: "card", + label: "Card", + align: "left", + padding: "normal", + convert: (value: Card | undefined) => + value?.name ?? value?.serial ?? "Unknown", + }, + { + id: "scanTime", + label: "Scan time", + align: "right", + padding: "normal", + convert: (value: Date) => value.toDateString(), + }, +];