feat: implement game filter to find games all selected people would play and add supporting test data
This commit is contained in:
parent
deae3a106b
commit
af721e7716
@ -4,6 +4,7 @@ import { Login } from "./Login";
|
||||
import { PersonList } from "./PersonList";
|
||||
import { PersonDetails } from "./PersonDetails";
|
||||
import { GameList } from "./GameList";
|
||||
import { GameFilter } from "./GameFilter";
|
||||
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
|
||||
import "./App.css";
|
||||
import { apiFetch } from "./api";
|
||||
@ -52,6 +53,9 @@ function App() {
|
||||
<Link to="/games" className="nav-link">
|
||||
Games
|
||||
</Link>
|
||||
<Link to="/filter" className="nav-link">
|
||||
Filter
|
||||
</Link>
|
||||
</div>
|
||||
<button onClick={handleLogout} className="btn-secondary">
|
||||
Logout
|
||||
@ -60,6 +64,7 @@ function App() {
|
||||
<Routes>
|
||||
<Route path="/" element={<PersonList people={people} />} />
|
||||
<Route path="/games" element={<GameList />} />
|
||||
<Route path="/filter" element={<GameFilter />} />
|
||||
<Route path="/person/:name" element={<PersonDetails />} />
|
||||
</Routes>
|
||||
</div>
|
||||
|
||||
142
frontend/src/GameFilter.tsx
Normal file
142
frontend/src/GameFilter.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Person, PersonList as PersonListProto } from "../items";
|
||||
import { apiFetch } from "./api";
|
||||
|
||||
export function GameFilter() {
|
||||
const [people, setPeople] = useState<Person[]>([]);
|
||||
const [selectedPeople, setSelectedPeople] = useState<Set<string>>(new Set());
|
||||
const [filteredGames, setFilteredGames] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
apiFetch("/api")
|
||||
.then((res) => res.arrayBuffer())
|
||||
.then((buffer) => {
|
||||
const list = PersonListProto.decode(new Uint8Array(buffer));
|
||||
setPeople(list.person);
|
||||
})
|
||||
.catch((err) => console.error("Failed to fetch people:", err));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedPeople.size === 0) {
|
||||
setFilteredGames([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all games where ALL selected people have "Would Play"
|
||||
const selectedPersons = people.filter((p) => selectedPeople.has(p.name));
|
||||
|
||||
if (selectedPersons.length === 0) {
|
||||
setFilteredGames([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a map of game -> set of people who would play it
|
||||
const gameToPlayers = new Map<string, Set<string>>();
|
||||
|
||||
selectedPersons.forEach((person) => {
|
||||
person.opinion.forEach((op) => {
|
||||
if (op.wouldPlay) {
|
||||
if (!gameToPlayers.has(op.title)) {
|
||||
gameToPlayers.set(op.title, new Set());
|
||||
}
|
||||
gameToPlayers.get(op.title)!.add(person.name);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Filter games where ALL selected people would play
|
||||
const games = Array.from(gameToPlayers.entries())
|
||||
.filter(([, players]) => players.size === selectedPeople.size)
|
||||
.map(([game]) => game);
|
||||
|
||||
setFilteredGames(games);
|
||||
}, [selectedPeople, people]);
|
||||
|
||||
const togglePerson = (name: string) => {
|
||||
const newSelected = new Set(selectedPeople);
|
||||
if (newSelected.has(name)) {
|
||||
newSelected.delete(name);
|
||||
} else {
|
||||
newSelected.add(name);
|
||||
}
|
||||
setSelectedPeople(newSelected);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Game Filter</h2>
|
||||
<p style={{ color: "var(--text-muted)", marginBottom: "2rem" }}>
|
||||
Select multiple people to find games that everyone would play
|
||||
</p>
|
||||
|
||||
<div style={{ marginBottom: "3rem" }}>
|
||||
<h3>Select People</h3>
|
||||
<div className="grid-container">
|
||||
{people.map((person) => (
|
||||
<div
|
||||
key={person.name}
|
||||
className="list-item"
|
||||
style={{
|
||||
borderColor: selectedPeople.has(person.name)
|
||||
? "var(--accent-color)"
|
||||
: "var(--border-color)",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => togglePerson(person.name)}
|
||||
>
|
||||
<div
|
||||
style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedPeople.has(person.name)}
|
||||
onChange={() => togglePerson(person.name)}
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
<strong>{person.name}</strong>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.9em",
|
||||
color: "var(--text-muted)",
|
||||
marginTop: "0.5rem",
|
||||
}}
|
||||
>
|
||||
{person.opinion.length} opinion(s)
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedPeople.size > 0 && (
|
||||
<div>
|
||||
<h3>Games Everyone Would Play ({filteredGames.length})</h3>
|
||||
{filteredGames.length > 0 ? (
|
||||
<ul className="grid-container">
|
||||
{filteredGames.map((game) => (
|
||||
<li key={game} className="list-item">
|
||||
<strong>{game}</strong>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.9em",
|
||||
color: "#4caf50",
|
||||
marginTop: "0.5rem",
|
||||
}}
|
||||
>
|
||||
✓ All {selectedPeople.size} selected would play
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p style={{ color: "var(--text-muted)", fontStyle: "italic" }}>
|
||||
No games found where all selected people would play
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
41
state.json
41
state.json
@ -8,12 +8,53 @@
|
||||
"max_players": 90,
|
||||
"price": 0,
|
||||
"remote_id": 0
|
||||
},
|
||||
{
|
||||
"title": "Test2",
|
||||
"source": 1,
|
||||
"multiplayer": true,
|
||||
"min_players": 1,
|
||||
"max_players": 1,
|
||||
"price": 0,
|
||||
"remote_id": 0
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"person": {
|
||||
"name": "John",
|
||||
"opinion": [
|
||||
{
|
||||
"title": "Naramo Nuclear Plant V2",
|
||||
"would_play": true
|
||||
},
|
||||
{
|
||||
"title": "Test2",
|
||||
"would_play": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"password_hash": "$2b$12$DRvTP/ibTWULkuJJr285bumRd7SG3n5bYkDpb09Qpklqf6FeTiHkC"
|
||||
},
|
||||
{
|
||||
"person": {
|
||||
"name": "John2",
|
||||
"opinion": [
|
||||
{
|
||||
"title": "Naramo Nuclear Plant V2",
|
||||
"would_play": false
|
||||
},
|
||||
{
|
||||
"title": "Test2",
|
||||
"would_play": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"password_hash": "$2b$12$DRvTP/ibTWULkuJJr285bumRd7SG3n5bYkDpb09Qpklqf6FeTiHkC"
|
||||
},
|
||||
{
|
||||
"person": {
|
||||
"name": "John3",
|
||||
"opinion": [
|
||||
{
|
||||
"title": "Naramo Nuclear Plant V2",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user