feat(TournamentPlacementsTab): implement knockout round ranking logic for non-qualified players
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 56s

This commit is contained in:
Torsten Schulz (local)
2026-09-16 09:46:38 +02:00
parent a01ec6487b
commit 6c05086618

View File

@@ -368,6 +368,69 @@ export default {
unique.push(p);
}
// Bei einer K.-o.-Endrunde folgen die nicht qualifizierten Spieler
// ihrer Vorrundenplatzierung. Gleiche Gruppenplätze teilen sich
// dabei auch den Endplatz (z. B. beide Gruppendritten auf Platz 5).
const preliminaryRankings = (this.groups || [])
.filter(group =>
(group.stageId === null || group.stageId === undefined)
&& String(group.classId ?? 'null') === classKey
)
.flatMap(group => this.groupRankings[group.groupId] || []);
if (preliminaryRankings.length > 0) {
const normalizedName = (firstName, lastName) =>
`${firstName || ''} ${lastName || ''}`.trim().toLocaleLowerCase('de');
const placedNames = new Set((byClass[classKey] || [])
.map(entry => entry.displayName || normalizedName(entry.member?.firstName, entry.member?.lastName))
.filter(Boolean)
.map(name => String(name).trim().toLocaleLowerCase('de')));
const knockoutEntrantNames = new Set((matchesByClass[classKey] || [])
.flatMap(match => [match.player1, match.player2])
.map(player => player?.member || player)
.map(player => normalizedName(player?.firstName, player?.lastName))
.filter(Boolean));
const remainingByGroupPlace = new Map();
preliminaryRankings.forEach(ranking => {
const name = String(ranking.name || '').trim();
const normalizedRankingName = name.toLocaleLowerCase('de');
if (!name || placedNames.has(normalizedRankingName) || knockoutEntrantNames.has(normalizedRankingName)) return;
const groupPlace = Number(ranking.position);
if (!Number.isFinite(groupPlace) || groupPlace < 1) return;
if (!remainingByGroupPlace.has(groupPlace)) {
remainingByGroupPlace.set(groupPlace, []);
}
remainingByGroupPlace.get(groupPlace).push(ranking);
});
let nextPosition = Math.max(
(byClass[classKey] || []).length,
knockoutEntrantNames.size
) + 1;
[...remainingByGroupPlace.keys()].sort((a, b) => a - b).forEach(groupPlace => {
const rankingsAtPlace = remainingByGroupPlace.get(groupPlace) || [];
rankingsAtPlace.forEach(ranking => {
const nameParts = String(ranking.name || '').trim().split(/\s+/);
(byClass[classKey] ||= []).push({
position: nextPosition,
member: {
id: ranking.id,
firstName: nameParts.slice(0, -1).join(' '),
lastName: nameParts.slice(-1).join(' '),
},
displayName: ranking.name,
classId: classKey === 'null' ? null : Number(classKey),
isExternal: ranking.isExternal || false,
});
});
nextPosition += rankingsAtPlace.length;
});
byClass[classKey] = (byClass[classKey] || []).sort((a, b) => Number(a.position) - Number(b.position));
return;
}
const maxPos = Math.max(0, ...(byClass[classKey] || []).map(e => Number(e.position) || 0));
let nextPos = maxPos + 1;
@@ -765,4 +828,3 @@ table tbody td:first-child {
width: 4em;
}