-
-
- {{ formatAgeOption(opt) }}
-
+
+
+
{{ $t('tournaments.internalStatsFilterAgeBands') }}
+
+
+
+ {{ formatBandOnly(b) }}
+
+
+
+
+
{{ $t('tournaments.internalStatsFilterGenderColumn') }}
+
+
+
+ {{ g.label }}
+
+
+
@@ -135,7 +154,10 @@ export default {
absoluteRanking: [],
averageRanking: [],
},
- selectedAgeKeys: [],
+ /** '9'|'11'|…|'19'|'adult' */
+ selectedBandKeys: [],
+ /** 'female'|'open' */
+ selectedGenders: [],
ageFilterInitialized: false,
pendingResetAgeSelection: false,
};
@@ -151,21 +173,48 @@ export default {
(!this.stats.absoluteRanking?.length && !this.stats.averageRanking?.length)
);
},
- sortedAgeOptions() {
- const list = [...(this.stats.ageClassOptions || [])];
- list.sort((a, b) => {
- const va = this.ttOptionSortKey(a);
- const vb = this.ttOptionSortKey(b);
- if (va !== vb) return va - vb;
- return (a.key || '').localeCompare(b.key || '', 'de');
- });
- return list;
+ /** Eine Zeile pro Altersband (ohne Geschlecht), sortiert J9 … Erwachsene */
+ bandOptions() {
+ const opts = this.stats.ageClassOptions || [];
+ const byKey = new Map();
+ for (const o of opts) {
+ if (!o || o.isNoClass) continue;
+ const bandKey = o.band === 'adult' ? 'adult' : String(o.bandNum);
+ if (!byKey.has(bandKey)) {
+ byKey.set(bandKey, {
+ bandKey,
+ band: o.band,
+ bandNum: o.bandNum,
+ sortKey: o.band === 'adult' ? 1000 : Number(o.bandNum) || 0,
+ });
+ }
+ }
+ return [...byKey.values()].sort((a, b) => a.sortKey - b.sortKey);
+ },
+ genderFilterOptions() {
+ return [
+ { mode: 'female', label: this.tournamentClassGenderLabelFromMode('female') },
+ { mode: 'open', label: this.tournamentClassGenderLabelFromMode('open') },
+ ];
+ },
+ effectiveAgeClassKeys() {
+ const opts = this.stats.ageClassOptions || [];
+ const valid = new Set(opts.map((o) => o.key));
+ const keys = [];
+ for (const bk of this.selectedBandKeys) {
+ for (const g of this.selectedGenders) {
+ const key = bk === 'adult' ? `tt|adult|${g}` : `tt|${bk}|${g}`;
+ if (valid.has(key)) keys.push(key);
+ }
+ }
+ return keys;
},
},
watch: {
clubId() {
this.ageFilterInitialized = false;
- this.selectedAgeKeys = [];
+ this.selectedBandKeys = [];
+ this.selectedGenders = [];
this.pendingResetAgeSelection = false;
if (this.modelValue) this.load();
},
@@ -200,10 +249,11 @@ export default {
const opts = this.stats.ageClassOptions || [];
const allKeys = opts.map((o) => o.key);
if (allKeys.length === 0) return params;
- if (this.selectedAgeKeys.length === 0) {
+ const sel = this.effectiveAgeClassKeys;
+ if (sel.length === 0) {
params.ageClassKeys = '';
- } else if (this.selectedAgeKeys.length < allKeys.length) {
- params.ageClassKeys = this.selectedAgeKeys.join(',');
+ } else if (sel.length < allKeys.length) {
+ params.ageClassKeys = sel.join(',');
}
return params;
},
@@ -211,22 +261,53 @@ export default {
this.pendingResetAgeSelection = true;
this.load();
},
- onAgeCheckboxChange(key, checked) {
+ allBandKeysFromOptions(opts) {
+ const seen = new Set();
+ const out = [];
+ for (const o of opts || []) {
+ if (!o || o.isNoClass) continue;
+ const bk = o.band === 'adult' ? 'adult' : String(o.bandNum);
+ if (!seen.has(bk)) {
+ seen.add(bk);
+ out.push(bk);
+ }
+ }
+ out.sort((a, b) => {
+ if (a === 'adult') return 1;
+ if (b === 'adult') return -1;
+ return Number(a) - Number(b);
+ });
+ return out;
+ },
+ onBandCheckboxChange(bandKey, checked) {
if (checked) {
- if (!this.selectedAgeKeys.includes(key)) {
- this.selectedAgeKeys = [...this.selectedAgeKeys, key];
+ if (!this.selectedBandKeys.includes(bandKey)) {
+ this.selectedBandKeys = [...this.selectedBandKeys, bandKey];
}
} else {
- this.selectedAgeKeys = this.selectedAgeKeys.filter((k) => k !== key);
+ this.selectedBandKeys = this.selectedBandKeys.filter((k) => k !== bandKey);
+ }
+ this.load();
+ },
+ onGenderCheckboxChange(mode, checked) {
+ if (checked) {
+ if (!this.selectedGenders.includes(mode)) {
+ this.selectedGenders = [...this.selectedGenders, mode];
+ }
+ } else {
+ this.selectedGenders = this.selectedGenders.filter((m) => m !== mode);
}
this.load();
},
selectAllAgeKeys() {
- this.selectedAgeKeys = (this.stats.ageClassOptions || []).map((o) => o.key);
+ const opts = this.stats.ageClassOptions || [];
+ this.selectedBandKeys = this.allBandKeysFromOptions(opts);
+ this.selectedGenders = ['female', 'open'];
this.load();
},
selectNoAgeKeys() {
- this.selectedAgeKeys = [];
+ this.selectedBandKeys = [];
+ this.selectedGenders = [];
this.load();
},
/** TT: nur Weiblich vs. Alle (offen) */
@@ -234,44 +315,35 @@ export default {
if (genderMode === 'female') return this.$t('tournaments.tournamentClassGenderFemale');
return this.$t('tournaments.tournamentClassGenderOpen');
},
- /** 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);
- }
- return 9999;
- },
- formatAgeOption(opt) {
- if (!opt) return '';
- if (opt.isNoClass) return this.$t('tournaments.internalStatsAgeNoClass');
- if (opt.band === 'youth' && opt.bandNum != null) {
- return `J${opt.bandNum} · ${this.tournamentClassGenderLabelFromMode(opt.genderMode)}`;
- }
- if (opt.band === 'adult') {
- return `${this.$t('tournaments.internalStatsTtAdult')} · ${this.tournamentClassGenderLabelFromMode(opt.genderMode)}`;
- }
- return opt.key || '';
+ formatBandOnly(b) {
+ if (!b) return '';
+ if (b.band === 'youth' && b.bandNum != null) return `J${b.bandNum}`;
+ if (b.band === 'adult') return this.$t('tournaments.internalStatsTtAdult');
+ return b.bandKey || '';
},
ageFilterPdfLine() {
const opts = this.stats.ageClassOptions || [];
if (opts.length === 0) return '';
const allKeys = opts.map((o) => o.key);
- if (this.selectedAgeKeys.length === allKeys.length) {
+ const sel = this.effectiveAgeClassKeys;
+ if (sel.length === allKeys.length) {
return this.$t('tournaments.internalStatsAgeFilterAll');
}
- if (this.selectedAgeKeys.length === 0) {
+ if (sel.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}`;
+ const bandLabels = this.bandOptions
+ .filter((b) => this.selectedBandKeys.includes(b.bandKey))
+ .map((b) => this.formatBandOnly(b));
+ const genderLabels = this.selectedGenders.map((m) => this.tournamentClassGenderLabelFromMode(m));
+ const parts = [];
+ if (bandLabels.length) {
+ parts.push(`${this.$t('tournaments.internalStatsFilterAgeBands')}: ${bandLabels.join(', ')}`);
+ }
+ if (genderLabels.length) {
+ parts.push(`${this.$t('tournaments.internalStatsFilterGenderColumn')}: ${genderLabels.join(', ')}`);
+ }
+ return parts.length ? `${this.$t('tournaments.internalStatsAgeFilter')}: ${parts.join(' · ')}` : '';
},
exportPdf() {
const t = this.$t;
@@ -413,11 +485,13 @@ export default {
this.stats = res.data || {};
const opts = this.stats.ageClassOptions || [];
if (this.pendingResetAgeSelection) {
- this.selectedAgeKeys = opts.map((o) => o.key);
+ this.selectedBandKeys = this.allBandKeysFromOptions(opts);
+ this.selectedGenders = ['female', 'open'];
this.pendingResetAgeSelection = false;
this.ageFilterInitialized = true;
} else if (!this.ageFilterInitialized && opts.length) {
- this.selectedAgeKeys = opts.map((o) => o.key);
+ this.selectedBandKeys = this.allBandKeysFromOptions(opts);
+ this.selectedGenders = ['female', 'open'];
this.ageFilterInitialized = true;
}
} catch (e) {
@@ -514,6 +588,26 @@ export default {
color: #374151;
}
+.age-filter-columns {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem 1.5rem;
+ align-items: start;
+}
+
+@media (max-width: 520px) {
+ .age-filter-columns {
+ grid-template-columns: 1fr;
+ }
+}
+
+.age-filter-column-title {
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: #6b7280;
+ margin-bottom: 0.4rem;
+}
+
.age-filter-actions {
display: flex;
gap: 0.75rem;
@@ -544,7 +638,7 @@ export default {
display: flex;
flex-direction: column;
gap: 0.35rem;
- max-height: 11rem;
+ max-height: 14rem;
overflow-y: auto;
}
diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json
index a58b11f7..79fc6646 100644
--- a/frontend/src/i18n/locales/de.json
+++ b/frontend/src/i18n/locales/de.json
@@ -702,6 +702,8 @@
"internalStatsOpenButton": "Turnierstatistik (Einzel)",
"internalStatsExportPdf": "Als PDF exportieren",
"internalStatsAgeFilter": "Altersklassen & Geschlecht (Einzel)",
+ "internalStatsFilterAgeBands": "Altersklassen",
+ "internalStatsFilterGenderColumn": "Geschlecht",
"tournamentClassGenderFemale": "Weiblich",
"tournamentClassGenderOpen": "Alle",
"internalStatsTtAdult": "Erwachsene",
diff --git a/frontend/src/i18n/locales/en-GB.json b/frontend/src/i18n/locales/en-GB.json
index 203c748b..1868a04f 100644
--- a/frontend/src/i18n/locales/en-GB.json
+++ b/frontend/src/i18n/locales/en-GB.json
@@ -360,6 +360,8 @@
"internalStatsOpenButton": "Tournament statistics (singles)",
"internalStatsExportPdf": "Export as PDF",
"internalStatsAgeFilter": "Age group & gender (singles)",
+ "internalStatsFilterAgeBands": "Age classes",
+ "internalStatsFilterGenderColumn": "Gender",
"tournamentClassGenderFemale": "Female",
"tournamentClassGenderOpen": "Open (all)",
"internalStatsTtAdult": "Adults",