diff --git a/package-lock.json b/package-lock.json index 4c3d02e..1be8366 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@fontsource/inter": "^5.2.5", + "@mui/icons-material": "^7.1.0", "@mui/joy": "^5.0.0-beta.52", "@mui/material": "^7.1.0", "@mui/x-charts": "^8.4.0", @@ -1173,6 +1174,31 @@ "url": "https://opencollective.com/mui-org" } }, + "node_modules/@mui/icons-material": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.1.0.tgz", + "integrity": "sha512-1mUPMAZ+Qk3jfgL5ftRR06ATH/Esi0izHl1z56H+df6cwIlCWG66RXciUqeJCttbOXOQ5y2DCjLZI/4t3Yg3LA==", + "dependencies": { + "@babel/runtime": "^7.27.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.1.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/joy": { "version": "5.0.0-beta.52", "resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.52.tgz", diff --git a/package.json b/package.json index 9372994..676f626 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@fontsource/inter": "^5.2.5", + "@mui/icons-material": "^7.1.0", "@mui/joy": "^5.0.0-beta.52", "@mui/material": "^7.1.0", "@mui/x-charts": "^8.4.0", diff --git a/src/App.tsx b/src/App.tsx index 9c55b1a..19cd0d5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,11 +4,11 @@ import type IPlayer from "./api/types/IPlayer.ts"; import {useEffect, useState} from "react"; import type IGame from "./api/types/IGame.ts"; import {GamesContext} from "./api/contexts/GamesContext.tsx"; -import {ApiUriContext} from "./api/fetchApi.tsx"; +import {ApiUriContext, getData} from "./api/fetchApi.tsx"; import {GetGames, GetPlayers} from "./api/endpoints/Data.tsx"; import { AccordionGroup, - Box, + Box, CircularProgress, Divider, Input, Stack, @@ -17,9 +17,13 @@ import { import GameAccordionItem from "./components/GameAccordionItem.tsx"; import PlayerAccordionItem from "./components/PlayerAccordionItem.tsx"; import StatsDrawer from "./components/StatsDrawer.tsx"; +import {Cancel, CheckCircleOutline} from '@mui/icons-material'; export default function App() { - const [apiUri, setApiUri] = useState("http://localhost:5239"); + const [apiUri, setApiUri] = useState("http://127.0.0.1:5239"); + const [connected, setConnected] = useState(false); + const [checkingConnection, setCheckingConnection] = useState(false); + const [players, setPlayers] = useState([]); const [games, setGames] = useState([]); @@ -34,15 +38,56 @@ export default function App() { } useEffect(() => { + if(!connected) + { + setPlayers([]); + setGames([]); + return; + } GetPlayers(apiUri).then(setPlayers); GetGames(apiUri).then(setGames); + }, [connected]); + + const checkConnection = () => { + setCheckingConnection(true); + return getData(`${apiUri}/swagger/v1/swagger.json`) + .then(() => { + setConnected(true); + return Promise.resolve(); + }) + .catch(() => { + setConnected(false) + return Promise.reject(); + }) + .finally(() => { + setCheckingConnection(false) + }); + } + + + const [connectionTimer, setConnectionTimer] = useState(null); + + useEffect(() => { + if(connectionTimer) + clearInterval(connectionTimer); + checkConnection() + .then(() => setConnectionTimer(setInterval(checkConnection, 5000))); }, [apiUri]); return ( - setApiUri(e.target.value)} /> + setApiUri(e.target.value)} + endDecorator={checkingConnection + ? + : connected + ? + : } + /> Players diff --git a/src/api/types/IGame.ts b/src/api/types/IGame.ts index 2952c8b..ca00d62 100644 --- a/src/api/types/IGame.ts +++ b/src/api/types/IGame.ts @@ -1,4 +1,6 @@ export default interface IGame { appId: bigint, - name: string + name: string, + iconUrl?: string | null, + logoUrl?: string | null, } \ No newline at end of file diff --git a/src/components/GameAccordionItem.tsx b/src/components/GameAccordionItem.tsx index a6983dd..1e0f351 100644 --- a/src/components/GameAccordionItem.tsx +++ b/src/components/GameAccordionItem.tsx @@ -3,7 +3,7 @@ import { Accordion, AccordionDetails, AccordionSummary, - Stack + Stack, Typography } from "@mui/joy"; import type IPlayer from "../api/types/IPlayer.ts"; import {useContext, useEffect, useState} from "react"; @@ -25,7 +25,7 @@ export default function GameAccordionItem({game, OpenDrawer} : {game: IGame, Ope return ( setExpanded(expanded)}> - {game.name} + {game.name} {players?.map((player) => OpenDrawer(player, game)} />)} diff --git a/src/components/GameCard.tsx b/src/components/GameCard.tsx new file mode 100644 index 0000000..949d79f --- /dev/null +++ b/src/components/GameCard.tsx @@ -0,0 +1,19 @@ +import {AspectRatio, Card, CardContent, Stack, Typography} from "@mui/joy"; +import type IGame from "../api/types/IGame.ts"; + +export default function GameCard({game, onClick} : {game: IGame | null, onClick?: React.MouseEventHandler | undefined}) { + return ( + + + + + + + + {game?.name} + + + + + ); +} \ No newline at end of file diff --git a/src/components/PlayerAccordionItem.tsx b/src/components/PlayerAccordionItem.tsx index 188cf89..c1c4b0d 100644 --- a/src/components/PlayerAccordionItem.tsx +++ b/src/components/PlayerAccordionItem.tsx @@ -3,15 +3,13 @@ import { Accordion, AccordionDetails, AccordionSummary, - AspectRatio, - Card, - CardContent, Stack, Typography } from "@mui/joy"; import type IPlayer from "../api/types/IPlayer.ts"; import {useContext, useEffect, useState} from "react"; import {ApiUriContext} from "../api/fetchApi.tsx"; import {GetGamesOfPlayer} from "../api/endpoints/Data.tsx"; +import GameCard from "./GameCard.tsx"; export default function PlayerAccordionItem({player, OpenDrawer} : {player: IPlayer, OpenDrawer : (player: IPlayer, game: IGame) => void}) { const apiUri = useContext(ApiUriContext); @@ -27,18 +25,10 @@ export default function PlayerAccordionItem({player, OpenDrawer} : {player: IPla return ( setExpanded(expanded)}> - {player.name} + {player.name} - {games?.map((game) => - - OpenDrawer(player, game)}> - - {game.name} - - - - )} + {games?.map((game) => OpenDrawer(player, game)} />)} diff --git a/src/components/PlayerCard.tsx b/src/components/PlayerCard.tsx index f7944fa..5af1d78 100644 --- a/src/components/PlayerCard.tsx +++ b/src/components/PlayerCard.tsx @@ -3,14 +3,14 @@ import type IPlayer from "../api/types/IPlayer.ts"; export default function PlayerCard({player, onClick} : {player: IPlayer | null, onClick?: React.MouseEventHandler | undefined}) { return ( - + - - + + - {player?.name} + {player?.name} diff --git a/src/components/StatsDrawer.tsx b/src/components/StatsDrawer.tsx index d6ca170..61c6bf0 100644 --- a/src/components/StatsDrawer.tsx +++ b/src/components/StatsDrawer.tsx @@ -1,12 +1,13 @@ import type IPlayer from "../api/types/IPlayer.ts"; import type IGame from "../api/types/IGame.ts"; -import {DialogContent, DialogTitle, Drawer, ModalClose, Typography} from "@mui/joy"; +import {DialogContent, DialogTitle, Drawer, ModalClose} from "@mui/joy"; import {type Dispatch, useContext, useEffect, useState} from "react"; import {ApiUriContext} from "../api/fetchApi.tsx"; import {GetTimelineGame} from "../api/endpoints/TimeTrack.tsx"; import type ITrackedTime from "../api/types/ITrackedTime.ts"; import {LineChart} from "@mui/x-charts"; import PlayerCard from "./PlayerCard.tsx"; +import GameCard from "./GameCard.tsx"; export default function StatsDrawer({player, game, open, setOpen} : {player: IPlayer | null, game: IGame | null, open: boolean, setOpen: Dispatch}) { @@ -24,11 +25,11 @@ export default function StatsDrawer({player, game, open, setOpen} : {player: IPl setOpen(false)}> - {game?.name} + - t.timeStamp)??[], scaleType: "utc", label: "Date"}]} + new Date(t.timeStamp))??[], scaleType: "utc", label: "Date"}]} series={[{data: trackedTime?.map(t => Number(t.timePlayed))??[], label: "Minutes Played"}]} sx={{height: "80%"}}/>