Corrige les joueurs en double dans les statistiques
Build and Publish Docker Image / build-and-push-image (push) Successful in 1m43s
Build and Publish Docker Image / build-and-push-image (push) Successful in 1m43s
Un nom saisi à la main recevait un nouvel id même si le joueur existait déjà : la même personne apparaissait plusieurs fois dans le classement, chaque ligne avec une seule partie. - NewGame : réutilise l'id (et l'avatar) du joueur existant quand le nom correspond, sauf si deux joueurs homonymes sont dans la même partie - Stats : classement agrégé par personne (nom normalisé) et non par id, y compris parties, victoires et compteurs Cabo ; les filtres par joueur suivent la même logique pour retrouver les anciennes parties Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+12
-1
@@ -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) {
|
||||
|
||||
+39
-33
@@ -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,9 +166,7 @@ export default function Statistics() {
|
||||
}
|
||||
> = {};
|
||||
|
||||
// Initialize with known players
|
||||
players.forEach((p) => {
|
||||
playerStats[p.id] = {
|
||||
const blankStat = (p: { id: string; name: string; avatar?: string }) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
played: 0,
|
||||
@@ -170,30 +175,32 @@ export default function Statistics() {
|
||||
caboSuccess: 0,
|
||||
caboFails: 0,
|
||||
kamikaze: 0,
|
||||
};
|
||||
});
|
||||
|
||||
// Initialize with known players (the roster entry provides the id + avatar)
|
||||
players.forEach((p) => {
|
||||
playerStats[keyOf(p)] = blankStat(p);
|
||||
});
|
||||
|
||||
finishedGames.forEach((session) => {
|
||||
// Map this session's player ids to their aggregation key
|
||||
const keyById = new Map<string, string>();
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user