PlayerStatsDrawer.tsx

This commit is contained in:
2025-05-26 19:53:26 +02:00
parent c91831594f
commit f1b7f28fe3
6 changed files with 111 additions and 15 deletions

View File

@ -16,8 +16,9 @@ import {
} from "@mui/joy";
import GameAccordionItem from "./components/GameAccordionItem.tsx";
import PlayerAccordionItem from "./components/PlayerAccordionItem.tsx";
import StatsDrawer from "./components/StatsDrawer.tsx";
import PlayerGameStatsDrawer from "./components/PlayerGameStatsDrawer.tsx";
import {Cancel, CheckCircleOutline} from '@mui/icons-material';
import {PlayerStatsDrawer} from "./components/PlayerStatsDrawer.tsx";
export default function App() {
const [apiUri, setApiUri] = useState<string>("http://127.0.0.1:5239");
@ -29,12 +30,18 @@ export default function App() {
const [selectedPlayer, setSelectedPlayer] = useState<IPlayer | null>(null);
const [selectedGame, setSelectedGame] = useState<IGame | null>(null);
const [open, setOpen] = useState<boolean>(false);
const [openPlayerGameStats, setOpenPlayerGameStats] = useState<boolean>(false);
const [openPlayerStats, setOpenPlayerStats] = useState<boolean>(false);
const OpenDrawer = (player: IPlayer, game: IGame) => {
const OpenPlayerGameStatsDrawer = (player: IPlayer, game: IGame) => {
setSelectedPlayer(player);
setSelectedGame(game);
setOpen(true);
setOpenPlayerGameStats(true);
}
const OpenPlayerStatsDrawer = (player: IPlayer) => {
setSelectedPlayer(player);
setOpenPlayerStats(true);
}
useEffect(() => {
@ -92,18 +99,19 @@ export default function App() {
<Box sx={{width:'50%'}}>
<Typography level={"h2"}>Players</Typography>
<AccordionGroup>
{players?.map((player) => <PlayerAccordionItem key={player.steamId} player={player} OpenDrawer={OpenDrawer} />)}
{players?.map((player) => <PlayerAccordionItem key={player.steamId} player={player} OpenPlayerGameStatsDrawer={OpenPlayerGameStatsDrawer} OpenPlayerStatsDrawer={OpenPlayerStatsDrawer} />)}
</AccordionGroup>
</Box>
<Divider />
<Box sx={{width:'50%'}}>
<Typography level={"h2"}>Games</Typography>
<AccordionGroup>
{games?.map((game) => <GameAccordionItem key={game.appId} game={game} OpenDrawer={OpenDrawer} />)}
{games?.map((game) => <GameAccordionItem key={game.appId} game={game} OpenPlayerGameStatsDrawer={OpenPlayerGameStatsDrawer} />)}
</AccordionGroup>
</Box>
</Stack>
<StatsDrawer player={selectedPlayer} game={selectedGame} open={open} setOpen={setOpen} />
<PlayerGameStatsDrawer player={selectedPlayer} game={selectedGame} open={openPlayerGameStats} setOpen={setOpenPlayerGameStats} />
<PlayerStatsDrawer player={selectedPlayer} open={openPlayerStats} setOpen={setOpenPlayerStats} />
</GamesContext>
</PlayersContext.Provider>
</ApiUriContext>

View File

@ -2,7 +2,7 @@ import {getData} from "../fetchApi.tsx";
import type ITrackedTime from "../types/ITrackedTime.ts";
export function GetTimelines(apiUri: string, steamId: bigint){
return getData(`${apiUri}/TimeTrack/${steamId}`) as Promise<Map<bigint, ITrackedTime[]>>;
return getData(`${apiUri}/TimeTrack/${steamId}`) as Promise<{key: bigint, value: ITrackedTime[]}[]>;
}
export function GetTimelineGame(apiUri: string, steamId: bigint, appId: bigint){
@ -14,5 +14,5 @@ export function GetTotal(apiUri: string, steamId: bigint){
}
export function GetTotalPerGame(apiUri: string, steamId: bigint){
return getData(`${apiUri}/TimeTrack/${steamId}/Total/PerGame`) as Promise<Map<bigint, bigint>>;
return getData(`${apiUri}/TimeTrack/${steamId}/Total/PerGame`) as Promise<{key: bigint, value: bigint}[]>;
}

View File

@ -11,7 +11,7 @@ import {GetPlayersOfGame} from "../api/endpoints/Data.tsx";
import {ApiUriContext} from "../api/fetchApi.tsx";
import PlayerCard from "./PlayerCard.tsx";
export default function GameAccordionItem({game, OpenDrawer} : {game: IGame, OpenDrawer : (player: IPlayer, game: IGame) => void}) {
export default function GameAccordionItem({game, OpenPlayerGameStatsDrawer} : {game: IGame, OpenPlayerGameStatsDrawer : (player: IPlayer, game: IGame) => void}) {
const apiUri = useContext(ApiUriContext);
const [players, setPlayers] = useState<IPlayer[]>([]);
@ -39,7 +39,7 @@ export default function GameAccordionItem({game, OpenDrawer} : {game: IGame, Ope
<Stack flexWrap={"wrap"} direction={"row"} spacing={1} useFlexGap>
{loading
? <CircularProgress />
: players?.map((player) => <PlayerCard player={player} onClick={() => OpenDrawer(player, game)} />)}
: players?.map((player) => <PlayerCard key={player.steamId} player={player} onClick={() => OpenPlayerGameStatsDrawer(player, game)} />)}
</Stack>
</AccordionDetails>
</Accordion>

View File

@ -12,7 +12,7 @@ import {GetGamesOfPlayer} from "../api/endpoints/Data.tsx";
import GameCard from "./GameCard.tsx";
import {GetTotal} from "../api/endpoints/TimeTrack.tsx";
export default function PlayerAccordionItem({player, OpenDrawer} : {player: IPlayer, OpenDrawer : (player: IPlayer, game: IGame) => void}) {
export default function PlayerAccordionItem({player, OpenPlayerGameStatsDrawer, OpenPlayerStatsDrawer} : {player: IPlayer, OpenPlayerGameStatsDrawer : (player: IPlayer, game: IGame) => void, OpenPlayerStatsDrawer : (player: IPlayer) => void}) {
const apiUri = useContext(ApiUriContext);
const [games, setGames] = useState<IGame[]>([]);
@ -41,13 +41,13 @@ export default function PlayerAccordionItem({player, OpenDrawer} : {player: IPla
</AccordionSummary>
<AccordionDetails>
<Stack flexWrap={"nowrap"} direction={"row"} alignContent={"center"} spacing={2} useFlexGap marginBottom={"10px"}>
<Button>All Games</Button>
<Button onClick={() => OpenPlayerStatsDrawer(player)}>All Games</Button>
<Typography level={"h4"} >Total Time: {totalTime}</Typography>
</Stack>
<Stack flexWrap={"wrap"} direction={"row"} spacing={1} useFlexGap>
{loadingGames
? <CircularProgress />
: games?.map((game) => <GameCard game={game} onClick={() => OpenDrawer(player, game)} />)}
: games?.map((game) => <GameCard key={game.appId} game={game} onClick={() => OpenPlayerGameStatsDrawer(player, game)} />)}
</Stack>
</AccordionDetails>
</Accordion>

View File

@ -9,7 +9,7 @@ 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<boolean>}) {
export default function PlayerGameStatsDrawer({player, game, open, setOpen} : {player: IPlayer | null, game: IGame | null, open: boolean, setOpen: Dispatch<boolean>}) {
const apiUri = useContext(ApiUriContext);

View File

@ -0,0 +1,88 @@
import type IPlayer from "../api/types/IPlayer.ts";
import {Autocomplete, DialogContent, DialogTitle, Drawer, ModalClose, Stack} from "@mui/joy";
import {type Dispatch, useContext, useEffect, useState} from "react";
import {ApiUriContext} from "../api/fetchApi.tsx";
import {LineChart, type LineSeriesType} from "@mui/x-charts";
import PlayerCard from "./PlayerCard.tsx";
import {GamesContext} from "../api/contexts/GamesContext.tsx";
import {GetTimelines} from "../api/endpoints/TimeTrack.tsx";
import type {DatasetElementType} from "@mui/x-charts/internals";
export function PlayerStatsDrawer({player, open, setOpen}: {
player: IPlayer | null,
open: boolean,
setOpen: Dispatch<boolean>
}) {
const apiUri = useContext(ApiUriContext);
const games = useContext(GamesContext);
const [trackedTimes, setTrackedTimes] = useState<DatasetElementType<string | number | Date | null | undefined>[]>([]);
const [availableSeries, setAvailableSeries] = useState<LineSeriesType[]>([]);
const [selectedSeries, setSelectedSeries] = useState<LineSeriesType[]>([]);
useEffect(() => {
if (!open || !player)
return;
GetTimelines(apiUri, player.steamId)
.then((tt) => {
const times: DatasetElementType<string | number | Date | null | undefined>[] = [];
const g: Set<bigint> = new Set<bigint>();
tt.forEach((app) => {
app.value.forEach((time) => {
let o = {date: new Date(time.timeStamp)};
// @ts-ignore
o[`${app.key.toString()}`] = Number(time.timePlayed / 60)
times.push(o);
g.add(app.key);
});
});
const seriesTypes: LineSeriesType[] = [];
const iterator = g.keys();
let value = iterator.next();
while (!value.done) {
const appId = value.value;
seriesTypes.push({
type: "line",
dataKey: appId.toString(),
connectNulls: true,
label: games.games.find(g => g.appId == appId)?.name ?? appId?.toString() ?? ""
})
value = iterator.next();
}
setAvailableSeries(seriesTypes);
setTrackedTimes(times);
});
}, [open]);
return (
<Drawer anchor={"bottom"} size={"lg"} open={open} onClose={() => setOpen(false)}>
<ModalClose/>
<DialogTitle>
<PlayerCard player={player}/>
</DialogTitle>
<DialogContent>
<Stack direction={"column"} height={"100%"}>
<Autocomplete multiple
options={availableSeries.sort((a, b) => (a.label as string).localeCompare(b.label as string))}
placeholder={"Games"}
getOptionLabel={(opt) => opt.label as string}
isOptionEqualToValue={(a, b) => a.dataKey == b.dataKey}
onChange={(_, value, reason) => {
if(reason == "selectOption" || reason == "removeOption" || reason == "clear")
setSelectedSeries(value);
console.log(value);
}}
/>
<LineChart dataset={trackedTimes}
xAxis={[{dataKey: "date", scaleType: "utc"}]}
yAxis={[{dataKey: "time", scaleType: "linear"}]}
series={selectedSeries}
/>
</Stack>
</DialogContent>
</Drawer>
);
}