feat(MembersOverview): add season filter and enhance age group selection
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 37s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 37s
- Introduced a new season filter dropdown in the MembersOverviewSection for selecting the season start year. - Enhanced age group selection by organizing options into groups for better clarity and added new age categories. - Updated localization files to include new terms related to the season filter and age classifications across multiple languages. - Improved the overall layout and styling of the filter components for a better user experience.
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
-- Jugend-Freigaben (Schema wie backend/models/Member.js)
|
||||
-- Fehlende Spalten verursachen SequelizeDatabaseError ER_BAD_FIELD_ERROR bei getClubMembers.
|
||||
|
||||
ALTER TABLE `member`
|
||||
ADD COLUMN `adult_release_approved` TINYINT(1) NOT NULL DEFAULT 0
|
||||
COMMENT 'Jugendspieler mit Freigabe fuer Erwachsene'
|
||||
AFTER `member_form_handed_over`,
|
||||
ADD COLUMN `adult_reserve_approved` TINYINT(1) NOT NULL DEFAULT 0
|
||||
COMMENT 'Jugendspieler als Ersatz bei Erwachsenen zugelassen'
|
||||
AFTER `adult_release_approved`;
|
||||
@@ -59,16 +59,38 @@
|
||||
<span>{{ $t('members.showInactiveMembers') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>{{ $t('members.ttSeasonFilter') }}</label>
|
||||
<select
|
||||
:value="selectedSeasonStartYear"
|
||||
class="filter-select"
|
||||
@change="$emit('update:selected-season-start-year', Number($event.target.value))"
|
||||
>
|
||||
<option v-for="opt in seasonFilterOptions" :key="opt.value" :value="opt.value">
|
||||
{{ opt.label }}
|
||||
</option>
|
||||
</select>
|
||||
<p class="filter-hint filter-hint-tt">{{ $t('members.ttStichtagHint') }}</p>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>{{ $t('members.ageGroup') }}:</label>
|
||||
<select :value="selectedAgeGroup" class="filter-select" @change="$emit('update:selected-age-group', $event.target.value)">
|
||||
<option value="">{{ $t('common.all') }}</option>
|
||||
<option value="adult">{{ $t('members.adults') }}</option>
|
||||
<option value="J19">{{ $t('members.j19') }}</option>
|
||||
<option value="J17">{{ $t('members.j17') }}</option>
|
||||
<option value="J15">{{ $t('members.j15') }}</option>
|
||||
<option value="J13">{{ $t('members.j13') }}</option>
|
||||
<option value="J11">{{ $t('members.j11') }}</option>
|
||||
<option value="adult">{{ $t('members.ttAdult') }}</option>
|
||||
<optgroup :label="$t('members.ttFilterGroupJ')">
|
||||
<option value="J19">{{ $t('members.j19') }}</option>
|
||||
<option value="J15">{{ $t('members.j15') }}</option>
|
||||
<option value="J13">{{ $t('members.j13') }}</option>
|
||||
<option value="J11">{{ $t('members.j11') }}</option>
|
||||
<option value="J9">{{ $t('members.j9') }}</option>
|
||||
</optgroup>
|
||||
<optgroup :label="$t('members.ttFilterGroupM')">
|
||||
<option value="M19">{{ $t('members.m19') }}</option>
|
||||
<option value="M15">{{ $t('members.m15') }}</option>
|
||||
<option value="M13">{{ $t('members.m13') }}</option>
|
||||
<option value="M11">{{ $t('members.m11') }}</option>
|
||||
<option value="M9">{{ $t('members.m9') }}</option>
|
||||
</optgroup>
|
||||
<option value="range">{{ $t('members.ageRange') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -199,6 +221,8 @@ export default {
|
||||
selectedMemberScope: { type: String, required: true },
|
||||
showInactiveMembers: { type: Boolean, required: true },
|
||||
selectedAgeGroup: { type: String, required: true },
|
||||
selectedSeasonStartYear: { type: Number, required: true },
|
||||
seasonFilterOptions: { type: Array, required: true },
|
||||
selectedAgeFrom: { type: [String, Number], required: true },
|
||||
selectedAgeTo: { type: [String, Number], required: true },
|
||||
selectedGender: { type: String, required: true },
|
||||
@@ -221,6 +245,7 @@ export default {
|
||||
'update:selected-member-scope',
|
||||
'update:show-inactive-members',
|
||||
'update:selected-age-group',
|
||||
'update:selected-season-start-year',
|
||||
'update:selected-age-from',
|
||||
'update:selected-age-to',
|
||||
'update:selected-gender',
|
||||
@@ -297,6 +322,14 @@ export default {
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.filter-hint {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
margin: 0;
|
||||
max-width: 24rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.member-search-group {
|
||||
flex: 1 1 18rem;
|
||||
}
|
||||
|
||||
@@ -467,12 +467,26 @@
|
||||
"clearFields": "Felder leeren",
|
||||
"showInactiveMembers": "Inaktive Mitglieder anzeigen",
|
||||
"ageGroup": "Altersklasse",
|
||||
"ttSeasonFilter": "Saison (Stichtag)",
|
||||
"ttSeasonCurrentTag": "aktuell",
|
||||
"ttSeasonNextTag": "kommend",
|
||||
"ttStichtagHint": "Stichtag 1.1. (DTTB). Jungen: nur J-Klassen. Mädchen: J und M möglich.",
|
||||
"ttAgeClassCol": "AK (TT)",
|
||||
"ttAdult": "Erwachsene (kein Jugend nach Stichtag)",
|
||||
"ttFilterGroupJ": "Mädchen & Jungen (gemischt)",
|
||||
"ttFilterGroupM": "Mädchen (nur weiblich)",
|
||||
"adults": "Erwachsene (18+)",
|
||||
"j19": "J19 (19 und jünger)",
|
||||
"j19": "J19",
|
||||
"j17": "J17 (17 und jünger)",
|
||||
"j15": "J15 (15 und jünger)",
|
||||
"j13": "J13 (13 und jünger)",
|
||||
"j11": "J11 (11 und jünger)",
|
||||
"j15": "J15",
|
||||
"j13": "J13",
|
||||
"j11": "J11",
|
||||
"j9": "J9",
|
||||
"m19": "M19",
|
||||
"m15": "M15",
|
||||
"m13": "M13",
|
||||
"m11": "M11",
|
||||
"m9": "M9",
|
||||
"clearFilters": "Filter zurücksetzen",
|
||||
"imageInternet": "Bild (Inet?)",
|
||||
"testMember": "Testm.",
|
||||
|
||||
@@ -242,12 +242,26 @@
|
||||
"clearFields": "Felder leeren",
|
||||
"showInactiveMembers": "Inaktive Mitglieder anzeigen",
|
||||
"ageGroup": "Altersklasse",
|
||||
"ttSeasonFilter": "Saison (Stichtag)",
|
||||
"ttSeasonCurrentTag": "aktuell",
|
||||
"ttSeasonNextTag": "kommend",
|
||||
"ttStichtagHint": "Stichtag 1.1. (DTTB). Jungen: nur J-Klassen. Mädchen: J und M möglich.",
|
||||
"ttAgeClassCol": "AK (TT)",
|
||||
"ttAdult": "Erwachsene (kein Jugend nach Stichtag)",
|
||||
"ttFilterGroupJ": "Mädchen & Jungen (gemischt)",
|
||||
"ttFilterGroupM": "Mädchen (nur weiblich)",
|
||||
"adults": "Erwachsene (18+)",
|
||||
"j19": "J19 (19 und jünger)",
|
||||
"j19": "J19",
|
||||
"j17": "J17 (17 und jünger)",
|
||||
"j15": "J15 (15 und jünger)",
|
||||
"j13": "J13 (13 und jünger)",
|
||||
"j11": "J11 (11 und jünger)",
|
||||
"j15": "J15",
|
||||
"j13": "J13",
|
||||
"j11": "J11",
|
||||
"j9": "J9",
|
||||
"m19": "M19",
|
||||
"m15": "M15",
|
||||
"m13": "M13",
|
||||
"m11": "M11",
|
||||
"m9": "M9",
|
||||
"clearFilters": "Filter zurücksetzen",
|
||||
"imageInternet": "Bild (Inet?)",
|
||||
"testMember": "Testm.",
|
||||
|
||||
@@ -467,12 +467,26 @@
|
||||
"clearFields": "Clear fields",
|
||||
"showInactiveMembers": "Show inactive members",
|
||||
"ageGroup": "Age group",
|
||||
"ttSeasonFilter": "Season (cutoff)",
|
||||
"ttSeasonCurrentTag": "current",
|
||||
"ttSeasonNextTag": "upcoming",
|
||||
"ttStichtagHint": "Cutoff 1 Jan (DTTB). Boys: J classes only. Girls: J and M.",
|
||||
"ttAgeClassCol": "Age class (TT)",
|
||||
"ttAdult": "Adults (not youth by cutoff)",
|
||||
"ttFilterGroupJ": "Boys & girls (mixed)",
|
||||
"ttFilterGroupM": "Girls only",
|
||||
"adults": "Adults (18+)",
|
||||
"j19": "U19 (19 and younger)",
|
||||
"j19": "U19",
|
||||
"j17": "U17 (17 and younger)",
|
||||
"j15": "U15 (15 and younger)",
|
||||
"j13": "U13 (13 and younger)",
|
||||
"j11": "U11 (11 and younger)",
|
||||
"j15": "U15",
|
||||
"j13": "U13",
|
||||
"j11": "U11",
|
||||
"j9": "U9",
|
||||
"m19": "G19",
|
||||
"m15": "G15",
|
||||
"m13": "G13",
|
||||
"m11": "G11",
|
||||
"m9": "G9",
|
||||
"clearFilters": "Reset filters",
|
||||
"imageInternet": "Image (web?)",
|
||||
"testMember": "Trial",
|
||||
|
||||
@@ -742,12 +742,26 @@
|
||||
"clearFields": "Clear fields",
|
||||
"showInactiveMembers": "Show inactive members",
|
||||
"ageGroup": "Age group",
|
||||
"ttSeasonFilter": "Season (cutoff)",
|
||||
"ttSeasonCurrentTag": "current",
|
||||
"ttSeasonNextTag": "upcoming",
|
||||
"ttStichtagHint": "Cutoff 1 Jan (DTTB). Boys: J classes only. Girls: J and M.",
|
||||
"ttAgeClassCol": "Age class (TT)",
|
||||
"ttAdult": "Adults (not youth by cutoff)",
|
||||
"ttFilterGroupJ": "Boys & girls (mixed)",
|
||||
"ttFilterGroupM": "Girls only",
|
||||
"adults": "Adults (18+)",
|
||||
"j19": "U19 (19 and younger)",
|
||||
"j19": "U19",
|
||||
"j17": "U17 (17 and younger)",
|
||||
"j15": "U15 (15 and younger)",
|
||||
"j13": "U13 (13 and younger)",
|
||||
"j11": "U11 (11 and younger)",
|
||||
"j15": "U15",
|
||||
"j13": "U13",
|
||||
"j11": "U11",
|
||||
"j9": "U9",
|
||||
"m19": "G19",
|
||||
"m15": "G15",
|
||||
"m13": "G13",
|
||||
"m11": "G11",
|
||||
"m9": "G9",
|
||||
"clearFilters": "Reset filters",
|
||||
"imageInternet": "Image (web?)",
|
||||
"testMember": "Trial",
|
||||
|
||||
@@ -467,12 +467,26 @@
|
||||
"clearFields": "Clear fields",
|
||||
"showInactiveMembers": "Show inactive members",
|
||||
"ageGroup": "Age group",
|
||||
"ttSeasonFilter": "Season (cutoff)",
|
||||
"ttSeasonCurrentTag": "current",
|
||||
"ttSeasonNextTag": "upcoming",
|
||||
"ttStichtagHint": "Cutoff 1 Jan (DTTB). Boys: J classes only. Girls: J and M.",
|
||||
"ttAgeClassCol": "Age class (TT)",
|
||||
"ttAdult": "Adults (not youth by cutoff)",
|
||||
"ttFilterGroupJ": "Boys & girls (mixed)",
|
||||
"ttFilterGroupM": "Girls only",
|
||||
"adults": "Adults (18+)",
|
||||
"j19": "U19 (19 and younger)",
|
||||
"j19": "U19",
|
||||
"j17": "U17 (17 and younger)",
|
||||
"j15": "U15 (15 and younger)",
|
||||
"j13": "U13 (13 and younger)",
|
||||
"j11": "U11 (11 and younger)",
|
||||
"j15": "U15",
|
||||
"j13": "U13",
|
||||
"j11": "U11",
|
||||
"j9": "U9",
|
||||
"m19": "G19",
|
||||
"m15": "G15",
|
||||
"m13": "G13",
|
||||
"m11": "G11",
|
||||
"m9": "G9",
|
||||
"clearFilters": "Reset filters",
|
||||
"imageInternet": "Image (web?)",
|
||||
"testMember": "Trial",
|
||||
|
||||
@@ -434,12 +434,26 @@
|
||||
"clearFields": "Vaciar campos",
|
||||
"showInactiveMembers": "Mostrar miembros inactivos",
|
||||
"ageGroup": "Categoría de edad",
|
||||
"ttSeasonFilter": "Temporada (corte)",
|
||||
"ttSeasonCurrentTag": "actual",
|
||||
"ttSeasonNextTag": "próxima",
|
||||
"ttStichtagHint": "Corte el 1 de enero (DTTB). Niños: solo clases J. Niñas: J y M.",
|
||||
"ttAgeClassCol": "Cat. edad (TT)",
|
||||
"ttAdult": "Adultos (no juvenil según corte)",
|
||||
"ttFilterGroupJ": "Niños y niñas (mixto)",
|
||||
"ttFilterGroupM": "Solo niñas",
|
||||
"adults": "Adultos (20+)",
|
||||
"j19": "U19 (19 años o menos)",
|
||||
"j19": "J19",
|
||||
"j17": "U17 (17 años o menos)",
|
||||
"j15": "U15 (15 años o menos)",
|
||||
"j13": "U13 (13 años o menos)",
|
||||
"j11": "U11 (11 años o menos)",
|
||||
"j15": "J15",
|
||||
"j13": "J13",
|
||||
"j11": "J11",
|
||||
"j9": "J9",
|
||||
"m19": "M19",
|
||||
"m15": "M15",
|
||||
"m13": "M13",
|
||||
"m11": "M11",
|
||||
"m9": "M9",
|
||||
"clearFilters": "Restablecer filtros",
|
||||
"imageInternet": "Imagen (web?)",
|
||||
"testMember": "Prueba",
|
||||
|
||||
@@ -434,12 +434,26 @@
|
||||
"clearFields": "Burahin ang mga field",
|
||||
"showInactiveMembers": "Ipakita ang mga hindi aktibong miyembro",
|
||||
"ageGroup": "Pangkat ng edad",
|
||||
"ttSeasonFilter": "Season (cutoff)",
|
||||
"ttSeasonCurrentTag": "kasalukuyan",
|
||||
"ttSeasonNextTag": "susunod",
|
||||
"ttStichtagHint": "Cutoff Enero 1 (DTTB). Lalaki: J lang. Babae: J at M.",
|
||||
"ttAgeClassCol": "Edad (TT)",
|
||||
"ttAdult": "Adulto (hindi youth sa cutoff)",
|
||||
"ttFilterGroupJ": "Babae at lalaki (halo)",
|
||||
"ttFilterGroupM": "Babae lamang",
|
||||
"adults": "Adulto (20+)",
|
||||
"j19": "J19 (19 pababa)",
|
||||
"j19": "J19",
|
||||
"j17": "J17 (17 pababa)",
|
||||
"j15": "J15 (15 pababa)",
|
||||
"j13": "J13 (13 pababa)",
|
||||
"j11": "J11 (11 pababa)",
|
||||
"j15": "J15",
|
||||
"j13": "J13",
|
||||
"j11": "J11",
|
||||
"j9": "J9",
|
||||
"m19": "M19",
|
||||
"m15": "M15",
|
||||
"m13": "M13",
|
||||
"m11": "M11",
|
||||
"m9": "M9",
|
||||
"clearFilters": "I-reset ang mga filter",
|
||||
"imageInternet": "Larawan (net?)",
|
||||
"testMember": "Trial",
|
||||
|
||||
@@ -434,12 +434,26 @@
|
||||
"clearFields": "Vider les champs",
|
||||
"showInactiveMembers": "Afficher les membres inactifs",
|
||||
"ageGroup": "Catégorie d'âge",
|
||||
"ttSeasonFilter": "Saison (date limite)",
|
||||
"ttSeasonCurrentTag": "actuelle",
|
||||
"ttSeasonNextTag": "à venir",
|
||||
"ttStichtagHint": "Date limite le 1er janv. (DTTB). Garçons : classes J uniquement. Filles : J et M.",
|
||||
"ttAgeClassCol": "Classe d'âge (TT)",
|
||||
"ttAdult": "Adultes (pas jeunes selon la date limite)",
|
||||
"ttFilterGroupJ": "Filles et garçons (mixte)",
|
||||
"ttFilterGroupM": "Filles uniquement",
|
||||
"adults": "Adultes (20+)",
|
||||
"j19": "U19 (19 ans et moins)",
|
||||
"j19": "J19",
|
||||
"j17": "U17 (17 ans et moins)",
|
||||
"j15": "U15 (15 ans et moins)",
|
||||
"j13": "U13 (13 ans et moins)",
|
||||
"j11": "U11 (11 ans et moins)",
|
||||
"j15": "J15",
|
||||
"j13": "J13",
|
||||
"j11": "J11",
|
||||
"j9": "J9",
|
||||
"m19": "M19",
|
||||
"m15": "M15",
|
||||
"m13": "M13",
|
||||
"m11": "M11",
|
||||
"m9": "M9",
|
||||
"clearFilters": "Réinitialiser les filtres",
|
||||
"imageInternet": "Image (web ?)",
|
||||
"testMember": "Essai",
|
||||
|
||||
@@ -434,12 +434,26 @@
|
||||
"clearFields": "Svuota campi",
|
||||
"showInactiveMembers": "Mostra membri inattivi",
|
||||
"ageGroup": "Fascia d’età",
|
||||
"ttSeasonFilter": "Stagione (data di riferimento)",
|
||||
"ttSeasonCurrentTag": "attuale",
|
||||
"ttSeasonNextTag": "prossima",
|
||||
"ttStichtagHint": "Data di riferimento 1° gennaio (DTTB). Ragazzi: solo classi J. Ragazze: J e M.",
|
||||
"ttAgeClassCol": "Classe (TT)",
|
||||
"ttAdult": "Adulti (non giovanili secondo la data)",
|
||||
"ttFilterGroupJ": "Ragazze e ragazzi (misto)",
|
||||
"ttFilterGroupM": "Solo ragazze",
|
||||
"adults": "Adulti (20+)",
|
||||
"j19": "U19 (19 anni o meno)",
|
||||
"j19": "J19",
|
||||
"j17": "U17 (17 anni o meno)",
|
||||
"j15": "U15 (15 anni o meno)",
|
||||
"j13": "U13 (13 anni o meno)",
|
||||
"j11": "U11 (11 anni o meno)",
|
||||
"j15": "J15",
|
||||
"j13": "J13",
|
||||
"j11": "J11",
|
||||
"j9": "J9",
|
||||
"m19": "M19",
|
||||
"m15": "M15",
|
||||
"m13": "M13",
|
||||
"m11": "M11",
|
||||
"m9": "M9",
|
||||
"clearFilters": "Reimposta filtri",
|
||||
"imageInternet": "Immagine (web?)",
|
||||
"testMember": "Prova",
|
||||
|
||||
@@ -434,12 +434,26 @@
|
||||
"clearFields": "入力欄をクリア",
|
||||
"showInactiveMembers": "非アクティブメンバーを表示",
|
||||
"ageGroup": "年齢区分",
|
||||
"ttSeasonFilter": "シーズン(基準日)",
|
||||
"ttSeasonCurrentTag": "今季",
|
||||
"ttSeasonNextTag": "来季",
|
||||
"ttStichtagHint": "基準日1月1日(DTTB)。男子:Jのみ。女子:JとM。",
|
||||
"ttAgeClassCol": "年齢(TT)",
|
||||
"ttAdult": "一般(基準日でジュニアではない)",
|
||||
"ttFilterGroupJ": "男女混合",
|
||||
"ttFilterGroupM": "女子のみ",
|
||||
"adults": "一般(20歳以上)",
|
||||
"j19": "J19(19歳以下)",
|
||||
"j19": "J19",
|
||||
"j17": "J17(17歳以下)",
|
||||
"j15": "J15(15歳以下)",
|
||||
"j13": "J13(13歳以下)",
|
||||
"j11": "J11(11歳以下)",
|
||||
"j15": "J15",
|
||||
"j13": "J13",
|
||||
"j11": "J11",
|
||||
"j9": "J9",
|
||||
"m19": "M19",
|
||||
"m15": "M15",
|
||||
"m13": "M13",
|
||||
"m11": "M11",
|
||||
"m9": "M9",
|
||||
"clearFilters": "フィルターをリセット",
|
||||
"imageInternet": "画像(公開)",
|
||||
"testMember": "体験",
|
||||
|
||||
@@ -434,12 +434,26 @@
|
||||
"clearFields": "Wyczyść pola",
|
||||
"showInactiveMembers": "Pokaż nieaktywnych członków",
|
||||
"ageGroup": "Kategoria wiekowa",
|
||||
"ttSeasonFilter": "Sezon (termin)",
|
||||
"ttSeasonCurrentTag": "bieżący",
|
||||
"ttSeasonNextTag": "następny",
|
||||
"ttStichtagHint": "Termin 1.01 (DTTB). Chłopcy: tylko klasy J. Dziewczęta: J i M.",
|
||||
"ttAgeClassCol": "Kategoria (TT)",
|
||||
"ttAdult": "Dorośli (brak juniora wg terminu)",
|
||||
"ttFilterGroupJ": "Dziewczęta i chłopcy (mieszane)",
|
||||
"ttFilterGroupM": "Tylko dziewczęta",
|
||||
"adults": "Dorośli (20+)",
|
||||
"j19": "U19 (19 lat i mniej)",
|
||||
"j19": "J19",
|
||||
"j17": "U17 (17 lat i mniej)",
|
||||
"j15": "U15 (15 lat i mniej)",
|
||||
"j13": "U13 (13 lat i mniej)",
|
||||
"j11": "U11 (11 lat i mniej)",
|
||||
"j15": "J15",
|
||||
"j13": "J13",
|
||||
"j11": "J11",
|
||||
"j9": "J9",
|
||||
"m19": "M19",
|
||||
"m15": "M15",
|
||||
"m13": "M13",
|
||||
"m11": "M11",
|
||||
"m9": "M9",
|
||||
"clearFilters": "Resetuj filtry",
|
||||
"imageInternet": "Zdjęcie (www?)",
|
||||
"testMember": "Próba",
|
||||
|
||||
@@ -434,12 +434,26 @@
|
||||
"clearFields": "ล้างช่องข้อมูล",
|
||||
"showInactiveMembers": "แสดงสมาชิกที่ไม่ใช้งาน",
|
||||
"ageGroup": "กลุ่มอายุ",
|
||||
"ttSeasonFilter": "ฤดูกาล (วันตัดสิทธิ์)",
|
||||
"ttSeasonCurrentTag": "ปัจจุบัน",
|
||||
"ttSeasonNextTag": "ถัดไป",
|
||||
"ttStichtagHint": "วันตัดสิทธิ์ 1 ม.ค. (DTTB) ชาย: เฉพาะ J หญิง: J และ M",
|
||||
"ttAgeClassCol": "รุ่นอายุ (TT)",
|
||||
"ttAdult": "ผู้ใหญ่ (ไม่ใช่เยาวชนตามวันตัด)",
|
||||
"ttFilterGroupJ": "ชายและหญิง (รวม)",
|
||||
"ttFilterGroupM": "เฉพาะหญิง",
|
||||
"adults": "ผู้ใหญ่ (20+)",
|
||||
"j19": "J19 (19 ปีหรือน้อยกว่า)",
|
||||
"j19": "J19",
|
||||
"j17": "J17 (17 ปีหรือน้อยกว่า)",
|
||||
"j15": "J15 (15 ปีหรือน้อยกว่า)",
|
||||
"j13": "J13 (13 ปีหรือน้อยกว่า)",
|
||||
"j11": "J11 (11 ปีหรือน้อยกว่า)",
|
||||
"j15": "J15",
|
||||
"j13": "J13",
|
||||
"j11": "J11",
|
||||
"j9": "J9",
|
||||
"m19": "M19",
|
||||
"m15": "M15",
|
||||
"m13": "M13",
|
||||
"m11": "M11",
|
||||
"m9": "M9",
|
||||
"clearFilters": "รีเซ็ตตัวกรอง",
|
||||
"imageInternet": "รูปภาพ (เน็ต)",
|
||||
"testMember": "ทดลอง",
|
||||
|
||||
@@ -434,12 +434,26 @@
|
||||
"clearFields": "Burahin ang mga field",
|
||||
"showInactiveMembers": "Ipakita ang mga hindi aktibong miyembro",
|
||||
"ageGroup": "Pangkat ng edad",
|
||||
"ttSeasonFilter": "Season (cutoff)",
|
||||
"ttSeasonCurrentTag": "kasalukuyan",
|
||||
"ttSeasonNextTag": "susunod",
|
||||
"ttStichtagHint": "Cutoff Enero 1 (DTTB). Lalaki: J lang. Babae: J at M.",
|
||||
"ttAgeClassCol": "Edad (TT)",
|
||||
"ttAdult": "Adulto (hindi youth sa cutoff)",
|
||||
"ttFilterGroupJ": "Babae at lalaki (halo)",
|
||||
"ttFilterGroupM": "Babae lamang",
|
||||
"adults": "Adulto (20+)",
|
||||
"j19": "J19 (19 pababa)",
|
||||
"j19": "J19",
|
||||
"j17": "J17 (17 pababa)",
|
||||
"j15": "J15 (15 pababa)",
|
||||
"j13": "J13 (13 pababa)",
|
||||
"j11": "J11 (11 pababa)",
|
||||
"j15": "J15",
|
||||
"j13": "J13",
|
||||
"j11": "J11",
|
||||
"j9": "J9",
|
||||
"m19": "M19",
|
||||
"m15": "M15",
|
||||
"m13": "M13",
|
||||
"m11": "M11",
|
||||
"m9": "M9",
|
||||
"clearFilters": "I-reset ang mga filter",
|
||||
"imageInternet": "Larawan (net?)",
|
||||
"testMember": "Trial",
|
||||
|
||||
@@ -434,12 +434,26 @@
|
||||
"clearFields": "清空字段",
|
||||
"showInactiveMembers": "显示非活跃成员",
|
||||
"ageGroup": "年龄组",
|
||||
"ttSeasonFilter": "赛季(截止日)",
|
||||
"ttSeasonCurrentTag": "当前",
|
||||
"ttSeasonNextTag": "下一",
|
||||
"ttStichtagHint": "截止日1月1日(DTTB)。男孩:仅 J 组。女孩:J 与 M。",
|
||||
"ttAgeClassCol": "年龄组(TT)",
|
||||
"ttAdult": "成人(按截止日非青年)",
|
||||
"ttFilterGroupJ": "男女孩(混合)",
|
||||
"ttFilterGroupM": "仅女孩",
|
||||
"adults": "成人(20岁以上)",
|
||||
"j19": "J19(19岁及以下)",
|
||||
"j19": "J19",
|
||||
"j17": "J17(17岁及以下)",
|
||||
"j15": "J15(15岁及以下)",
|
||||
"j13": "J13(13岁及以下)",
|
||||
"j11": "J11(11岁及以下)",
|
||||
"j15": "J15",
|
||||
"j13": "J13",
|
||||
"j11": "J11",
|
||||
"j9": "J9",
|
||||
"m19": "M19",
|
||||
"m15": "M15",
|
||||
"m13": "M13",
|
||||
"m11": "M11",
|
||||
"m9": "M9",
|
||||
"clearFilters": "重置筛选",
|
||||
"imageInternet": "图片(网络)",
|
||||
"testMember": "试用",
|
||||
|
||||
128
frontend/src/utils/ttAgeClass.js
Normal file
128
frontend/src/utils/ttAgeClass.js
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Tischtennis-Altersklassen nach Stichtag 01.01. (z. B. DTTB / Mannschafts- und Einzelspielbetrieb).
|
||||
* Pro Saison verschieben sich die Jahrgänge um ein Jahr; die Logik nutzt das erste Saisonjahr (z. B. 2025 für 2025/26).
|
||||
*/
|
||||
|
||||
const YOUTH_BANDS = [9, 11, 13, 15, 19];
|
||||
|
||||
/**
|
||||
* @param {Date} [d]
|
||||
* @returns {number} Erstes Kalenderjahr der laufenden Spielzeit (01.08.–31.07. üblich: Aug–Dez = Jahr, Jan–Jul = Vorjahr).
|
||||
*/
|
||||
export function getSeasonStartYearFromDate(d = new Date()) {
|
||||
const month = d.getMonth() + 1;
|
||||
const year = d.getFullYear();
|
||||
return month >= 8 ? year : year - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stichtag 01.01. für „Mädchen & Jungen n“: Geburtsdatum >= 01.01.(Saisonbeginn − (n − 1))
|
||||
* Beispiel Saison 2025/26: J19 → 01.01.2007, J15 → 01.01.2011, …, J9 → 01.01.2017
|
||||
* @param {number} seasonStartYear z. B. 2025
|
||||
* @param {number} classNum 9 | 11 | 13 | 15 | 19
|
||||
*/
|
||||
export function getStichtagDate(seasonStartYear, classNum) {
|
||||
const y = seasonStartYear - (classNum - 1);
|
||||
return new Date(y, 0, 1);
|
||||
}
|
||||
|
||||
function birthDateToTime(birthDate) {
|
||||
if (!birthDate) return null;
|
||||
let d;
|
||||
if (birthDate instanceof Date && !Number.isNaN(birthDate.getTime())) {
|
||||
d = birthDate;
|
||||
} else if (typeof birthDate === 'string') {
|
||||
const t = birthDate.trim();
|
||||
if (t.includes('-')) {
|
||||
const [a, b, c] = t.split(/[T\s]/)[0].split('-').map(Number);
|
||||
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c)) {
|
||||
d = new Date(a, b - 1, c);
|
||||
}
|
||||
} else 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)) {
|
||||
d = new Date(year, month - 1, day);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!d || Number.isNaN(d.getTime())) return null;
|
||||
return d.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Engste Jugend-Altersklasse (exklusive Bänder zwischen den Stichtagen).
|
||||
* @returns {'J9'|'J11'|'J13'|'J15'|'J19'|'adult'|null} null = kein gültiges Geburtsdatum
|
||||
*/
|
||||
export function getExclusiveJugendClass(birthDate, seasonStartYear) {
|
||||
const t = birthDateToTime(birthDate);
|
||||
if (t === null) return null;
|
||||
|
||||
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 (t < c19) return 'adult';
|
||||
if (t >= c9) return 'J9';
|
||||
if (t >= c11 && t < c9) return 'J11';
|
||||
if (t >= c13 && t < c11) return 'J13';
|
||||
if (t >= c15 && t < c13) return 'J15';
|
||||
if (t >= c19 && t < c15) return 'J19';
|
||||
return 'adult';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} member
|
||||
* @param {string} filterKey '', 'adult', 'range', 'J9'…'J19', 'M9'…'M19'
|
||||
* @param {number} seasonStartYear
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function memberMatchesTtAgeClass(member, filterKey, seasonStartYear) {
|
||||
if (!filterKey || filterKey === 'range') return true;
|
||||
|
||||
const jClass = getExclusiveJugendClass(member.birthDate, seasonStartYear);
|
||||
|
||||
if (filterKey === 'adult') {
|
||||
return jClass === 'adult';
|
||||
}
|
||||
|
||||
if (filterKey.startsWith('J')) {
|
||||
return jClass === filterKey;
|
||||
}
|
||||
|
||||
if (filterKey.startsWith('M')) {
|
||||
if (member.gender !== 'female') return false;
|
||||
const want = `J${filterKey.slice(1)}`;
|
||||
return jClass === want;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anzeige in der Liste: Jungen nur J…; Mädchen J… und M…
|
||||
*/
|
||||
export function formatMemberTtAgeClassLabels(member, seasonStartYear) {
|
||||
const j = getExclusiveJugendClass(member.birthDate, seasonStartYear);
|
||||
if (j === null) return { primary: null, secondary: null };
|
||||
if (j === 'adult') return { primary: 'adult', secondary: null };
|
||||
if (member.gender === 'female') {
|
||||
const m = `M${j.slice(1)}`;
|
||||
return { primary: j, secondary: m };
|
||||
}
|
||||
return { primary: j, secondary: null };
|
||||
}
|
||||
|
||||
export function formatSeasonSlash(seasonStartYear) {
|
||||
const y = seasonStartYear;
|
||||
return `${y}/${String(y + 1).slice(-2)}`;
|
||||
}
|
||||
|
||||
export { YOUTH_BANDS };
|
||||
@@ -12,6 +12,8 @@
|
||||
:selected-member-scope="selectedMemberScope"
|
||||
:show-inactive-members="showInactiveMembers"
|
||||
:selected-age-group="selectedAgeGroup"
|
||||
:selected-season-start-year="selectedSeasonStartYear"
|
||||
:season-filter-options="ttSeasonFilterOptions"
|
||||
:selected-age-from="selectedAgeFrom"
|
||||
:selected-age-to="selectedAgeTo"
|
||||
:selected-gender="selectedGender"
|
||||
@@ -32,6 +34,7 @@
|
||||
@update:selected-member-scope="selectedMemberScope = $event"
|
||||
@update:show-inactive-members="showInactiveMembers = $event"
|
||||
@update:selected-age-group="selectedAgeGroup = $event"
|
||||
@update:selected-season-start-year="selectedSeasonStartYear = $event"
|
||||
@update:selected-age-from="selectedAgeFrom = $event"
|
||||
@update:selected-age-to="selectedAgeTo = $event"
|
||||
@update:selected-gender="selectedGender = $event"
|
||||
@@ -332,6 +335,7 @@
|
||||
<th>{{ $t('members.contact') }}</th>
|
||||
<th>{{ $t('members.birthdate') }}</th>
|
||||
<th>{{ $t('members.age') }}</th>
|
||||
<th>{{ $t('members.ttAgeClassCol') }}</th>
|
||||
<th>{{ $t('members.lastTraining') }}</th>
|
||||
<th v-if="hasTestMembers">{{ $t('members.trainingParticipations') }}</th>
|
||||
<th>{{ $t('members.actions') }}</th>
|
||||
@@ -417,6 +421,7 @@
|
||||
</td>
|
||||
<td>{{ getFormattedBirthdate(member.birthDate) }}</td>
|
||||
<td>{{ getAgeLabel(member.birthDate) }}</td>
|
||||
<td class="tt-age-class-cell">{{ formatMemberTtAgeClassCell(member) }}</td>
|
||||
<td>{{ getOptionalFormattedDate(member.lastTraining, 'members.previewNoLastTraining') }}</td>
|
||||
<td v-if="hasTestMembers">
|
||||
<span v-if="member.testMembership">{{ member.trainingParticipations || 0 }}</span>
|
||||
@@ -567,6 +572,12 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
import { getSafeErrorMessage, getSafeMessage } from '../utils/errorMessages.js';
|
||||
import {
|
||||
getSeasonStartYearFromDate,
|
||||
memberMatchesTtAgeClass,
|
||||
formatMemberTtAgeClassLabels,
|
||||
formatSeasonSlash
|
||||
} from '../utils/ttAgeClass.js';
|
||||
import PDFGenerator from '../components/PDFGenerator.js';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
@@ -625,6 +636,20 @@ export default {
|
||||
.map(([id, name]) => ({ id, name }))
|
||||
.sort((a, b) => a.name.localeCompare(b.name, 'de-DE'));
|
||||
},
|
||||
|
||||
ttSeasonFilterOptions() {
|
||||
const y0 = getSeasonStartYearFromDate();
|
||||
return [
|
||||
{
|
||||
value: y0,
|
||||
label: `${formatSeasonSlash(y0)} (${this.$t('members.ttSeasonCurrentTag')})`
|
||||
},
|
||||
{
|
||||
value: y0 + 1,
|
||||
label: `${formatSeasonSlash(y0 + 1)} (${this.$t('members.ttSeasonNextTag')})`
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
filteredMembers() {
|
||||
return this.members.filter(member => {
|
||||
@@ -664,10 +689,9 @@ export default {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Altersklasse Filter
|
||||
// Altersklasse (TT, Stichtag 1.1.)
|
||||
if (this.selectedAgeGroup && this.selectedAgeGroup !== 'range') {
|
||||
const age = this.getAge(member.birthDate);
|
||||
if (!this.matchesAgeGroup(age, this.selectedAgeGroup)) {
|
||||
if (!memberMatchesTtAgeClass(member, this.selectedAgeGroup, this.selectedSeasonStartYear)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -940,6 +964,7 @@ export default {
|
||||
selectedGroupToAdd: '',
|
||||
showTransferDialog: false,
|
||||
selectedAgeGroup: '',
|
||||
selectedSeasonStartYear: getSeasonStartYearFromDate(),
|
||||
selectedAgeFrom: '',
|
||||
selectedAgeTo: '',
|
||||
selectedGender: '',
|
||||
@@ -994,19 +1019,32 @@ export default {
|
||||
},
|
||||
|
||||
async init() {
|
||||
try {
|
||||
await this.loadMembers();
|
||||
} catch (error) {
|
||||
console.error(this.$t('members.errorLoadingMembers'), error);
|
||||
this.membersLoadError = getSafeErrorMessage(error, this.$t('members.errorLoadingMembers'));
|
||||
}
|
||||
await this.loadMembers();
|
||||
},
|
||||
async loadMembers() {
|
||||
this.isLoadingMembers = true;
|
||||
this.membersLoadError = '';
|
||||
try {
|
||||
const response = await apiClient.get(`/clubmembers/get/${this.currentClub}/true`);
|
||||
this.members = response.data
|
||||
const payload = response.data;
|
||||
const ok =
|
||||
response.status >= 200 &&
|
||||
response.status < 300 &&
|
||||
Array.isArray(payload);
|
||||
if (!ok) {
|
||||
const raw =
|
||||
payload &&
|
||||
typeof payload === 'object' &&
|
||||
!Array.isArray(payload) &&
|
||||
(payload.error ?? payload.message);
|
||||
this.membersLoadError = getSafeMessage(
|
||||
typeof raw === 'string' ? raw : null,
|
||||
this.$t('members.errorLoadingMembers')
|
||||
);
|
||||
this.members = [];
|
||||
return;
|
||||
}
|
||||
this.members = payload
|
||||
.map(member => this.applyBackendBaseUrlToMember(member))
|
||||
.sort((a, b) => {
|
||||
const lastNameA = a.lastName ? a.lastName.toLowerCase() : '';
|
||||
@@ -1031,6 +1069,10 @@ export default {
|
||||
this.selectedPreviewTrainingGroups = [];
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(this.$t('members.errorLoadingMembers'), error);
|
||||
this.membersLoadError = getSafeErrorMessage(error, this.$t('members.errorLoadingMembers'));
|
||||
this.members = [];
|
||||
} finally {
|
||||
this.isLoadingMembers = false;
|
||||
}
|
||||
@@ -1181,6 +1223,7 @@ export default {
|
||||
this.$t('members.status'),
|
||||
this.$t('members.birthdate'),
|
||||
this.$t('members.age'),
|
||||
this.$t('members.ttAgeClassCol'),
|
||||
this.$t('members.contact'),
|
||||
this.$t('members.emailAddress')
|
||||
];
|
||||
@@ -1190,6 +1233,7 @@ export default {
|
||||
this.getMemberStatusBadges(member).map(badge => badge.label).join(' | '),
|
||||
this.getFormattedBirthdate(member.birthDate),
|
||||
this.getAgeLabel(member.birthDate),
|
||||
this.formatMemberTtAgeClassCell(member),
|
||||
this.getFormattedPhoneNumbers(member),
|
||||
this.getFormattedEmails(member)
|
||||
]);
|
||||
@@ -2362,40 +2406,23 @@ export default {
|
||||
: age;
|
||||
},
|
||||
|
||||
matchesAgeGroup(age, ageGroup) {
|
||||
if (age === null) return false;
|
||||
|
||||
// Tischtennis-Logik: Altersklassen basieren auf Jahrgängen mit Stichtag 1. Januar
|
||||
// J19 = 19 Jahre und jünger am 1. Januar des aktuellen Jahres
|
||||
// J17 = 17 Jahre und jünger am 1. Januar des aktuellen Jahres
|
||||
// etc.
|
||||
|
||||
switch (ageGroup) {
|
||||
case 'adult':
|
||||
return age > 19; // Erwachsene = älter als 19
|
||||
|
||||
case 'J19':
|
||||
return age <= 19; // J19 = 19 und jünger
|
||||
|
||||
case 'J17':
|
||||
return age <= 17; // J17 = 17 und jünger
|
||||
|
||||
case 'J15':
|
||||
return age <= 15; // J15 = 15 und jünger
|
||||
|
||||
case 'J13':
|
||||
return age <= 13; // J13 = 13 und jünger
|
||||
|
||||
case 'J11':
|
||||
return age <= 11; // J11 = 11 und jünger
|
||||
|
||||
default:
|
||||
return true;
|
||||
formatMemberTtAgeClassCell(member) {
|
||||
const { primary, secondary } = formatMemberTtAgeClassLabels(member, this.selectedSeasonStartYear);
|
||||
if (primary === null) {
|
||||
return '–';
|
||||
}
|
||||
if (primary === 'adult') {
|
||||
return this.$t('members.ttAdult');
|
||||
}
|
||||
if (secondary) {
|
||||
return `${primary} · ${secondary}`;
|
||||
}
|
||||
return primary;
|
||||
},
|
||||
|
||||
clearFilters() {
|
||||
this.selectedAgeGroup = '';
|
||||
this.selectedSeasonStartYear = getSeasonStartYearFromDate();
|
||||
this.selectedAgeFrom = '';
|
||||
this.selectedAgeTo = '';
|
||||
this.selectedGender = '';
|
||||
@@ -3319,6 +3346,11 @@ table td {
|
||||
padding: 0.5rem 1rem 1rem;
|
||||
}
|
||||
|
||||
.tt-age-class-cell {
|
||||
font-size: 0.88rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.action-icons-row {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
|
||||
Reference in New Issue
Block a user