From 0616866108fd05fd615ed8630c69afe319e1de52 Mon Sep 17 00:00:00 2001 From: Zed Date: Fri, 31 Jul 2026 00:28:52 +0200 Subject: [PATCH] Stats : records (meilleur score par jeu, selon le sens du jeu) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nouvelle section "Meilleurs scores" : par jeu, le meilleur score en une partie et le joueur qui l'a réalisé — le plus haut pour les jeux positifs, le plus bas pour les jeux négatifs (Skyjo, Cabo, Odin). Respecte les filtres. Co-Authored-By: Claude Opus 4.8 --- src/pages/Stats/index.tsx | 95 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/src/pages/Stats/index.tsx b/src/pages/Stats/index.tsx index 666f002..502c58b 100644 --- a/src/pages/Stats/index.tsx +++ b/src/pages/Stats/index.tsx @@ -3,10 +3,13 @@ import { useLiveQuery } from "dexie-react-hooks"; import { db } from "../../database/db"; import { useProfileStore } from "../../stores/profileStore"; import { useAllGames } from "../../games"; +import { calculatePlayerTotalScore } from "../../utils/scoring"; +import { GameConfig } from "../../types"; import { Card, CardContent } from "../../components/ui/card"; import { Badge } from "../../components/ui/badge"; import { PageHeader } from "../../components/PageHeader"; -import { Trophy, Clock, Gamepad2, Medal, Filter } from "lucide-react"; +import * as Icons from "lucide-react"; +import { Trophy, Clock, Gamepad2, Medal, Filter, Crown } from "lucide-react"; import { Avatar } from "../../components/ui/avatar"; import { motion } from "framer-motion"; @@ -200,6 +203,37 @@ export default function Statistics() { ? leaderboard.filter((stat) => selectedPlayerIds.includes(stat.id)) : leaderboard; + // 5. Records: best single-game score per game, according to the game's + // scoring direction (highest for "positive", lowest for "negative"). + type GameRecord = { + game: GameConfig; + score: number; + name: string; + avatar?: string; + }; + const records: GameRecord[] = []; + games.forEach((game) => { + const lowerIsBetter = game.scoreType === "negative"; + let best: { score: number; name: string; avatar?: string } | undefined; + finishedGames + .filter((s) => s.gameId === game.id) + .forEach((session) => { + session.players.forEach((p) => { + const score = calculatePlayerTotalScore( + p.id, + session.rounds, + game, + true, + ); + const better = + best === undefined || + (lowerIsBetter ? score < best.score : score > best.score); + if (better) best = { score, name: p.name, avatar: p.avatar }; + }); + }); + if (best) records.push({ game, ...best }); + }); + return (
@@ -427,6 +461,65 @@ export default function Statistics() {
)} + + {/* Records: highest score per game */} + {records.length > 0 && ( +
+

+ + Meilleurs scores +

+
+ {records.map((r, index) => { + const Icon = + (Icons as any)[r.game.icon || "Box"] || Icons.Box; + return ( + +
+ {r.game.imagePath ? ( + {r.game.name} + ) : ( + + )} +
+
+

+ {r.game.name} +

+

+ + {r.name} +

+
+ + {r.score} + +
+ ); + })} +
+
+ )} ); }