diff --git a/src/pages/NewGame.tsx b/src/pages/NewGame.tsx index 4b89401..07bb51d 100644 --- a/src/pages/NewGame.tsx +++ b/src/pages/NewGame.tsx @@ -219,7 +219,18 @@ export default function NewGame() { .and((pl) => pl.profileId === activeProfileId && !pl.deletedAt) .first(); - if (!exists) { + if (exists) { + // Reuse the roster entry so a hand-typed name isn't counted as a new + // player in the stats. Guard against the same name twice in one game. + const idTaken = playersToSave.some((o, j) => j !== i && o.id === exists.id); + if (!idTaken) { + playersToSave[i] = { + ...p, + id: exists.id, + avatar: p.avatar ?? exists.avatar, + }; + } + } else { let finalAvatar = p.avatar; // Generate a random avatar automatically if (!finalAvatar) { diff --git a/src/pages/Stats/index.tsx b/src/pages/Stats/index.tsx index 3d9aa58..40e9b0c 100644 --- a/src/pages/Stats/index.tsx +++ b/src/pages/Stats/index.tsx @@ -66,6 +66,10 @@ export default function Statistics() { const isDeletedPlayer = (id: string, name: string) => deletedIds.has(id) || deletedNames.has(norm(name)); + // The same person can carry several ids across sessions (a hand-typed name + // gets a fresh id), so statistics are keyed on the name, not the id. + const keyOf = (p: { id: string; name: string }) => norm(p.name) || p.id; + const [timeFilter, setTimeFilter] = useState<"all" | "7d" | "30d" | "year">( "all", ); @@ -97,10 +101,13 @@ export default function Statistics() { // Filter by Players (Must include ALL selected players to allow "Head to Head" stats) if (selectedPlayerIds.length > 0) { + // Compare on the aggregation key: the same person may carry a different + // id from one session to the next. + const selectedKeys = players + .filter((p) => selectedPlayerIds.includes(p.id)) + .map(keyOf); filtered = filtered.filter((s) => - selectedPlayerIds.every((selectedId) => - s.players.some((p) => p.id === selectedId), - ), + selectedKeys.every((key) => s.players.some((p) => keyOf(p) === key)), ); } @@ -110,7 +117,7 @@ export default function Statistics() { } return filtered; - }, [allSessions, timeFilter, selectedPlayerIds, selectedGameId]); + }, [allSessions, timeFilter, selectedPlayerIds, selectedGameId, players]); // 2. Calculate Global Stats const totalGames = sessions.length; @@ -159,41 +166,41 @@ export default function Statistics() { } > = {}; - // Initialize with known players + const blankStat = (p: { id: string; name: string; avatar?: string }) => ({ + id: p.id, + name: p.name, + played: 0, + won: 0, + avatar: p.avatar, + caboSuccess: 0, + caboFails: 0, + kamikaze: 0, + }); + + // Initialize with known players (the roster entry provides the id + avatar) players.forEach((p) => { - playerStats[p.id] = { - id: p.id, - name: p.name, - played: 0, - won: 0, - avatar: p.avatar, - caboSuccess: 0, - caboFails: 0, - kamikaze: 0, - }; + playerStats[keyOf(p)] = blankStat(p); }); finishedGames.forEach((session) => { + // Map this session's player ids to their aggregation key + const keyById = new Map(); + session.players.forEach((p) => { if (isDeletedPlayer(p.id, p.name)) return; // joueur supprimé : hors stats - if (!playerStats[p.id]) { - playerStats[p.id] = { - id: p.id, - name: p.name, - played: 0, - won: 0, - avatar: p.avatar, - caboSuccess: 0, - caboFails: 0, - kamikaze: 0, - }; + const key = keyOf(p); + keyById.set(p.id, key); + if (!playerStats[key]) playerStats[key] = blankStat(p); + if (!playerStats[key].avatar && p.avatar) { + playerStats[key].avatar = p.avatar; } - playerStats[p.id].played += 1; + playerStats[key].played += 1; }); session.winnerIds?.forEach((winnerId) => { - if (playerStats[winnerId]) { - playerStats[winnerId].won += 1; + const key = keyById.get(winnerId); + if (key && playerStats[key]) { + playerStats[key].won += 1; } }); @@ -201,15 +208,11 @@ export default function Statistics() { if (session.gameId === "cabo") { session.rounds.forEach((round) => { round.scores.forEach((score) => { - if (score.caboCall === "success" && playerStats[score.playerId]) { - playerStats[score.playerId].caboSuccess += 1; - } - if (score.caboCall === "fail" && playerStats[score.playerId]) { - playerStats[score.playerId].caboFails += 1; - } - if (score.kamikaze && playerStats[score.playerId]) { - playerStats[score.playerId].kamikaze += 1; - } + const key = keyById.get(score.playerId); + if (!key || !playerStats[key]) return; + if (score.caboCall === "success") playerStats[key].caboSuccess += 1; + if (score.caboCall === "fail") playerStats[key].caboFails += 1; + if (score.kamikaze) playerStats[key].kamikaze += 1; }); }); } @@ -224,9 +227,12 @@ export default function Statistics() { .sort((a, b) => b.winrate - a.winrate || b.played - a.played); // Sort by winrate, then games played // 4. Hide unselected players from leaderboard if any are selected + const selectedKeys = players + .filter((p) => selectedPlayerIds.includes(p.id)) + .map(keyOf); const displayLeaderboard = - selectedPlayerIds.length > 0 - ? leaderboard.filter((stat) => selectedPlayerIds.includes(stat.id)) + selectedKeys.length > 0 + ? leaderboard.filter((stat) => selectedKeys.includes(keyOf(stat))) : leaderboard; // 5. Records: best single-game score per game, according to the game's