feat: modularize game filtering logic and UI into a custom hook and dedicated components.feat: modularize game filtering logic and UI into a custom hook and dedicated components.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { useState, useEffect, useRef, useMemo } from "react";
|
||||
import {
|
||||
Person,
|
||||
Game as GameProto,
|
||||
GetGameInfoRequest,
|
||||
GameInfoResponse,
|
||||
} from "../../items";
|
||||
import { apiFetch } from "../api";
|
||||
|
||||
export function useGameFilter(people: Person[], selectedPeople: Set<string>) {
|
||||
const [fetchedTitles, setFetchedTitles] = useState<string[]>([]);
|
||||
const metaDataRef = useRef<{ [key: string]: GameProto }>({});
|
||||
|
||||
const { gameToNegative, gameToPositiveOpinion } = useMemo(() => {
|
||||
const gameToNegative = new Map<string, Set<string>>();
|
||||
const gameToPositiveOpinion = new Map<string, Set<string>>();
|
||||
|
||||
if (selectedPeople.size === 0)
|
||||
return { gameToNegative, gameToPositiveOpinion };
|
||||
|
||||
const selectedPersons = people.filter((p) => selectedPeople.has(p.name));
|
||||
selectedPersons.forEach((person) => {
|
||||
person.opinion.forEach((op) => {
|
||||
if (!gameToNegative.has(op.title))
|
||||
gameToNegative.set(op.title, new Set());
|
||||
if (!gameToPositiveOpinion.has(op.title))
|
||||
gameToPositiveOpinion.set(op.title, new Set());
|
||||
|
||||
if (!op.wouldPlay) {
|
||||
gameToNegative.get(op.title)!.add(person.name);
|
||||
} else {
|
||||
gameToPositiveOpinion.get(op.title)!.add(person.name);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return { gameToNegative, gameToPositiveOpinion };
|
||||
}, [people, selectedPeople]);
|
||||
|
||||
const titlesEveryoneWouldPlay = useMemo(() => {
|
||||
return Array.from(gameToNegative.entries())
|
||||
.filter(([, players]) => players.size === 0)
|
||||
.map(([game]) => game);
|
||||
}, [gameToNegative]);
|
||||
|
||||
useEffect(() => {
|
||||
const titlesToFetch = titlesEveryoneWouldPlay.filter(
|
||||
(title) => !metaDataRef.current[title]
|
||||
);
|
||||
if (titlesToFetch.length === 0) return;
|
||||
|
||||
const gamesToFetch = GetGameInfoRequest.encode(
|
||||
GetGameInfoRequest.create({
|
||||
games: titlesToFetch,
|
||||
})
|
||||
).finish();
|
||||
|
||||
apiFetch("/api/games/batch", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/octet-stream" },
|
||||
body: gamesToFetch,
|
||||
})
|
||||
.then((res) => res.arrayBuffer())
|
||||
.then((buffer) => {
|
||||
const list = GameInfoResponse.decode(new Uint8Array(buffer));
|
||||
list.games.forEach((game) => {
|
||||
metaDataRef.current[game.title] = game;
|
||||
});
|
||||
// Trigger a re-render to update filteredGames
|
||||
setFetchedTitles([...titlesToFetch]);
|
||||
})
|
||||
.catch((err) => console.error("Failed to fetch game metadata:", err));
|
||||
}, [titlesEveryoneWouldPlay]);
|
||||
|
||||
const filteredGames = useMemo(() => {
|
||||
if (selectedPeople.size === 0) return [];
|
||||
|
||||
const games = titlesEveryoneWouldPlay
|
||||
.filter((title) => metaDataRef.current[title])
|
||||
.map((title) => metaDataRef.current[title]);
|
||||
|
||||
return filterByPlayerCount(games, selectedPeople.size);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [titlesEveryoneWouldPlay, selectedPeople.size, fetchedTitles]);
|
||||
|
||||
return { filteredGames, gameToPositive: gameToPositiveOpinion };
|
||||
}
|
||||
|
||||
function filterByPlayerCount(
|
||||
games: GameProto[],
|
||||
playerCount: number
|
||||
): string[] {
|
||||
return games
|
||||
.filter(
|
||||
(game) => game.maxPlayers >= playerCount && game.minPlayers <= playerCount
|
||||
)
|
||||
.map((game) => game.title);
|
||||
}
|
||||
Reference in New Issue
Block a user