pinball-frontend/app/components/stats-card.tsx (88 lines of code) (raw):
'use client'
import { useEffect, useState } from "react";
import { completedGamesRef } from "../firebase";
import { average, getAggregateFromServer, limit, onSnapshot, orderBy, query, where } from "firebase/firestore";
import Avatar from "@/app/components/avatar";
import { getYesterdayTimestamp } from "@/app/utils/timestamp";
const returnInput = (value: number) => value;
type Game = {
gameId: string,
avatar: string,
playerName: string,
value: string,
}
const defaultGames = [{
gameId: '',
avatar: 'beaver',
playerName: 'Loading...',
value: 'Loading...',
}];
const yesterdayUtcTimestamp = getYesterdayTimestamp();
export default function StatsCard({ title, field, units, mapper = returnInput }: { title: string, field: string, units: string, mapper?: Function }) {
const [topTen, setTopTen] = useState<Game[]>(defaultGames);
const topGame = topTen[0];
const [averageValue, setAverageValue] = useState<string>('Loading...');
useEffect(() => {
const maxValueQuery = query(completedGamesRef, where('utcTimestamp', '>', yesterdayUtcTimestamp), orderBy(field, 'desc'), limit(10))
const unsubscribe = onSnapshot(maxValueQuery, (querySnapshot) => {
const topTen = querySnapshot.docs.map(gameStats => {
const data = gameStats.data();
return {
gameId: data.GameId,
avatar: data.Avatar,
playerName: data.PlayerName,
value: mapper(data[field]).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","),
}
});
setTopTen(topTen);
});
return unsubscribe;
}, [field, mapper]);
useEffect(() => {
const unsubscribe = onSnapshot(query(completedGamesRef), async () => {
const averageValueQueryResponse = await getAggregateFromServer(query(completedGamesRef, where('utcTimestamp', '>', yesterdayUtcTimestamp)), { averageAverageValue: average(field) });
const { averageAverageValue } = averageValueQueryResponse.data();
if (averageAverageValue) {
setAverageValue(Math.floor(mapper(averageAverageValue)).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","));
}
});
return unsubscribe;
}, []);
return (
<div className="m-2">
<div className="flex justify-center border rounded overflow-hidden shadow-lg mx-auto w-full">
<div className="px-6 py-4 text-center">
<div className="mb-2 font-bold">Max<br />{title}</div>
<div className="font-bold text-4xl">
<center>
<Avatar avatar={topGame.avatar} />
<div className="text-lg">
{topGame.playerName}
</div>
<span className="font-mono">
{topGame.value}
</span>
</center>
</div>
<div className="mb-2 -mt-2">{units}</div>
<hr className="m-8" />
<div className="mb-2 font-bold">Average<br />{title}</div>
<div className="font-bold text-4xl font-mono">
{averageValue}
</div>
<div className="mb-2 -mt-2">{units}</div>
<hr className="m-8" />
<div className="mb-2 font-bold">Top 10</div>
{topTen.map((game => (<div key={game.gameId}>
<hr className="m-2" />
<div className="flex justify-between">
<Avatar avatar={game.avatar} />
<div className="text-right">
<div>{game.playerName}</div>
<div className="font-mono">{game.value}</div>
</div>
</div>
</div>)))}
</div>
</div>
</div>
);
}