feat(TournamentStats): enhance internal tournament statistics with member profile integration
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 37s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 37s
- Integrated member profile data (birth date and gender) into the internal tournament statistics calculations for more accurate age class filtering. - Removed deprecated age class filtering functions and streamlined the statistics computation process. - Updated the InternalTournamentStats component to reflect changes in age class options and improved sorting logic. - Enhanced localization strings across multiple languages to support new terminology related to age classes and gender, improving user accessibility and understanding.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { memberToCanonicalTtBucket } from '../utils/ttInternalStatsBuckets.js';
|
||||
|
||||
/**
|
||||
* Statistik-Werte für interne Einzel-Turniere (Gruppenphase + K.-o.-Runde).
|
||||
*
|
||||
@@ -6,6 +8,7 @@
|
||||
* 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).
|
||||
* Alters-/Geschlechtsfilter: pro Spieler aus Vereinsmitglied Geburtsdatum + Geschlecht (nicht aus Turnierklassen-Name).
|
||||
*/
|
||||
|
||||
export function parseWinnerFromMatch(match) {
|
||||
@@ -81,35 +84,6 @@ export function groupPercentFromRankings(rankings, nInGroup) {
|
||||
return map;
|
||||
}
|
||||
|
||||
/** Schlüssel für Gruppen/Spiele ohne zugeordnete Klasse (classId null) */
|
||||
export const NO_CLASS_AGE_KEY = 'ak|__noclass__';
|
||||
|
||||
/**
|
||||
* Stabiler Filter-Schlüssel aus einer Einzel-Klasse (Geburtsjahre + Geschlecht).
|
||||
* @param {object} classRow – TournamentClass-Plain
|
||||
* @returns {string|null} null bei Doppel
|
||||
*/
|
||||
export function ageClassFilterKey(classRow) {
|
||||
if (!classRow || classRow.isDoubles) return null;
|
||||
const min = classRow.minBirthYear ?? classRow.min_birth_year ?? null;
|
||||
const max = classRow.maxBirthYear ?? classRow.max_birth_year ?? null;
|
||||
const g = classRow.gender ?? '';
|
||||
return `ak|${min ?? ''}|${max ?? ''}|${g}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number|null|undefined} classIdNum
|
||||
* @param {Array<object>} classes
|
||||
* @returns {string|null} null wenn unbekannte ID oder Doppel-Klasse
|
||||
*/
|
||||
export function ageKeyForClassSlice(classIdNum, classes) {
|
||||
if (classIdNum == null || classIdNum === undefined) return NO_CLASS_AGE_KEY;
|
||||
const c = (classes || []).find((x) => Number(x.id) === Number(classIdNum));
|
||||
if (!c) return null;
|
||||
if (c.isDoubles) return null;
|
||||
return ageClassFilterKey(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} opts
|
||||
* @param {Array} opts.groups – Aus getGroupsWithParticipants (participants: flache Objekte mit id, position, isExternal, …)
|
||||
@@ -117,7 +91,9 @@ export function ageKeyForClassSlice(classIdNum, classes) {
|
||||
* @param {Array<{ id: number, isDoubles?: boolean }>} opts.classes
|
||||
* @param {Map<number, number>} opts.tmToMemberId – Turnier-Mitglied-ID -> Vereins-Mitglied-ID
|
||||
* @param {Map<number, { firstName: string, lastName: string }>} [opts.tmToName] – optional Namen
|
||||
* @param {Set<string>|null} [opts.allowedAgeKeys] – nur diese Altersklassen-Schlüssel werten; null = alle
|
||||
* @param {Map<number, { birthDate?: string|null, gender?: string|null }>} [opts.tmToMemberProfile] – pro TM: Mitglied Geburtsdatum + Geschlecht (für Filter)
|
||||
* @param {Set<string>|null} [opts.allowedAgeKeys] – tt|9|female … tt|adult|open; null = alle
|
||||
* @param {string} [opts.tournamentDateIso] – Turnierdatum für J/Erwachsenen-Zuordnung
|
||||
* @returns {Map<number, { points: number, firstName: string, lastName: string }>} Vereins-Mitglied-ID -> Aggregation
|
||||
*/
|
||||
export function computeInternalSinglesStatsForTournament({
|
||||
@@ -126,7 +102,9 @@ export function computeInternalSinglesStatsForTournament({
|
||||
classes,
|
||||
tmToMemberId,
|
||||
tmToName,
|
||||
tmToMemberProfile,
|
||||
allowedAgeKeys = null,
|
||||
tournamentDateIso,
|
||||
}) {
|
||||
const doublesClassIds = new Set(
|
||||
(classes || []).filter((c) => c.isDoubles).map((c) => Number(c.id))
|
||||
@@ -164,18 +142,12 @@ export function computeInternalSinglesStatsForTournament({
|
||||
return { firstName: n?.firstName || '', lastName: n?.lastName || '' };
|
||||
};
|
||||
|
||||
const profileForTm = (tmId) => tmToMemberProfile?.get(Number(tmId)) ?? null;
|
||||
|
||||
for (const classKey of classKeys) {
|
||||
const classIdNum = classKey === 'null' ? null : Number(classKey);
|
||||
if (classIdNum != null && doublesClassIds.has(classIdNum)) continue;
|
||||
|
||||
const sliceAgeKey = ageKeyForClassSlice(classIdNum, classes);
|
||||
if (sliceAgeKey == null) continue;
|
||||
|
||||
if (allowedAgeKeys != null) {
|
||||
if (allowedAgeKeys.size === 0) continue;
|
||||
if (!allowedAgeKeys.has(sliceAgeKey)) continue;
|
||||
}
|
||||
|
||||
const classGroups = (groups || []).filter((g) => {
|
||||
const gc = g.classId != null ? Number(g.classId) : null;
|
||||
const key = gc != null ? String(gc) : 'null';
|
||||
@@ -235,6 +207,12 @@ export function computeInternalSinglesStatsForTournament({
|
||||
const mid = tmToMemberId.get(Number(tmId));
|
||||
if (mid == null) continue;
|
||||
|
||||
const bucket = memberToCanonicalTtBucket(profileForTm(tmId), tournamentDateIso);
|
||||
if (allowedAgeKeys != null) {
|
||||
if (allowedAgeKeys.size === 0) continue;
|
||||
if (bucket == null || !allowedAgeKeys.has(bucket)) continue;
|
||||
}
|
||||
|
||||
const gPts = groupPointsByTm.get(tmId) ?? 0;
|
||||
const wins = koWins.get(tmId) || 0;
|
||||
const inKo = playedKo.has(tmId);
|
||||
@@ -258,7 +236,4 @@ export default {
|
||||
groupPercentFromRankings,
|
||||
computeInternalSinglesStatsForTournament,
|
||||
parseWinnerFromMatch,
|
||||
ageClassFilterKey,
|
||||
ageKeyForClassSlice,
|
||||
NO_CLASS_AGE_KEY,
|
||||
};
|
||||
|
||||
@@ -15,11 +15,8 @@ import { Op, literal } from 'sequelize';
|
||||
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
import {
|
||||
computeInternalSinglesStatsForTournament,
|
||||
ageClassFilterKey,
|
||||
NO_CLASS_AGE_KEY,
|
||||
} from './internalTournamentStatsService.js';
|
||||
import { computeInternalSinglesStatsForTournament } from './internalTournamentStatsService.js';
|
||||
import { getCanonicalTtAgeClassOptions } from '../utils/ttInternalStatsBuckets.js';
|
||||
|
||||
/** @param {unknown} val Query ageClassKeys: fehlend = alle; leer = keine; sonst kommagetrennte Schlüssel */
|
||||
function parseInternalStatsAgeClassKeys(val) {
|
||||
@@ -4326,8 +4323,6 @@ Ve // 2. Neues Turnier anlegen
|
||||
});
|
||||
const list = JSON.parse(JSON.stringify(tournaments));
|
||||
|
||||
const ageClassOptionsByKey = new Map();
|
||||
|
||||
const memberAgg = new Map();
|
||||
|
||||
const rawKeys = parseInternalStatsAgeClassKeys(ageClassKeysQuery);
|
||||
@@ -4338,48 +4333,6 @@ Ve // 2. Neues Turnier anlegen
|
||||
const groups = await this.getGroupsWithParticipants(userToken, clubId, t.id);
|
||||
const matches = await this.getTournamentMatches(userToken, clubId, t.id);
|
||||
|
||||
for (const c of classesJson) {
|
||||
if (c.isDoubles) continue;
|
||||
const k = ageClassFilterKey(c);
|
||||
if (!k) continue;
|
||||
if (!ageClassOptionsByKey.has(k)) {
|
||||
ageClassOptionsByKey.set(k, {
|
||||
key: k,
|
||||
name: c.name || '',
|
||||
minBirthYear: c.minBirthYear ?? c.min_birth_year ?? null,
|
||||
maxBirthYear: c.maxBirthYear ?? c.max_birth_year ?? null,
|
||||
gender: c.gender ?? null,
|
||||
isNoClass: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
let hasNoClassSlice = false;
|
||||
for (const g of groups || []) {
|
||||
if (g.classId == null || g.classId === undefined) {
|
||||
hasNoClassSlice = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasNoClassSlice) {
|
||||
for (const ma of matches || []) {
|
||||
if (ma.round === 'group') continue;
|
||||
if (ma.classId == null || ma.classId === undefined) {
|
||||
hasNoClassSlice = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasNoClassSlice && !ageClassOptionsByKey.has(NO_CLASS_AGE_KEY)) {
|
||||
ageClassOptionsByKey.set(NO_CLASS_AGE_KEY, {
|
||||
key: NO_CLASS_AGE_KEY,
|
||||
name: '',
|
||||
minBirthYear: null,
|
||||
maxBirthYear: null,
|
||||
gender: null,
|
||||
isNoClass: true,
|
||||
});
|
||||
}
|
||||
|
||||
let allowedAgeKeys = null;
|
||||
if (rawKeys !== null) {
|
||||
allowedAgeKeys = new Set(rawKeys);
|
||||
@@ -4398,10 +4351,11 @@ Ve // 2. Neues Turnier anlegen
|
||||
const tmList = [...tmIds];
|
||||
const tmToMemberId = new Map();
|
||||
const tmToName = new Map();
|
||||
const tmToMemberProfile = new Map();
|
||||
if (tmList.length > 0) {
|
||||
const tms = await TournamentMember.findAll({
|
||||
where: { tournamentId: t.id, id: { [Op.in]: tmList } },
|
||||
include: [{ model: Member, as: 'member', attributes: ['id', 'firstName', 'lastName'] }],
|
||||
include: [{ model: Member, as: 'member', attributes: ['id', 'firstName', 'lastName', 'birthDate', 'gender'] }],
|
||||
});
|
||||
for (const row of tms) {
|
||||
const plain = row.toJSON();
|
||||
@@ -4411,6 +4365,10 @@ Ve // 2. Neues Turnier anlegen
|
||||
firstName: plain.member.firstName || '',
|
||||
lastName: plain.member.lastName || '',
|
||||
});
|
||||
tmToMemberProfile.set(plain.id, {
|
||||
birthDate: plain.member.birthDate ?? null,
|
||||
gender: plain.member.gender ?? null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4421,7 +4379,9 @@ Ve // 2. Neues Turnier anlegen
|
||||
classes: classesJson,
|
||||
tmToMemberId,
|
||||
tmToName,
|
||||
tmToMemberProfile,
|
||||
allowedAgeKeys,
|
||||
tournamentDateIso: t.date,
|
||||
});
|
||||
|
||||
for (const [mid, row] of memberTotals) {
|
||||
@@ -4458,13 +4418,7 @@ Ve // 2. Neues Turnier anlegen
|
||||
b.averagePoints - a.averagePoints || (a.lastName || '').localeCompare(b.lastName || '', 'de')
|
||||
);
|
||||
|
||||
const ageClassOptions = [...ageClassOptionsByKey.values()].sort((a, b) => {
|
||||
if (a.isNoClass && !b.isNoClass) return 1;
|
||||
if (!a.isNoClass && b.isNoClass) return -1;
|
||||
const an = (a.name || '').localeCompare(b.name || '', 'de');
|
||||
if (an !== 0) return an;
|
||||
return (a.key || '').localeCompare(b.key || '', 'de');
|
||||
});
|
||||
const ageClassOptions = getCanonicalTtAgeClassOptions();
|
||||
|
||||
return {
|
||||
months: monthsNum,
|
||||
|
||||
147
backend/utils/ttInternalStatsBuckets.js
Normal file
147
backend/utils/ttInternalStatsBuckets.js
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Feste TT-Einzel-Filter (J9–J19, Erwachsene × Weiblich/Alle).
|
||||
* Zuordnung über **Teilnehmer/Mitglied**: Geburtsdatum + Geschlecht, Saison-Stichtag 01.01.
|
||||
* (vgl. frontend/src/utils/ttAgeClass.js)
|
||||
*/
|
||||
|
||||
const YOUTH_BANDS = [9, 11, 13, 15, 19];
|
||||
|
||||
/**
|
||||
* @param {string|undefined} isoDateStr Turnierdatum (DATEONLY)
|
||||
* @returns {number} erstes Saisonjahr (01.08.–31.07.)
|
||||
*/
|
||||
export function getSeasonStartYearFromTournamentDate(isoDateStr) {
|
||||
if (!isoDateStr) {
|
||||
const n = new Date();
|
||||
const month = n.getMonth() + 1;
|
||||
const year = n.getFullYear();
|
||||
return month >= 8 ? year : year - 1;
|
||||
}
|
||||
const d = new Date(String(isoDateStr).length <= 10 ? `${isoDateStr}T12:00:00` : isoDateStr);
|
||||
if (Number.isNaN(d.getTime())) {
|
||||
const n = new Date();
|
||||
const month = n.getMonth() + 1;
|
||||
const year = n.getFullYear();
|
||||
return month >= 8 ? year : year - 1;
|
||||
}
|
||||
const month = d.getMonth() + 1;
|
||||
const year = d.getFullYear();
|
||||
return month >= 8 ? year : year - 1;
|
||||
}
|
||||
|
||||
function getStichtagDate(seasonStartYear, classNum) {
|
||||
const y = seasonStartYear - (classNum - 1);
|
||||
return new Date(y, 0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} ms Geburtszeitpunkt
|
||||
* @param {number} seasonStartYear
|
||||
* @returns {'J9'|'J11'|'J13'|'J15'|'J19'|'adult'}
|
||||
*/
|
||||
function getExclusiveJugendLabelFromBirthTime(ms, seasonStartYear) {
|
||||
const c = (k) => getStichtagDate(seasonStartYear, k).getTime();
|
||||
const c9 = c(9);
|
||||
const c11 = c(11);
|
||||
const c13 = c(13);
|
||||
const c15 = c(15);
|
||||
const c19 = c(19);
|
||||
if (ms < c19) return 'adult';
|
||||
if (ms >= c9) return 'J9';
|
||||
if (ms >= c11 && ms < c9) return 'J11';
|
||||
if (ms >= c13 && ms < c11) return 'J13';
|
||||
if (ms >= c15 && ms < c13) return 'J15';
|
||||
if (ms >= c19 && ms < c15) return 'J19';
|
||||
return 'adult';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|Date|null|undefined} birthDate – wie Member.birthDate (Klartext nach get)
|
||||
* @returns {number|null} ms seit Epoch
|
||||
*/
|
||||
export function parseMemberBirthDateToMs(birthDate) {
|
||||
if (birthDate == null || birthDate === '') return null;
|
||||
if (birthDate instanceof Date && !Number.isNaN(birthDate.getTime())) {
|
||||
return birthDate.getTime();
|
||||
}
|
||||
const t = String(birthDate).trim();
|
||||
if (/^\d{4}-\d{2}-\d{2}/.test(t)) {
|
||||
const [a, b, c] = t.split(/[T\s]/)[0].split('-').map(Number);
|
||||
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c)) {
|
||||
const d = new Date(a, b - 1, c);
|
||||
return Number.isNaN(d.getTime()) ? null : d.getTime();
|
||||
}
|
||||
}
|
||||
if (t.includes('.')) {
|
||||
const parts = t.split('.');
|
||||
if (parts.length >= 3) {
|
||||
const day = parseInt(parts[0], 10);
|
||||
const month = parseInt(parts[1], 10);
|
||||
const year = parseInt(parts[2], 10);
|
||||
if (Number.isFinite(day) && Number.isFinite(month) && Number.isFinite(year)) {
|
||||
const d = new Date(year, month - 1, day);
|
||||
return Number.isNaN(d.getTime()) ? null : d.getTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** TT: nur Weiblich vs. Alle (alles außer female → open) */
|
||||
function memberGenderMode(gender) {
|
||||
return gender === 'female' ? 'female' : 'open';
|
||||
}
|
||||
|
||||
/**
|
||||
* Kanonischer Filter-Schlüssel aus Vereinsmitglied (Teilnehmer).
|
||||
* @param {{ birthDate?: string|Date|null, gender?: string|null }|null|undefined} memberProfile
|
||||
* @param {string|undefined} tournamentDateIso
|
||||
* @returns {string|null} z. B. tt|13|open; null ohne auswertbares Geburtsdatum
|
||||
*/
|
||||
export function memberToCanonicalTtBucket(memberProfile, tournamentDateIso) {
|
||||
if (!memberProfile) return null;
|
||||
const ms = parseMemberBirthDateToMs(memberProfile.birthDate);
|
||||
if (ms == null) return null;
|
||||
const seasonStartYear = getSeasonStartYearFromTournamentDate(tournamentDateIso);
|
||||
const jc = getExclusiveJugendLabelFromBirthTime(ms, seasonStartYear);
|
||||
const gMode = memberGenderMode(memberProfile.gender);
|
||||
if (jc === 'adult') return `tt|adult|${gMode}`;
|
||||
const n = parseInt(jc.slice(1), 10);
|
||||
if (!YOUTH_BANDS.includes(n)) return `tt|adult|${gMode}`;
|
||||
return `tt|${n}|${gMode}`;
|
||||
}
|
||||
|
||||
export function getCanonicalTtAgeClassOptions() {
|
||||
const out = [];
|
||||
for (const n of YOUTH_BANDS) {
|
||||
out.push({
|
||||
key: `tt|${n}|female`,
|
||||
band: 'youth',
|
||||
bandNum: n,
|
||||
genderMode: 'female',
|
||||
isNoClass: false,
|
||||
});
|
||||
out.push({
|
||||
key: `tt|${n}|open`,
|
||||
band: 'youth',
|
||||
bandNum: n,
|
||||
genderMode: 'open',
|
||||
isNoClass: false,
|
||||
});
|
||||
}
|
||||
out.push({
|
||||
key: 'tt|adult|female',
|
||||
band: 'adult',
|
||||
bandNum: null,
|
||||
genderMode: 'female',
|
||||
isNoClass: false,
|
||||
});
|
||||
out.push({
|
||||
key: 'tt|adult|open',
|
||||
band: 'adult',
|
||||
bandNum: null,
|
||||
genderMode: 'open',
|
||||
isNoClass: false,
|
||||
});
|
||||
return out;
|
||||
}
|
||||
@@ -35,7 +35,7 @@
|
||||
</p>
|
||||
<p class="stats-explain">{{ $t('tournaments.internalStatsPointsExplain') }}</p>
|
||||
|
||||
<fieldset v-if="sortedAgeOptions.length > 1" class="age-filter">
|
||||
<fieldset v-if="sortedAgeOptions.length" class="age-filter">
|
||||
<legend class="age-filter-legend">{{ $t('tournaments.internalStatsAgeFilter') }}</legend>
|
||||
<div class="age-filter-actions">
|
||||
<button type="button" class="age-filter-link" @click="selectAllAgeKeys" :disabled="loading">
|
||||
@@ -154,13 +154,10 @@ export default {
|
||||
sortedAgeOptions() {
|
||||
const list = [...(this.stats.ageClassOptions || [])];
|
||||
list.sort((a, b) => {
|
||||
const va = this.ageClassSortValue(a);
|
||||
const vb = this.ageClassSortValue(b);
|
||||
const va = this.ttOptionSortKey(a);
|
||||
const vb = this.ttOptionSortKey(b);
|
||||
if (va !== vb) return va - vb;
|
||||
const ga = this.tournamentGenderSortOrder(a.gender);
|
||||
const gb = this.tournamentGenderSortOrder(b.gender);
|
||||
if (ga !== gb) return ga - gb;
|
||||
return (a.name || '').localeCompare(b.name || '', 'de', { sensitivity: 'base' });
|
||||
return (a.key || '').localeCompare(b.key || '', 'de');
|
||||
});
|
||||
return list;
|
||||
},
|
||||
@@ -232,48 +229,35 @@ export default {
|
||||
this.selectedAgeKeys = [];
|
||||
this.load();
|
||||
},
|
||||
/** TT: nur Weiblich vs. Alle (offen); kein „Männlich“ mehr in Turnierklassen */
|
||||
tournamentClassGenderLabel(gender) {
|
||||
if (gender === 'female') return this.$t('tournaments.tournamentClassGenderFemale');
|
||||
/** TT: nur Weiblich vs. Alle (offen) */
|
||||
tournamentClassGenderLabelFromMode(genderMode) {
|
||||
if (genderMode === 'female') return this.$t('tournaments.tournamentClassGenderFemale');
|
||||
return this.$t('tournaments.tournamentClassGenderOpen');
|
||||
},
|
||||
tournamentGenderSortOrder(gender) {
|
||||
return gender === 'female' ? 0 : 1;
|
||||
},
|
||||
/**
|
||||
* Reihenfolge: J9 … J19 (bzw. U…), dann Erwachsene/Senioren, Sonstiges, ohne Klasse zuletzt.
|
||||
*/
|
||||
ageClassSortValue(opt) {
|
||||
if (opt.isNoClass) return 99999;
|
||||
const name = (opt.name || '').trim();
|
||||
const ju = name.match(/\bJ\s*(\d+)\b/i) || name.match(/\bU\s*(\d+)\b/i);
|
||||
if (ju) {
|
||||
const num = parseInt(ju[1], 10);
|
||||
return 100 + num;
|
||||
/** Sortierung: J9w, J9o, J11w … Erwachsene, zuletzt ohne Zuordnung */
|
||||
ttOptionSortKey(opt) {
|
||||
if (!opt) return 0;
|
||||
if (opt.isNoClass) return 1e6;
|
||||
if (opt.band === 'adult') return 5000 + (opt.genderMode === 'female' ? 0 : 1);
|
||||
if (opt.band === 'youth' && opt.bandNum != null) {
|
||||
return opt.bandNum * 10 + (opt.genderMode === 'female' ? 0 : 1);
|
||||
}
|
||||
if (/erwachsen|senioren/i.test(name)) return 9000;
|
||||
if (/damen|herren|aktive|mixed|offen|frei/i.test(name)) return 9100;
|
||||
return 5000;
|
||||
return 9999;
|
||||
},
|
||||
formatAgeOption(opt) {
|
||||
if (!opt) return '';
|
||||
if (opt.isNoClass) return this.$t('tournaments.internalStatsAgeNoClass');
|
||||
const bits = [];
|
||||
const nameTrim = (opt.name || '').trim();
|
||||
if (nameTrim) bits.push(nameTrim);
|
||||
const min = opt.minBirthYear;
|
||||
const max = opt.maxBirthYear;
|
||||
if (!nameTrim) {
|
||||
if (min != null && max != null) bits.push(`${min}–${max}`);
|
||||
else if (max != null) bits.push(`≤ ${max}`);
|
||||
else if (min != null) bits.push(`≥ ${min}`);
|
||||
if (opt.band === 'youth' && opt.bandNum != null) {
|
||||
return `J${opt.bandNum} · ${this.tournamentClassGenderLabelFromMode(opt.genderMode)}`;
|
||||
}
|
||||
bits.push(this.tournamentClassGenderLabel(opt.gender));
|
||||
return bits.join(' · ');
|
||||
if (opt.band === 'adult') {
|
||||
return `${this.$t('tournaments.internalStatsTtAdult')} · ${this.tournamentClassGenderLabelFromMode(opt.genderMode)}`;
|
||||
}
|
||||
return opt.key || '';
|
||||
},
|
||||
ageFilterPdfLine() {
|
||||
const opts = this.stats.ageClassOptions || [];
|
||||
if (opts.length <= 1) return '';
|
||||
if (opts.length === 0) return '';
|
||||
const allKeys = opts.map((o) => o.key);
|
||||
if (this.selectedAgeKeys.length === allKeys.length) {
|
||||
return this.$t('tournaments.internalStatsAgeFilterAll');
|
||||
|
||||
@@ -181,6 +181,7 @@
|
||||
"internalStatsAgeFilter": "Altersklassä & Gschlächt (Einzel)",
|
||||
"tournamentClassGenderFemale": "Weiblich",
|
||||
"tournamentClassGenderOpen": "Alli",
|
||||
"internalStatsTtAdult": "Erwachsene",
|
||||
"internalStatsAgeNoClass": "Ohni Klassäzuordnig",
|
||||
"internalStatsAgeSelectAll": "Alli",
|
||||
"internalStatsAgeSelectNone": "Keini",
|
||||
|
||||
@@ -412,6 +412,7 @@
|
||||
"internalStatsAgeFilter": "Altersklassen & Geschlecht (Einzel)",
|
||||
"tournamentClassGenderFemale": "Weiblich",
|
||||
"tournamentClassGenderOpen": "Alle",
|
||||
"internalStatsTtAdult": "Erwachsene",
|
||||
"internalStatsAgeNoClass": "Ohne Klassenzuordnung",
|
||||
"internalStatsAgeSelectAll": "Alle",
|
||||
"internalStatsAgeSelectNone": "Keine",
|
||||
|
||||
@@ -704,6 +704,7 @@
|
||||
"internalStatsAgeFilter": "Altersklassen & Geschlecht (Einzel)",
|
||||
"tournamentClassGenderFemale": "Weiblich",
|
||||
"tournamentClassGenderOpen": "Alle",
|
||||
"internalStatsTtAdult": "Erwachsene",
|
||||
"internalStatsAgeNoClass": "Ohne Klassenzuordnung",
|
||||
"internalStatsAgeSelectAll": "Alle",
|
||||
"internalStatsAgeSelectNone": "Keine",
|
||||
@@ -714,7 +715,7 @@
|
||||
"internalStatsLast6Months": "Letzte 6 Monate",
|
||||
"internalStatsLast3Months": "Letzte 3 Monate",
|
||||
"internalStatsTournamentsInPeriod": "{count} Turnier(e) im Zeitraum (ohne Minimeisterschaften).",
|
||||
"internalStatsPointsExplain": "Wertung: Pro Gruppe wird die Platzierung als Prozentzahl ausgedrückt (bei N Teilnehmern mit Platzierung: 1. = 100 %, Letzter = 0 %, dazwischen linear; gleiche Platzierung = gleicher Wert). N umfasst alle Platzierten in der Gruppe (inkl. Gäste). Bei nur einem Teilnehmer: 100 %. Wer die K.-o.-Runde erreicht, erhält den höchsten Gruppenwert der Klasse plus 1, danach je gewonnenes K.-o.-Spiel einen weiteren Punkt. Nur Vereinsmitglieder (Einzel).",
|
||||
"internalStatsPointsExplain": "Wertung: Pro Gruppe wird die Platzierung als Prozentzahl ausgedrückt (bei N Teilnehmern mit Platzierung: 1. = 100 %, Letzter = 0 %, dazwischen linear; gleiche Platzierung = gleicher Wert). N umfasst alle Platzierten in der Gruppe (inkl. Gäste). Bei nur einem Teilnehmer: 100 %. Wer die K.-o.-Runde erreicht, erhält den höchsten Gruppenwert der Klasse plus 1, danach je gewonnenes K.-o.-Spiel einen weiteren Punkt. Nur Vereinsmitglieder (Einzel). Die Filter J9–J19 / Erwachsene und Weiblich/Alle beziehen sich auf das jeweilige Mitglied (Geburtsdatum und Geschlecht laut Vereinsdaten), nicht auf die Bezeichnung der Turnierklasse.",
|
||||
"internalStatsAbsoluteRank": "Rangliste Gesamtwertung",
|
||||
"internalStatsAverageRank": "Rangliste Durchschnitt (pro Turnier)",
|
||||
"internalStatsPoints": "Summe",
|
||||
|
||||
@@ -181,6 +181,7 @@
|
||||
"internalStatsAgeFilter": "Age group & gender (singles)",
|
||||
"tournamentClassGenderFemale": "Female",
|
||||
"tournamentClassGenderOpen": "Open (all)",
|
||||
"internalStatsTtAdult": "Adults",
|
||||
"internalStatsAgeNoClass": "No class assigned",
|
||||
"internalStatsAgeSelectAll": "All",
|
||||
"internalStatsAgeSelectNone": "None",
|
||||
|
||||
@@ -362,6 +362,7 @@
|
||||
"internalStatsAgeFilter": "Age group & gender (singles)",
|
||||
"tournamentClassGenderFemale": "Female",
|
||||
"tournamentClassGenderOpen": "Open (all)",
|
||||
"internalStatsTtAdult": "Adults",
|
||||
"internalStatsAgeNoClass": "No class assigned",
|
||||
"internalStatsAgeSelectAll": "All",
|
||||
"internalStatsAgeSelectNone": "None",
|
||||
@@ -372,7 +373,7 @@
|
||||
"internalStatsLast6Months": "Last 6 months",
|
||||
"internalStatsLast3Months": "Last 3 months",
|
||||
"internalStatsTournamentsInPeriod": "{count} tournament(s) in this period (excluding mini championships).",
|
||||
"internalStatsPointsExplain": "Scoring: In each group, placement is expressed as a percentage (with N ranked players: 1st = 100%, last = 0%, linear in between; tied ranks share the same value). N counts everyone ranked in that group (including guests). With only one player: 100%. Players who reach the knockout get the highest group score in that class plus 1, then one extra point per knockout match won. Club members in singles classes only.",
|
||||
"internalStatsPointsExplain": "Scoring: In each group, placement is expressed as a percentage (with N ranked players: 1st = 100%, last = 0%, linear in between; tied ranks share the same value). N counts everyone ranked in that group (including guests). With only one player: 100%. Players who reach the knockout get the highest group score in that class plus 1, then one extra point per knockout match won. Club members in singles classes only. The J9–J19 / adults and female/open filters use each member’s birth date and gender from club records, not the tournament class name.",
|
||||
"internalStatsAbsoluteRank": "Total score ranking",
|
||||
"internalStatsAverageRank": "Average per tournament",
|
||||
"internalStatsPoints": "Total",
|
||||
|
||||
@@ -181,6 +181,7 @@
|
||||
"internalStatsAgeFilter": "Age group & gender (singles)",
|
||||
"tournamentClassGenderFemale": "Female",
|
||||
"tournamentClassGenderOpen": "Open (all)",
|
||||
"internalStatsTtAdult": "Adults",
|
||||
"internalStatsAgeNoClass": "No class assigned",
|
||||
"internalStatsAgeSelectAll": "All",
|
||||
"internalStatsAgeSelectNone": "None",
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
"internalStatsAgeFilter": "Edad y género (individual)",
|
||||
"tournamentClassGenderFemale": "Femenino",
|
||||
"tournamentClassGenderOpen": "Abierta (todos)",
|
||||
"internalStatsTtAdult": "Adultos",
|
||||
"internalStatsAgeNoClass": "Sin clase asignada",
|
||||
"internalStatsAgeSelectAll": "Todas",
|
||||
"internalStatsAgeSelectNone": "Ninguna",
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
"internalStatsAgeFilter": "Edad at kasarian (singles)",
|
||||
"tournamentClassGenderFemale": "Babae",
|
||||
"tournamentClassGenderOpen": "Bukas (lahat)",
|
||||
"internalStatsTtAdult": "Adults",
|
||||
"internalStatsAgeNoClass": "Walang klase",
|
||||
"internalStatsAgeSelectAll": "Lahat",
|
||||
"internalStatsAgeSelectNone": "Wala",
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
"internalStatsAgeFilter": "Âge et genre (simple)",
|
||||
"tournamentClassGenderFemale": "Féminin",
|
||||
"tournamentClassGenderOpen": "Tous",
|
||||
"internalStatsTtAdult": "Adultes",
|
||||
"internalStatsAgeNoClass": "Sans classe assignée",
|
||||
"internalStatsAgeSelectAll": "Tout",
|
||||
"internalStatsAgeSelectNone": "Aucune",
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
"internalStatsAgeFilter": "Età e genere (singolo)",
|
||||
"tournamentClassGenderFemale": "Femminile",
|
||||
"tournamentClassGenderOpen": "Aperta (tutti)",
|
||||
"internalStatsTtAdult": "Adulti",
|
||||
"internalStatsAgeNoClass": "Senza classe assegnata",
|
||||
"internalStatsAgeSelectAll": "Tutte",
|
||||
"internalStatsAgeSelectNone": "Nessuna",
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
"internalStatsAgeFilter": "年齢・性別(シングルス)",
|
||||
"tournamentClassGenderFemale": "女子",
|
||||
"tournamentClassGenderOpen": "オープン(全員)",
|
||||
"internalStatsTtAdult": "一般・シニア",
|
||||
"internalStatsAgeNoClass": "クラス未設定",
|
||||
"internalStatsAgeSelectAll": "すべて",
|
||||
"internalStatsAgeSelectNone": "なし",
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
"internalStatsAgeFilter": "Grupa wiekowa i płeć (singel)",
|
||||
"tournamentClassGenderFemale": "Kobiety",
|
||||
"tournamentClassGenderOpen": "Otwarta (wszyscy)",
|
||||
"internalStatsTtAdult": "Dorośli",
|
||||
"internalStatsAgeNoClass": "Bez przypisania do klasy",
|
||||
"internalStatsAgeSelectAll": "Wszystkie",
|
||||
"internalStatsAgeSelectNone": "Żadna",
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
"internalStatsAgeFilter": "รุ่นอายุและเพศ (เดี่ยว)",
|
||||
"tournamentClassGenderFemale": "หญิง",
|
||||
"tournamentClassGenderOpen": "เปิด (ทุกคน)",
|
||||
"internalStatsTtAdult": "ประเภทใหญ่",
|
||||
"internalStatsAgeNoClass": "ไม่มีคลาส",
|
||||
"internalStatsAgeSelectAll": "ทั้งหมด",
|
||||
"internalStatsAgeSelectNone": "ไม่มี",
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
"internalStatsAgeFilter": "Edad at kasarian (singles)",
|
||||
"tournamentClassGenderFemale": "Babae",
|
||||
"tournamentClassGenderOpen": "Bukas (lahat)",
|
||||
"internalStatsTtAdult": "Adults",
|
||||
"internalStatsAgeNoClass": "Walang klase",
|
||||
"internalStatsAgeSelectAll": "Lahat",
|
||||
"internalStatsAgeSelectNone": "Wala",
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
"internalStatsAgeFilter": "年龄与性别(单打)",
|
||||
"tournamentClassGenderFemale": "女子",
|
||||
"tournamentClassGenderOpen": "公开组(全体)",
|
||||
"internalStatsTtAdult": "成人组",
|
||||
"internalStatsAgeNoClass": "未分配级别",
|
||||
"internalStatsAgeSelectAll": "全选",
|
||||
"internalStatsAgeSelectNone": "全不选",
|
||||
|
||||
Reference in New Issue
Block a user