in pinball-frontend/app/events/page.tsx [20:194]
export default function Stats() {
const [gameEvents, setGameEvents] = useState<GameEvent[]>([]);
const [gameStartEvent, setGameStartEvent] = useState<GameEvent | null>(null);
const gameStartTimestamp = gameStartEvent?.utcTimestamp || getNowTimestamp();
const [timeElapsedMillis, setTimeElapsedMillis] = useState(0);
const [machineId, setMachineId] = useState('');
const [gameId, setGameId] = useState('');
const lastPrEvent = gameEvents.find((gameEvent) => gameEvent?.data?.BacklogName === 'PR');
const prCount = lastPrEvent ? lastPrEvent.data.BacklogCount : gameStartEvent ? gameStartEvent.data.InitialPRCount : 0;
const lastBugEvent = gameEvents.find((gameEvent) => gameEvent?.data?.BacklogName === 'Bug');
const bugCount = lastBugEvent ? lastBugEvent.data.BacklogCount : gameStartEvent ? gameStartEvent.data.InitialBugCount : 0;
const lastBallDrainEvent = gameEvents.find((gameEvent) => gameEvent.pinballEventType === 'BallDrained');
const ballDrainCount = lastBallDrainEvent ? lastBallDrainEvent.data.TotalDrains : 0;
const lastBallLaunchEvent = gameEvents.find((gameEvent) => gameEvent.pinballEventType === 'BallLaunched');
const ballLaunchCount = lastBallLaunchEvent ? lastBallLaunchEvent.data.LaunchedBallCount : 0;
const lastLoopHitEvent = gameEvents.find((gameEvent) => gameEvent.pinballEventType === 'LoopHit');
const loopHitCount = lastLoopHitEvent ? lastLoopHitEvent.data.TotalLoops : 0;
const [currentGame, setCurrentGame] = useState({
gameId: 'CURRENT_GAME',
playerName: '',
avatar: '',
});
useEffect(() => {
const interval = setInterval(() => {
const gameEndEvent = gameEvents.find((gameEvent) => (gameEvent.pinballEventType === 'GameEnded'));
if (gameEndEvent) {
setTimeElapsedMillis(gameEndEvent.gameLengthMilliseconds);
} else if (gameStartTimestamp) {
const now = getNowTimestamp();
setTimeElapsedMillis(now - gameStartTimestamp);
} else {
setTimeElapsedMillis(0);
}
}, 1000);
//Clearing the interval
return () => clearInterval(interval);
}, [gameEvents, gameStartTimestamp, timeElapsedMillis]);
useEffect(() => {
const liveEventsQuery = query(liveGameEventsRef, orderBy('publishTime', 'desc'), limit(1000));
const unsubscribe = onSnapshot(liveEventsQuery, (querySnapshot) => {
const gameEvents = querySnapshot.docs.map((doc) => {
const { GameId, ...data } = doc.data().data; // remove GameId from data
// sorts the keys so they are always printed in the same order
const dataString = JSON.stringify(data, Object.keys(data).sort(), 2);
const pinballEventType = doc.data().PinballEventType;
const utcTimestamp = doc.data().utcTimestamp;
if (doc.data().MachineId) {
setMachineId(doc.data().MachineId);
}
if (doc.data().GameId) {
setGameId(doc.data().GameId);
}
const gameEvent = {
messageId: doc.data().messageId,
publishTime: doc.data().publishTime,
pinballEventType,
utcTimestamp,
gameLengthMilliseconds: data.GameLengthMilliseconds,
dataString,
data,
};
if (pinballEventType === 'GameStarted') {
console.log({ docData: doc.data() })
setGameStartEvent(gameEvent)
setCurrentGame({
...currentGame,
playerName: data.PlayerName,
avatar: data.Avatar,
});
}
return gameEvent;
});
setGameEvents(gameEvents);
});
return unsubscribe;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const twoDigitPad = (number: number) => {
return ('0' + number).slice(-2);
}
return (
<main className="flex min-h-screen flex-col justify-between overflow-x-hidden m-4">
<QRCodeLink url="https://pinball-frontend-404073014646.us-west3.run.app/events" />
<div className="flex">
<div className="w-full">
<div className="pb-2 sm:flex">
<div className="font-bold pr-4 text-xl">Machine ID:</div>
<div className="font-mono text-xs sm:text-lg">{machineId}</div>
</div>
<div className="pb-2 sm:flex">
<div className="font-bold pr-4 text-xl">Game ID:</div>
<div className="font-mono text-xs sm:text-lg">{gameId}</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 p-2" style={{ borderWidth: '2px', borderColor: '#FBBC04' }}>
<MetricsTile
title="Game Events"
value={gameEvents.length}
/>
<MetricsTile
title="Ball Drains"
value={ballDrainCount}
/>
<MetricsTile
title="Launched Balls"
value={ballLaunchCount}
/>
<MetricsTile
title="Loops Hit"
value={loopHitCount}
/>
</div>
<table className="text-left font-thin mt-12">
<thead>
<tr>
<th scope="col" className="hidden sm:block px-6 py-3 w-[10ch]">
Metadata
</th>
<th scope="col" className="px-6 py-3">
Event Data
</th>
</tr>
</thead>
<tbody>
{gameEvents.map((gameEvent) => (
<tr key={gameEvent.messageId} className="border border-t-1 border-b-0 border-l-0 border-r-0" >
<td className="hidden sm:block px-6 py-4 font-mono">
<div className="text-xl whitespace-nowrap">
Event Type:
<br />
{gameEvent.pinballEventType}
</div>
<div className="text-xs whitespace-nowrap py-4">
Message Id:
<br />
{gameEvent.messageId}
</div>
</td>
<td scope="row" className="px-6 py-4 whitespace-nowrap font-mono">
<div className="sm:hidden text-xs whitespace-nowrap">
<div className="text-xl whitespace-nowrap">
Event Type: {gameEvent.pinballEventType}
</div>
<div className="text-xs whitespace-nowrap py-4">
Message Id: {gameEvent.messageId}
</div>
</div>
<pre className="text-sm md:text-xl">
{gameEvent.dataString}
</pre>
</td>
</tr>
))}
</tbody>
</table>
</div>
<ActiveRankingCard
title="Game Length Leader Board"
field="GameLengthMilliseconds"
mapper={(milliseconds: number) => Math.floor(milliseconds / 1000)}
units="seconds"
currentGame={{
...currentGame,
value: timeElapsedMillis,
}}
/>
</div>
</main >
);
}