feat(TournamentStats): enhance internal tournament statistics with age class filtering
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 37s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 37s
- Updated the `getInternalTournamentPlayerStats` endpoint to accept age class keys for more granular statistics. - Introduced new utility functions for handling age class filtering in the internal tournament stats service. - Enhanced the InternalTournamentStats component with a new age class filter UI, allowing users to select specific age classes for their statistics. - Updated localization strings across multiple languages to support the new age class filtering feature, improving user accessibility and understanding.
This commit is contained in:
@@ -64,8 +64,9 @@ export const getInternalTournamentStats = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
const months = req.query.months;
|
||||
const ageClassKeys = req.query.ageClassKeys;
|
||||
try {
|
||||
const data = await tournamentService.getInternalTournamentPlayerStats(token, clubId, months);
|
||||
const data = await tournamentService.getInternalTournamentPlayerStats(token, clubId, months, ageClassKeys);
|
||||
res.status(200).json(data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -81,6 +81,35 @@ 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, …)
|
||||
@@ -88,6 +117,7 @@ export function groupPercentFromRankings(rankings, nInGroup) {
|
||||
* @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
|
||||
* @returns {Map<number, { points: number, firstName: string, lastName: string }>} Vereins-Mitglied-ID -> Aggregation
|
||||
*/
|
||||
export function computeInternalSinglesStatsForTournament({
|
||||
@@ -96,6 +126,7 @@ export function computeInternalSinglesStatsForTournament({
|
||||
classes,
|
||||
tmToMemberId,
|
||||
tmToName,
|
||||
allowedAgeKeys = null,
|
||||
}) {
|
||||
const doublesClassIds = new Set(
|
||||
(classes || []).filter((c) => c.isDoubles).map((c) => Number(c.id))
|
||||
@@ -137,6 +168,14 @@ export function computeInternalSinglesStatsForTournament({
|
||||
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';
|
||||
@@ -219,4 +258,7 @@ export default {
|
||||
groupPercentFromRankings,
|
||||
computeInternalSinglesStatsForTournament,
|
||||
parseWinnerFromMatch,
|
||||
ageClassFilterKey,
|
||||
ageKeyForClassSlice,
|
||||
NO_CLASS_AGE_KEY,
|
||||
};
|
||||
|
||||
@@ -15,7 +15,30 @@ import { Op, literal } from 'sequelize';
|
||||
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
import { computeInternalSinglesStatsForTournament } from './internalTournamentStatsService.js';
|
||||
import {
|
||||
computeInternalSinglesStatsForTournament,
|
||||
ageClassFilterKey,
|
||||
NO_CLASS_AGE_KEY,
|
||||
} from './internalTournamentStatsService.js';
|
||||
|
||||
/** @param {unknown} val Query ageClassKeys: fehlend = alle; leer = keine; sonst kommagetrennte Schlüssel */
|
||||
function parseInternalStatsAgeClassKeys(val) {
|
||||
if (val === undefined || val === null) return null;
|
||||
if (Array.isArray(val)) {
|
||||
const out = [];
|
||||
for (const v of val) {
|
||||
String(v)
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.forEach((k) => out.push(k));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
const s = String(val).trim();
|
||||
if (s === '') return [];
|
||||
return s.split(',').map((x) => x.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
function normalizeJsonConfig(value, label = 'config') {
|
||||
if (value == null) return {};
|
||||
@@ -4283,7 +4306,7 @@ Ve // 2. Neues Turnier anlegen
|
||||
/**
|
||||
* Ranglisten für interne Einzel-Turniere (Punkte nach Gruppenplatz + K.-o.) über einen Zeitraum.
|
||||
*/
|
||||
async getInternalTournamentPlayerStats(userToken, clubId, months = 12) {
|
||||
async getInternalTournamentPlayerStats(userToken, clubId, months = 12, ageClassKeysQuery = null) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const m = Number(months);
|
||||
const monthsNum = [3, 6, 12].includes(m) ? m : 12;
|
||||
@@ -4303,14 +4326,65 @@ Ve // 2. Neues Turnier anlegen
|
||||
});
|
||||
const list = JSON.parse(JSON.stringify(tournaments));
|
||||
|
||||
const ageClassOptionsByKey = new Map();
|
||||
|
||||
const memberAgg = new Map();
|
||||
|
||||
const rawKeys = parseInternalStatsAgeClassKeys(ageClassKeysQuery);
|
||||
|
||||
for (const t of list) {
|
||||
const classes = await this.getTournamentClasses(userToken, clubId, t.id);
|
||||
const classesJson = classes.map((c) => (c.toJSON ? c.toJSON() : c));
|
||||
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);
|
||||
}
|
||||
|
||||
const tmIds = new Set();
|
||||
for (const g of groups || []) {
|
||||
for (const p of g.participants || []) {
|
||||
@@ -4347,6 +4421,7 @@ Ve // 2. Neues Turnier anlegen
|
||||
classes: classesJson,
|
||||
tmToMemberId,
|
||||
tmToName,
|
||||
allowedAgeKeys,
|
||||
});
|
||||
|
||||
for (const [mid, row] of memberTotals) {
|
||||
@@ -4383,10 +4458,19 @@ 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');
|
||||
});
|
||||
|
||||
return {
|
||||
months: monthsNum,
|
||||
fromDate: fromStr,
|
||||
tournamentCount: list.length,
|
||||
ageClassOptions,
|
||||
absoluteRanking,
|
||||
averageRanking,
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</button>
|
||||
<label class="period-label">
|
||||
<span>{{ $t('tournaments.internalStatsPeriod') }}</span>
|
||||
<select v-model="months" @change="load" :disabled="loading">
|
||||
<select v-model="months" @change="onMonthsChange" :disabled="loading">
|
||||
<option :value="12">{{ $t('tournaments.internalStatsLast12Months') }}</option>
|
||||
<option :value="6">{{ $t('tournaments.internalStatsLast6Months') }}</option>
|
||||
<option :value="3">{{ $t('tournaments.internalStatsLast3Months') }}</option>
|
||||
@@ -35,6 +35,29 @@
|
||||
</p>
|
||||
<p class="stats-explain">{{ $t('tournaments.internalStatsPointsExplain') }}</p>
|
||||
|
||||
<fieldset v-if="sortedAgeOptions.length > 1" 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">
|
||||
{{ $t('tournaments.internalStatsAgeSelectAll') }}
|
||||
</button>
|
||||
<button type="button" class="age-filter-link" @click="selectNoAgeKeys" :disabled="loading">
|
||||
{{ $t('tournaments.internalStatsAgeSelectNone') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="age-filter-checkboxes">
|
||||
<label v-for="opt in sortedAgeOptions" :key="opt.key" class="age-filter-item">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="selectedAgeKeys.includes(opt.key)"
|
||||
:disabled="loading"
|
||||
@change="onAgeCheckboxChange(opt.key, $event.target.checked)"
|
||||
/>
|
||||
<span>{{ formatAgeOption(opt) }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div v-if="loading" class="stats-loading">{{ $t('common.loading') }}</div>
|
||||
<div v-else-if="error" class="stats-error">{{ error }}</div>
|
||||
<div v-else class="stats-grid">
|
||||
@@ -108,9 +131,13 @@ export default {
|
||||
dialogPosition: { x: 80, y: 80 },
|
||||
stats: {
|
||||
tournamentCount: 0,
|
||||
ageClassOptions: [],
|
||||
absoluteRanking: [],
|
||||
averageRanking: [],
|
||||
},
|
||||
selectedAgeKeys: [],
|
||||
ageFilterInitialized: false,
|
||||
pendingResetAgeSelection: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -124,9 +151,21 @@ export default {
|
||||
(!this.stats.absoluteRanking?.length && !this.stats.averageRanking?.length)
|
||||
);
|
||||
},
|
||||
sortedAgeOptions() {
|
||||
const list = [...(this.stats.ageClassOptions || [])];
|
||||
list.sort((a, b) =>
|
||||
this.formatAgeOption(a).localeCompare(this.formatAgeOption(b), 'de', {
|
||||
sensitivity: 'base',
|
||||
}),
|
||||
);
|
||||
return list;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
clubId() {
|
||||
this.ageFilterInitialized = false;
|
||||
this.selectedAgeKeys = [];
|
||||
this.pendingResetAgeSelection = false;
|
||||
if (this.modelValue) this.load();
|
||||
},
|
||||
modelValue(open) {
|
||||
@@ -154,6 +193,74 @@ export default {
|
||||
: 'internalStatsLast3Months';
|
||||
return this.$t(`tournaments.${key}`);
|
||||
},
|
||||
buildStatsParams() {
|
||||
const params = { months: this.months };
|
||||
if (this.pendingResetAgeSelection) return params;
|
||||
const opts = this.stats.ageClassOptions || [];
|
||||
const allKeys = opts.map((o) => o.key);
|
||||
if (allKeys.length === 0) return params;
|
||||
if (this.selectedAgeKeys.length === 0) {
|
||||
params.ageClassKeys = '';
|
||||
} else if (this.selectedAgeKeys.length < allKeys.length) {
|
||||
params.ageClassKeys = this.selectedAgeKeys.join(',');
|
||||
}
|
||||
return params;
|
||||
},
|
||||
onMonthsChange() {
|
||||
this.pendingResetAgeSelection = true;
|
||||
this.load();
|
||||
},
|
||||
onAgeCheckboxChange(key, checked) {
|
||||
if (checked) {
|
||||
if (!this.selectedAgeKeys.includes(key)) {
|
||||
this.selectedAgeKeys = [...this.selectedAgeKeys, key];
|
||||
}
|
||||
} else {
|
||||
this.selectedAgeKeys = this.selectedAgeKeys.filter((k) => k !== key);
|
||||
}
|
||||
this.load();
|
||||
},
|
||||
selectAllAgeKeys() {
|
||||
this.selectedAgeKeys = (this.stats.ageClassOptions || []).map((o) => o.key);
|
||||
this.load();
|
||||
},
|
||||
selectNoAgeKeys() {
|
||||
this.selectedAgeKeys = [];
|
||||
this.load();
|
||||
},
|
||||
formatAgeOption(opt) {
|
||||
if (!opt) return '';
|
||||
if (opt.isNoClass) return this.$t('tournaments.internalStatsAgeNoClass');
|
||||
const bits = [];
|
||||
if (opt.name) bits.push(opt.name);
|
||||
const min = opt.minBirthYear;
|
||||
const max = opt.maxBirthYear;
|
||||
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.gender === 'male') bits.push(this.$t('members.genderMale'));
|
||||
else if (opt.gender === 'female') bits.push(this.$t('members.genderFemale'));
|
||||
else if (opt.gender === 'mixed') bits.push(this.$t('tournaments.genderMixed'));
|
||||
return bits.join(' · ');
|
||||
},
|
||||
ageFilterPdfLine() {
|
||||
const opts = this.stats.ageClassOptions || [];
|
||||
if (opts.length <= 1) return '';
|
||||
const allKeys = opts.map((o) => o.key);
|
||||
if (this.selectedAgeKeys.length === allKeys.length) {
|
||||
return this.$t('tournaments.internalStatsAgeFilterAll');
|
||||
}
|
||||
if (this.selectedAgeKeys.length === 0) {
|
||||
return this.$t('tournaments.internalStatsAgeFilterNone');
|
||||
}
|
||||
const labels = this.selectedAgeKeys
|
||||
.map((k) => {
|
||||
const o = opts.find((x) => x.key === k);
|
||||
return o ? this.formatAgeOption(o) : k;
|
||||
})
|
||||
.join('; ');
|
||||
return `${this.$t('tournaments.internalStatsAgeFilter')}: ${labels}`;
|
||||
},
|
||||
exportPdf() {
|
||||
const t = this.$t;
|
||||
const margin = 14;
|
||||
@@ -178,6 +285,12 @@ export default {
|
||||
);
|
||||
y += 6;
|
||||
|
||||
const ageLine = this.ageFilterPdfLine();
|
||||
if (ageLine) {
|
||||
pdf.text(ageLine, margin, y);
|
||||
y += 5;
|
||||
}
|
||||
|
||||
const explain = t('tournaments.internalStatsPointsExplain');
|
||||
const explainLines = pdf.splitTextToSize(explain, pageW - margin * 2);
|
||||
pdf.setFontSize(8);
|
||||
@@ -283,12 +396,26 @@ export default {
|
||||
this.error = null;
|
||||
try {
|
||||
const res = await apiClient.get(`/tournament/internal-stats/${this.clubId}`, {
|
||||
params: { months: this.months },
|
||||
params: this.buildStatsParams(),
|
||||
});
|
||||
this.stats = res.data || {};
|
||||
const opts = this.stats.ageClassOptions || [];
|
||||
if (this.pendingResetAgeSelection) {
|
||||
this.selectedAgeKeys = opts.map((o) => o.key);
|
||||
this.pendingResetAgeSelection = false;
|
||||
this.ageFilterInitialized = true;
|
||||
} else if (!this.ageFilterInitialized && opts.length) {
|
||||
this.selectedAgeKeys = opts.map((o) => o.key);
|
||||
this.ageFilterInitialized = true;
|
||||
}
|
||||
} catch (e) {
|
||||
this.error = e?.response?.data?.error || e.message || 'Error';
|
||||
this.stats = { tournamentCount: 0, absoluteRanking: [], averageRanking: [] };
|
||||
this.stats = {
|
||||
tournamentCount: 0,
|
||||
ageClassOptions: [],
|
||||
absoluteRanking: [],
|
||||
averageRanking: [],
|
||||
};
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
@@ -360,6 +487,69 @@ export default {
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.age-filter {
|
||||
margin: 0 0 1rem;
|
||||
padding: 0.65rem 0.75rem;
|
||||
border: 1px solid var(--border-color, #e5e7eb);
|
||||
border-radius: 8px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.age-filter-legend {
|
||||
padding: 0 0.35rem;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.age-filter-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.age-filter-link {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary-color, #2563eb);
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.age-filter-link:hover:not(:disabled) {
|
||||
color: var(--primary-hover, #1d4ed8);
|
||||
}
|
||||
|
||||
.age-filter-link:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.age-filter-checkboxes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
max-height: 11rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.age-filter-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.45rem;
|
||||
font-size: 0.88rem;
|
||||
line-height: 1.35;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.age-filter-item input {
|
||||
margin-top: 0.2rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
@@ -178,6 +178,12 @@
|
||||
"internalStatsTitle": "Statistik interne Turniere (Einzel)",
|
||||
"internalStatsOpenButton": "Turnierstatistik (Einzel)",
|
||||
"internalStatsExportPdf": "Als PDF exportieren",
|
||||
"internalStatsAgeFilter": "Altersklassen (Einzel)",
|
||||
"internalStatsAgeNoClass": "Ohni Klassäzuordnig",
|
||||
"internalStatsAgeSelectAll": "Alli",
|
||||
"internalStatsAgeSelectNone": "Keini",
|
||||
"internalStatsAgeFilterAll": "Alli Altersklassä",
|
||||
"internalStatsAgeFilterNone": "Kei Altersklass usgwählt",
|
||||
"internalStatsPeriod": "Ziitruum",
|
||||
"internalStatsLast12Months": "Letscht 12 Mönet",
|
||||
"internalStatsLast6Months": "Letscht 6 Mönet",
|
||||
|
||||
@@ -409,6 +409,12 @@
|
||||
"internalStatsTitle": "Statistik interne Turniere (Einzel)",
|
||||
"internalStatsOpenButton": "Turnierstatistik (Einzel)",
|
||||
"internalStatsExportPdf": "Als PDF exportieren",
|
||||
"internalStatsAgeFilter": "Altersklassen (Einzel)",
|
||||
"internalStatsAgeNoClass": "Ohne Klassenzuordnung",
|
||||
"internalStatsAgeSelectAll": "Alle",
|
||||
"internalStatsAgeSelectNone": "Keine",
|
||||
"internalStatsAgeFilterAll": "Alle Altersklassen",
|
||||
"internalStatsAgeFilterNone": "Keine Altersklasse ausgewählt",
|
||||
"internalStatsPeriod": "Zeitraum",
|
||||
"internalStatsLast12Months": "Letzte 12 Monate",
|
||||
"internalStatsLast6Months": "Letzte 6 Monate",
|
||||
|
||||
@@ -701,6 +701,12 @@
|
||||
"internalStatsTitle": "Statistik interne Turniere (Einzel)",
|
||||
"internalStatsOpenButton": "Turnierstatistik (Einzel)",
|
||||
"internalStatsExportPdf": "Als PDF exportieren",
|
||||
"internalStatsAgeFilter": "Altersklassen (Einzel)",
|
||||
"internalStatsAgeNoClass": "Ohne Klassenzuordnung",
|
||||
"internalStatsAgeSelectAll": "Alle",
|
||||
"internalStatsAgeSelectNone": "Keine",
|
||||
"internalStatsAgeFilterAll": "Alle Altersklassen",
|
||||
"internalStatsAgeFilterNone": "Keine Altersklasse ausgewählt",
|
||||
"internalStatsPeriod": "Zeitraum",
|
||||
"internalStatsLast12Months": "Letzte 12 Monate",
|
||||
"internalStatsLast6Months": "Letzte 6 Monate",
|
||||
|
||||
@@ -178,6 +178,12 @@
|
||||
"internalStatsTitle": "Internal tournaments statistics (singles)",
|
||||
"internalStatsOpenButton": "Tournament statistics (singles)",
|
||||
"internalStatsExportPdf": "Export as PDF",
|
||||
"internalStatsAgeFilter": "Age classes (singles)",
|
||||
"internalStatsAgeNoClass": "No class assigned",
|
||||
"internalStatsAgeSelectAll": "All",
|
||||
"internalStatsAgeSelectNone": "None",
|
||||
"internalStatsAgeFilterAll": "All age classes",
|
||||
"internalStatsAgeFilterNone": "No age class selected",
|
||||
"internalStatsPeriod": "Period",
|
||||
"internalStatsLast12Months": "Last 12 months",
|
||||
"internalStatsLast6Months": "Last 6 months",
|
||||
|
||||
@@ -359,6 +359,12 @@
|
||||
"internalStatsTitle": "Internal tournaments statistics (singles)",
|
||||
"internalStatsOpenButton": "Tournament statistics (singles)",
|
||||
"internalStatsExportPdf": "Export as PDF",
|
||||
"internalStatsAgeFilter": "Age classes (singles)",
|
||||
"internalStatsAgeNoClass": "No class assigned",
|
||||
"internalStatsAgeSelectAll": "All",
|
||||
"internalStatsAgeSelectNone": "None",
|
||||
"internalStatsAgeFilterAll": "All age classes",
|
||||
"internalStatsAgeFilterNone": "No age class selected",
|
||||
"internalStatsPeriod": "Period",
|
||||
"internalStatsLast12Months": "Last 12 months",
|
||||
"internalStatsLast6Months": "Last 6 months",
|
||||
|
||||
@@ -178,6 +178,12 @@
|
||||
"internalStatsTitle": "Internal tournaments statistics (singles)",
|
||||
"internalStatsOpenButton": "Tournament statistics (singles)",
|
||||
"internalStatsExportPdf": "Export as PDF",
|
||||
"internalStatsAgeFilter": "Age classes (singles)",
|
||||
"internalStatsAgeNoClass": "No class assigned",
|
||||
"internalStatsAgeSelectAll": "All",
|
||||
"internalStatsAgeSelectNone": "None",
|
||||
"internalStatsAgeFilterAll": "All age classes",
|
||||
"internalStatsAgeFilterNone": "No age class selected",
|
||||
"internalStatsPeriod": "Period",
|
||||
"internalStatsLast12Months": "Last 12 months",
|
||||
"internalStatsLast6Months": "Last 6 months",
|
||||
|
||||
@@ -177,6 +177,12 @@
|
||||
"internalStatsTitle": "Estadísticas de torneos internos (individual)",
|
||||
"internalStatsOpenButton": "Estadísticas de torneos (individual)",
|
||||
"internalStatsExportPdf": "Exportar como PDF",
|
||||
"internalStatsAgeFilter": "Categorías de edad (individual)",
|
||||
"internalStatsAgeNoClass": "Sin clase asignada",
|
||||
"internalStatsAgeSelectAll": "Todas",
|
||||
"internalStatsAgeSelectNone": "Ninguna",
|
||||
"internalStatsAgeFilterAll": "Todas las categorías",
|
||||
"internalStatsAgeFilterNone": "Ninguna categoría seleccionada",
|
||||
"internalStatsPeriod": "Periodo",
|
||||
"internalStatsLast12Months": "Últimos 12 meses",
|
||||
"internalStatsLast6Months": "Últimos 6 meses",
|
||||
|
||||
@@ -177,6 +177,12 @@
|
||||
"internalStatsTitle": "Estadistika ng internal na paligsahan (singles)",
|
||||
"internalStatsOpenButton": "Buksan ang estadistika (singles)",
|
||||
"internalStatsExportPdf": "I-export bilang PDF",
|
||||
"internalStatsAgeFilter": "Mga pangkat ng edad (singles)",
|
||||
"internalStatsAgeNoClass": "Walang klase",
|
||||
"internalStatsAgeSelectAll": "Lahat",
|
||||
"internalStatsAgeSelectNone": "Wala",
|
||||
"internalStatsAgeFilterAll": "Lahat ng pangkat ng edad",
|
||||
"internalStatsAgeFilterNone": "Walang napiling pangkat ng edad",
|
||||
"internalStatsPeriod": "Saklaw",
|
||||
"internalStatsLast12Months": "Huling 12 buwan",
|
||||
"internalStatsLast6Months": "Huling 6 na buwan",
|
||||
|
||||
@@ -177,6 +177,12 @@
|
||||
"internalStatsTitle": "Statistiques des tournois internes (simple)",
|
||||
"internalStatsOpenButton": "Statistiques (simple)",
|
||||
"internalStatsExportPdf": "Exporter en PDF",
|
||||
"internalStatsAgeFilter": "Catégories d’âge (simple)",
|
||||
"internalStatsAgeNoClass": "Sans classe assignée",
|
||||
"internalStatsAgeSelectAll": "Tout",
|
||||
"internalStatsAgeSelectNone": "Aucune",
|
||||
"internalStatsAgeFilterAll": "Toutes les catégories",
|
||||
"internalStatsAgeFilterNone": "Aucune catégorie sélectionnée",
|
||||
"internalStatsPeriod": "Période",
|
||||
"internalStatsLast12Months": "12 derniers mois",
|
||||
"internalStatsLast6Months": "6 derniers mois",
|
||||
|
||||
@@ -177,6 +177,12 @@
|
||||
"internalStatsTitle": "Statistiche tornei interni (singolo)",
|
||||
"internalStatsOpenButton": "Statistiche tornei (singolo)",
|
||||
"internalStatsExportPdf": "Esporta PDF",
|
||||
"internalStatsAgeFilter": "Categorie di età (singolo)",
|
||||
"internalStatsAgeNoClass": "Senza classe assegnata",
|
||||
"internalStatsAgeSelectAll": "Tutte",
|
||||
"internalStatsAgeSelectNone": "Nessuna",
|
||||
"internalStatsAgeFilterAll": "Tutte le categorie",
|
||||
"internalStatsAgeFilterNone": "Nessuna categoria selezionata",
|
||||
"internalStatsPeriod": "Periodo",
|
||||
"internalStatsLast12Months": "Ultimi 12 mesi",
|
||||
"internalStatsLast6Months": "Ultimi 6 mesi",
|
||||
|
||||
@@ -177,6 +177,12 @@
|
||||
"internalStatsTitle": "内部大会の統計(シングルス)",
|
||||
"internalStatsOpenButton": "統計を表示(シングルス)",
|
||||
"internalStatsExportPdf": "PDFで出力",
|
||||
"internalStatsAgeFilter": "年齢クラス(シングルス)",
|
||||
"internalStatsAgeNoClass": "クラス未設定",
|
||||
"internalStatsAgeSelectAll": "すべて",
|
||||
"internalStatsAgeSelectNone": "なし",
|
||||
"internalStatsAgeFilterAll": "すべての年齢クラス",
|
||||
"internalStatsAgeFilterNone": "年齢クラス未選択",
|
||||
"internalStatsPeriod": "期間",
|
||||
"internalStatsLast12Months": "過去12か月",
|
||||
"internalStatsLast6Months": "過去6か月",
|
||||
|
||||
@@ -177,6 +177,12 @@
|
||||
"internalStatsTitle": "Statystyki turniejów wewnętrznych (singel)",
|
||||
"internalStatsOpenButton": "Statystyki turniejów (singel)",
|
||||
"internalStatsExportPdf": "Eksportuj do PDF",
|
||||
"internalStatsAgeFilter": "Klasy wiekowe (singel)",
|
||||
"internalStatsAgeNoClass": "Bez przypisania do klasy",
|
||||
"internalStatsAgeSelectAll": "Wszystkie",
|
||||
"internalStatsAgeSelectNone": "Żadna",
|
||||
"internalStatsAgeFilterAll": "Wszystkie klasy wiekowe",
|
||||
"internalStatsAgeFilterNone": "Nie wybrano klasy wiekowej",
|
||||
"internalStatsPeriod": "Okres",
|
||||
"internalStatsLast12Months": "Ostatnie 12 miesięcy",
|
||||
"internalStatsLast6Months": "Ostatnie 6 miesięcy",
|
||||
|
||||
@@ -177,6 +177,12 @@
|
||||
"internalStatsTitle": "สถิติการแข่งขันภายใน (เดี่ยว)",
|
||||
"internalStatsOpenButton": "เปิดสถิติ (เดี่ยว)",
|
||||
"internalStatsExportPdf": "ส่งออก PDF",
|
||||
"internalStatsAgeFilter": "รุ่นอายุ (เดี่ยว)",
|
||||
"internalStatsAgeNoClass": "ไม่มีคลาส",
|
||||
"internalStatsAgeSelectAll": "ทั้งหมด",
|
||||
"internalStatsAgeSelectNone": "ไม่มี",
|
||||
"internalStatsAgeFilterAll": "ทุกรุ่นอายุ",
|
||||
"internalStatsAgeFilterNone": "ไม่ได้เลือกรุ่นอายุ",
|
||||
"internalStatsPeriod": "ช่วงเวลา",
|
||||
"internalStatsLast12Months": "12 เดือนล่าสุด",
|
||||
"internalStatsLast6Months": "6 เดือนล่าสุด",
|
||||
|
||||
@@ -177,6 +177,12 @@
|
||||
"internalStatsTitle": "Istatistika ng internal na tournament (singles)",
|
||||
"internalStatsOpenButton": "Buksan ang istatistika (singles)",
|
||||
"internalStatsExportPdf": "I-export bilang PDF",
|
||||
"internalStatsAgeFilter": "Mga pangkat ng edad (singles)",
|
||||
"internalStatsAgeNoClass": "Walang klase",
|
||||
"internalStatsAgeSelectAll": "Lahat",
|
||||
"internalStatsAgeSelectNone": "Wala",
|
||||
"internalStatsAgeFilterAll": "Lahat ng pangkat ng edad",
|
||||
"internalStatsAgeFilterNone": "Walang napiling pangkat ng edad",
|
||||
"internalStatsPeriod": "Saklaw",
|
||||
"internalStatsLast12Months": "Huling 12 buwan",
|
||||
"internalStatsLast6Months": "Huling 6 na buwan",
|
||||
|
||||
@@ -177,6 +177,12 @@
|
||||
"internalStatsTitle": "内部锦标赛统计(单打)",
|
||||
"internalStatsOpenButton": "打开单打统计数据",
|
||||
"internalStatsExportPdf": "导出 PDF",
|
||||
"internalStatsAgeFilter": "年龄组(单打)",
|
||||
"internalStatsAgeNoClass": "未分配级别",
|
||||
"internalStatsAgeSelectAll": "全选",
|
||||
"internalStatsAgeSelectNone": "全不选",
|
||||
"internalStatsAgeFilterAll": "全部年龄组",
|
||||
"internalStatsAgeFilterNone": "未选择年龄组",
|
||||
"internalStatsPeriod": "时间范围",
|
||||
"internalStatsLast12Months": "过去 12 个月",
|
||||
"internalStatsLast6Months": "过去 6 个月",
|
||||
|
||||
Reference in New Issue
Block a user