feat(TournamentStats): refine internal tournament scoring and enhance UI features
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 36s

- Updated the scoring logic for internal tournaments to reflect percentage-based placements, improving clarity and fairness in rankings.
- Refactored the `groupPointsFromRankings` function to `groupPercentFromRankings` for better readability and accuracy in calculations.
- Enhanced the InternalTournamentStats component with a new PDF export feature and improved dialog positioning for better user experience.
- Updated localization strings across multiple languages to align with the new scoring system and UI enhancements, ensuring better accessibility and understanding for users.
This commit is contained in:
Torsten Schulz (local)
2026-04-08 11:20:46 +02:00
parent 43dbd5442a
commit c1b8b2c665
18 changed files with 294 additions and 81 deletions

View File

@@ -59,7 +59,7 @@ export const getTournaments = async (req, res) => {
}
};
/** Ranglisten interne Einzel-Turniere (Gruppen- + K.-o.-Punkte) */
/** Ranglisten interne Einzel-Turniere (Gruppen-% + K.-o.-Bonus wie im Service) */
export const getInternalTournamentStats = async (req, res) => {
const { authcode: token } = req.headers;
const { clubId } = req.params;

View File

@@ -1,10 +1,11 @@
/**
* Statistik-Punkte für interne Einzel-Turniere (Gruppenphase + K.-o.-Runde).
* Statistik-Werte für interne Einzel-Turniere (Gruppenphase + K.-o.-Runde).
*
* Gruppe: 1. Platz = 100, 2. = 99, 3. = 98, … (Platzierung aus API: 1 = bestes Ergebnis).
* Gleiche Platzierung = gleiche Punkte. Ab Platz 101 wird mit 0 gekappt.
* K.-o.: Wer die K.-o.-Runde erreicht: höchste Gruppenpunkte dieser Klasse + 1,
* danach +1 pro gewonnenes K.-o.-Spiel.
* Gruppe: Platzierung als Prozent relativ zur Feldgröße N (alle mit Platzierung > 0),
* Formel (N pos) / (N 1) × 100 für N ≥ 2 (1. = 100 %, Letzter = 0 %),
* bei N = 1: 100 %. Nur Vereinsmitglieder werden gewertet; N zählt alle Platzierten.
* K.-o.: Wer die K.-o.-Runde erreicht: höchster Gruppen-Prozentwert dieser Klasse + 1,
* danach +1 pro gewonnenes K.-o.-Spiel (wie bisher, jetzt auf %-Skala).
*/
export function parseWinnerFromMatch(match) {
@@ -50,19 +51,32 @@ export function parseWinnerFromMatch(match) {
}
/**
* @param {Array<{ position: number, id: number }>} rankings Platz 1 = bestes Ergebnis; id = Turnier-Mitglied-ID
* @returns {Map<number, number>} tournamentMemberId -> Punkte (1. Platz 100, 2. 99, …)
* @param {Array<{ position: number, id: number }>} rankings zu wertende Spieler; id = Turnier-Mitglied-ID
* @param {number} nInGroup Anzahl Teilnehmer in der Gruppe mit Platzierung > 0 (inkl. Externe)
* @returns {Map<number, number>} tournamentMemberId -> Prozent 0100 (Gleitkomma)
*/
export function groupPointsFromRankings(rankings) {
export function groupPercentFromRankings(rankings, nInGroup) {
const map = new Map();
if (!rankings || rankings.length === 0) return map;
const N = Number(nInGroup);
if (!rankings || rankings.length === 0 || !Number.isFinite(N) || N < 1) return map;
if (N === 1) {
for (const r of rankings) {
const id = r.id;
if (id == null) continue;
map.set(Number(id), 100);
}
return map;
}
for (const r of rankings) {
const pos = Number(r.position);
if (!Number.isFinite(pos) || pos < 1) continue;
const id = r.id;
if (id == null) continue;
const pts = Math.max(0, 101 - pos);
map.set(Number(id), pts);
const raw = ((N - pos) / (N - 1)) * 100;
const pct = Math.max(0, Math.min(100, raw));
map.set(Number(id), pct);
}
return map;
}
@@ -130,12 +144,15 @@ export function computeInternalSinglesStatsForTournament({
});
const groupPointsByTm = new Map();
let maxGroupPoints = 0;
let maxGroupPct = 0;
for (const g of classGroups) {
const parts = g.participants || [];
if (parts.length === 0) continue;
const nInGroup = parts.filter((p) => Number(p.position) > 0).length;
if (nInGroup < 1) continue;
const singlesRankings = parts
.filter((p) => !p.isExternal && p.id && tmToMemberId.has(Number(p.id)))
.map((p) => ({
@@ -144,10 +161,10 @@ export function computeInternalSinglesStatsForTournament({
}))
.filter((r) => r.position > 0);
const m = groupPointsFromRankings(singlesRankings);
const m = groupPercentFromRankings(singlesRankings, nInGroup);
for (const [tmId, pts] of m) {
groupPointsByTm.set(tmId, pts);
if (pts > maxGroupPoints) maxGroupPoints = pts;
if (pts > maxGroupPct) maxGroupPct = pts;
}
}
@@ -184,7 +201,7 @@ export function computeInternalSinglesStatsForTournament({
const inKo = playedKo.has(tmId);
let total;
if (inKo) {
total = maxGroupPoints + 1 + wins;
total = maxGroupPct + 1 + wins;
} else {
total = gPts;
}
@@ -199,7 +216,7 @@ export function computeInternalSinglesStatsForTournament({
}
export default {
groupPointsFromRankings,
groupPercentFromRankings,
computeInternalSinglesStatsForTournament,
parseWinnerFromMatch,
};