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 { PersonList } from "./PersonList";
|
||||||
import { PersonDetails } from "./PersonDetails";
|
import { PersonDetails } from "./PersonDetails";
|
||||||
import { GameList } from "./GameList";
|
import { GameList } from "./GameList";
|
||||||
|
import { GameFilter } from "./GameFilter";
|
||||||
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
|
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { apiFetch } from "./api";
|
import { apiFetch } from "./api";
|
||||||
@ -52,6 +53,9 @@ function App() {
|
|||||||
<Link to="/games" className="nav-link">
|
<Link to="/games" className="nav-link">
|
||||||
Games
|
Games
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link to="/filter" className="nav-link">
|
||||||
|
Filter
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={handleLogout} className="btn-secondary">
|
<button onClick={handleLogout} className="btn-secondary">
|
||||||
Logout
|
Logout
|
||||||
@ -60,6 +64,7 @@ function App() {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<PersonList people={people} />} />
|
<Route path="/" element={<PersonList people={people} />} />
|
||||||
<Route path="/games" element={<GameList />} />
|
<Route path="/games" element={<GameList />} />
|
||||||
|
<Route path="/filter" element={<GameFilter />} />
|
||||||
<Route path="/person/:name" element={<PersonDetails />} />
|
<Route path="/person/:name" element={<PersonDetails />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</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,
|
"max_players": 90,
|
||||||
"price": 0,
|
"price": 0,
|
||||||
"remote_id": 0
|
"remote_id": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Test2",
|
||||||
|
"source": 1,
|
||||||
|
"multiplayer": true,
|
||||||
|
"min_players": 1,
|
||||||
|
"max_players": 1,
|
||||||
|
"price": 0,
|
||||||
|
"remote_id": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"users": [
|
"users": [
|
||||||
{
|
{
|
||||||
"person": {
|
"person": {
|
||||||
"name": "John",
|
"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": [
|
"opinion": [
|
||||||
{
|
{
|
||||||
"title": "Naramo Nuclear Plant V2",
|
"title": "Naramo Nuclear Plant V2",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user