From 6cdcbfe0dbe26bfcec711699b4c30b57079b0352 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Fri, 30 Jan 2026 22:51:04 +0100 Subject: [PATCH] feat(tournament): enhance tournament group handling for pools and classes - Updated TournamentService to manage participant class IDs more effectively in pooled groups, ensuring accurate statistics and match handling. - Refactored TournamentGroupsTab and TournamentPlacementsTab components to utilize a new method for retrieving group rankings based on class ID, improving data organization. - Adjusted getLivePosition method to accommodate group objects, enhancing flexibility in live match updates. - Improved group ranking logic to support multiple entries per group and class, ensuring accurate display of tournament standings. --- backend/services/tournamentService.js | 41 +++++++++++++++---- .../tournament/TournamentGroupsTab.vue | 19 +++++---- .../tournament/TournamentPlacementsTab.vue | 5 ++- frontend/src/views/TournamentTab.vue | 8 +++- 4 files changed, 53 insertions(+), 20 deletions(-) diff --git a/backend/services/tournamentService.js b/backend/services/tournamentService.js index 5b7b3c3..3901d5c 100644 --- a/backend/services/tournamentService.js +++ b/backend/services/tournamentService.js @@ -1656,12 +1656,30 @@ class TournamentService { } classGroups.forEach((g, idx) => { - // Berechne Rankings für diese Gruppe + // Bei zusammengeführten Klassen (Pool): Teilnehmer-classId pro ID + const participantIdToClassId = {}; + for (const tm of g.tournamentGroupMembers || []) { + participantIdToClassId[tm.id] = tm.classId ?? g.classId; + } + for (const ext of g.externalGroupMembers || []) { + participantIdToClassId[ext.id] = ext.classId ?? g.classId; + } + const classIdsInGroup = [...new Set(Object.values(participantIdToClassId).filter(c => c != null))]; + const isPool = !!g.poolId || classIdsInGroup.length > 1; + const participantClassIds = isPool && classIdsInGroup.length > 0 ? classIdsInGroup : [g.classId]; + + participantClassIds.forEach((participantClassId) => { const stats = {}; - + const effectiveClassId = isPool ? participantClassId : g.classId; + if (isDoubles && pairingsByGroup[g.id]) { - // Bei Doppel: Verwende Paarungen + // Bei Doppel: Verwende Paarungen (bei Pool nur Paarungen dieser Klasse) for (const pairing of pairingsByGroup[g.id]) { + if (isPool) { + const c1 = pairing.member1?.classId ?? pairing.external1?.classId ?? g.classId; + const c2 = pairing.member2?.classId ?? pairing.external2?.classId ?? g.classId; + if (c1 !== effectiveClassId || c2 !== effectiveClassId) continue; + } const player1Name = pairing.member1?.member ? `${pairing.member1.member.firstName} ${pairing.member1.member.lastName}` : pairing.external1 @@ -1695,9 +1713,9 @@ class TournamentService { }; } } else { - // Bei Einzel: Verwende einzelne Spieler - // Interne Teilnehmer + // Bei Einzel: Verwende einzelne Spieler (bei Pool nur Teilnehmer dieser Klasse) for (const tm of g.tournamentGroupMembers || []) { + if (isPool && (tm.classId ?? g.classId) !== effectiveClassId) continue; stats[tm.id] = { id: tm.id, name: `${tm.member.firstName} ${tm.member.lastName}`, @@ -1714,9 +1732,8 @@ class TournamentService { matchesLost: 0 }; } - - // Externe Teilnehmer for (const ext of g.externalGroupMembers || []) { + if (isPool && (ext.classId ?? g.classId) !== effectiveClassId) continue; stats[ext.id] = { id: ext.id, name: `${ext.firstName} ${ext.lastName}`, @@ -1735,8 +1752,13 @@ class TournamentService { } } - // Berechne Statistiken aus Matches + // Berechne Statistiken aus Matches (bei Pool nur Spiele innerhalb derselben Klasse) for (const m of groupMatches.filter(m => m.groupId === g.id)) { + if (isPool) { + const c1 = participantIdToClassId[m.player1Id]; + const c2 = participantIdToClassId[m.player2Id]; + if (c1 !== effectiveClassId || c2 !== effectiveClassId) continue; + } if (isDoubles) { // Bei Doppel: Finde die Paarungen für player1Id und player2Id const pairing1Key = Object.keys(stats).find(key => @@ -2020,10 +2042,11 @@ class TournamentService { result.push({ groupId: g.id, - classId: g.classId, + classId: effectiveClassId, groupNumber: idx + 1, // Nummer innerhalb der Klasse participants: participantsWithPosition }); + }); // participantClassIds.forEach }); } diff --git a/frontend/src/components/tournament/TournamentGroupsTab.vue b/frontend/src/components/tournament/TournamentGroupsTab.vue index 63eae3e..72042d5 100644 --- a/frontend/src/components/tournament/TournamentGroupsTab.vue +++ b/frontend/src/components/tournament/TournamentGroupsTab.vue @@ -118,14 +118,14 @@ {{ $t('tournaments.sets') }} {{ $t('tournaments.diff') }} {{ $t('tournaments.pointsRatio') }} - + G{{ String.fromCharCode(96 + group.groupNumber) }}{{ idx + 1 }} {{ $t('tournaments.livePosition') }} - + G{{ String.fromCharCode(96 + group.groupNumber) }}{{ idx + 1 }} {{ pl.position }}. {{ pl.name }}{{ $t('tournaments.gaveUp') }} @@ -140,7 +140,7 @@ ({{ (Math.abs(pl.pointsWon || 0) - Math.abs(pl.pointsLost || 0)) >= 0 ? '+' : '' }}{{ Math.abs(pl.pointsWon || 0) - Math.abs(pl.pointsLost || 0) }}) - @@ -151,7 +151,7 @@ - - {{ getLivePosition(pl.id, group.groupId) }}. + {{ getLivePosition(pl.id, group) }}. @@ -285,6 +285,10 @@ export default { } }, methods: { + groupRankingsForGroup(group) { + const key = `${group.groupId}-${group.classId ?? 'null'}`; + return this.groupRankings[key] || []; + }, filterMatchesByClass(matches) { // Wenn keine Klasse ausgewählt ist (null), zeige alle if (this.selectedViewClass === null || this.selectedViewClass === undefined) { @@ -403,8 +407,9 @@ export default { return classes; }, - getLivePosition(playerId, groupId) { - const groupPlayers = this.groupRankings[groupId] || []; + getLivePosition(playerId, group) { + const groupId = group && typeof group === 'object' ? group.groupId : group; + const groupPlayers = this.groupRankingsForGroup && group && typeof group === 'object' ? this.groupRankingsForGroup(group) : (this.groupRankings[groupId] || []); const liveStats = groupPlayers.map(player => { let livePoints = player.points || 0; let liveSetsWon = player.setsWon || 0; @@ -455,7 +460,7 @@ export default { }); const position = liveStats.findIndex(p => p.id === playerId) + 1; - return position || groupPlayers.findIndex(p => p.id === playerId) + 1; + return position || (groupPlayers.findIndex(p => p.id === playerId) + 1) || 0; }, handleMatchClick(player1Id, player2Id, groupId) { // Highlight das Match diff --git a/frontend/src/components/tournament/TournamentPlacementsTab.vue b/frontend/src/components/tournament/TournamentPlacementsTab.vue index d85c9ba..e857122 100644 --- a/frontend/src/components/tournament/TournamentPlacementsTab.vue +++ b/frontend/src/components/tournament/TournamentPlacementsTab.vue @@ -409,10 +409,11 @@ export default { }, groupPlacements() { const placements = []; - // Primär: aus groups + groupRankings + // Primär: aus groups + groupRankings (Schlüssel groupId-classId bei Pool) if ((this.groups || []).length > 0) { this.groups.forEach(group => { - const rankings = this.groupRankings[group.groupId] || []; + const key = `${group.groupId}-${group.classId ?? 'null'}`; + const rankings = this.groupRankings[key] || []; if (rankings.length > 0) { placements.push({ groupId: group.groupId, diff --git a/frontend/src/views/TournamentTab.vue b/frontend/src/views/TournamentTab.vue index 845f812..4446a2a 100644 --- a/frontend/src/views/TournamentTab.vue +++ b/frontend/src/views/TournamentTab.vue @@ -479,10 +479,11 @@ export default { }, groupRankings() { - // Die Teilnehmer kommen bereits sortiert vom Backend mit allen benötigten Statistiken + // Schlüssel: groupId-classId (bei Pool mehrere Einträge pro Gruppe, pro Klasse getrennt) const rankings = {}; this.groups.forEach(g => { - rankings[g.groupId] = g.participants.map(p => ({ + const key = `${g.groupId}-${g.classId ?? 'null'}`; + rankings[key] = g.participants.map(p => ({ id: p.id, name: p.name, seeded: p.seeded || false, @@ -502,6 +503,9 @@ export default { }); return rankings; }, + groupRankingsKey(groupId, classId) { + return `${groupId}-${classId ?? 'null'}`; + }, // Mapping von groupId zu groupNumber für die Teilnehmer-Auswahl groupIdToNumberMap() {