From 003b8fd3bc9156b22cf58be6f2223102ad01fe2a Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 8 Apr 2026 14:01:47 +0200 Subject: [PATCH] feat(TournamentStats): update age class filtering UI and logic in InternalTournamentStats component - Replaced age class filtering with band and gender options for improved user selection. - Introduced new methods for handling band and gender checkbox changes, enhancing the filtering logic. - Updated the component's state management to accommodate selected bands and genders. - Enhanced localization strings to support new filtering options, improving user accessibility and understanding. --- .../tournament/InternalTournamentStats.vue | 220 +++++++++++++----- frontend/src/i18n/locales/de.json | 2 + frontend/src/i18n/locales/en-GB.json | 2 + 3 files changed, 161 insertions(+), 63 deletions(-) diff --git a/frontend/src/components/tournament/InternalTournamentStats.vue b/frontend/src/components/tournament/InternalTournamentStats.vue index ab89784a..2384695a 100644 --- a/frontend/src/components/tournament/InternalTournamentStats.vue +++ b/frontend/src/components/tournament/InternalTournamentStats.vue @@ -35,7 +35,7 @@

{{ $t('tournaments.internalStatsPointsExplain') }}

-
+
{{ $t('tournaments.internalStatsAgeFilter') }}
-
- +
+
+
{{ $t('tournaments.internalStatsFilterAgeBands') }}
+
+ +
+
+
+
{{ $t('tournaments.internalStatsFilterGenderColumn') }}
+
+ +
+
@@ -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",