diff --git a/frontend/src/components/members/MembersOverviewSection.vue b/frontend/src/components/members/MembersOverviewSection.vue
index 9607a25e..eec4766d 100644
--- a/frontend/src/components/members/MembersOverviewSection.vue
+++ b/frontend/src/components/members/MembersOverviewSection.vue
@@ -58,6 +58,14 @@
>
{{ $t('members.showInactiveMembers') }}
+
@@ -220,6 +228,7 @@ export default {
memberScopeOptions: { type: Array, required: true },
selectedMemberScope: { type: String, required: true },
showInactiveMembers: { type: Boolean, required: true },
+ showTrainingParticipationsColumn: { type: Boolean, required: true },
selectedAgeGroup: { type: String, required: true },
selectedSeasonStartYear: { type: Number, required: true },
seasonFilterOptions: { type: Array, required: true },
@@ -244,6 +253,7 @@ export default {
'update:search-query',
'update:selected-member-scope',
'update:show-inactive-members',
+ 'update:show-training-participations-column',
'update:selected-age-group',
'update:selected-season-start-year',
'update:selected-age-from',
diff --git a/frontend/src/i18n/locales/de-CH.json b/frontend/src/i18n/locales/de-CH.json
index c95c50bd..383b4dad 100644
--- a/frontend/src/i18n/locales/de-CH.json
+++ b/frontend/src/i18n/locales/de-CH.json
@@ -466,6 +466,7 @@
"create": "Anlegen",
"clearFields": "Felder leeren",
"showInactiveMembers": "Inaktive Mitglieder anzeigen",
+ "showTrainingParticipationsColumn": "Spalte „Trainingsteilnahmen“ anzeigen",
"ageGroup": "Altersklasse",
"ttSeasonFilter": "Saison (Stichtag)",
"ttSeasonCurrentTag": "aktuell",
diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json
index 81bbb8ac..7482a348 100644
--- a/frontend/src/i18n/locales/de.json
+++ b/frontend/src/i18n/locales/de.json
@@ -241,6 +241,7 @@
"create": "Anlegen",
"clearFields": "Felder leeren",
"showInactiveMembers": "Inaktive Mitglieder anzeigen",
+ "showTrainingParticipationsColumn": "Spalte „Trainingsteilnahmen“ anzeigen",
"ageGroup": "Altersklasse",
"ttSeasonFilter": "Saison (Stichtag)",
"ttSeasonCurrentTag": "aktuell",
diff --git a/frontend/src/i18n/locales/en-AU.json b/frontend/src/i18n/locales/en-AU.json
index 8a9516ff..2458ee0b 100644
--- a/frontend/src/i18n/locales/en-AU.json
+++ b/frontend/src/i18n/locales/en-AU.json
@@ -466,6 +466,7 @@
"create": "Create",
"clearFields": "Clear fields",
"showInactiveMembers": "Show inactive members",
+ "showTrainingParticipationsColumn": "Show “Training participations” column",
"ageGroup": "Age group",
"ttSeasonFilter": "Season (cutoff)",
"ttSeasonCurrentTag": "current",
diff --git a/frontend/src/i18n/locales/en-GB.json b/frontend/src/i18n/locales/en-GB.json
index 9e37c380..da75969b 100644
--- a/frontend/src/i18n/locales/en-GB.json
+++ b/frontend/src/i18n/locales/en-GB.json
@@ -741,6 +741,7 @@
"create": "Create",
"clearFields": "Clear fields",
"showInactiveMembers": "Show inactive members",
+ "showTrainingParticipationsColumn": "Show “Training participations” column",
"ageGroup": "Age group",
"ttSeasonFilter": "Season (cutoff)",
"ttSeasonCurrentTag": "current",
diff --git a/frontend/src/i18n/locales/en-US.json b/frontend/src/i18n/locales/en-US.json
index e6892d01..b857fde6 100644
--- a/frontend/src/i18n/locales/en-US.json
+++ b/frontend/src/i18n/locales/en-US.json
@@ -466,6 +466,7 @@
"create": "Create",
"clearFields": "Clear fields",
"showInactiveMembers": "Show inactive members",
+ "showTrainingParticipationsColumn": "Show “Training participations” column",
"ageGroup": "Age group",
"ttSeasonFilter": "Season (cutoff)",
"ttSeasonCurrentTag": "current",
diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json
index a23dfaae..0786fcb2 100644
--- a/frontend/src/i18n/locales/es.json
+++ b/frontend/src/i18n/locales/es.json
@@ -433,6 +433,7 @@
"create": "Crear",
"clearFields": "Vaciar campos",
"showInactiveMembers": "Mostrar miembros inactivos",
+ "showTrainingParticipationsColumn": "Mostrar columna « Participaciones »",
"ageGroup": "Categoría de edad",
"ttSeasonFilter": "Temporada (corte)",
"ttSeasonCurrentTag": "actual",
diff --git a/frontend/src/i18n/locales/fil.json b/frontend/src/i18n/locales/fil.json
index 77dbfb40..738514ec 100644
--- a/frontend/src/i18n/locales/fil.json
+++ b/frontend/src/i18n/locales/fil.json
@@ -433,6 +433,7 @@
"create": "Lumikha",
"clearFields": "Burahin ang mga field",
"showInactiveMembers": "Ipakita ang mga hindi aktibong miyembro",
+ "showTrainingParticipationsColumn": "Ipakita ang column na « Training participations »",
"ageGroup": "Pangkat ng edad",
"ttSeasonFilter": "Season (cutoff)",
"ttSeasonCurrentTag": "kasalukuyan",
diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json
index 6f3fd407..3cab3e1f 100644
--- a/frontend/src/i18n/locales/fr.json
+++ b/frontend/src/i18n/locales/fr.json
@@ -433,6 +433,7 @@
"create": "Créer",
"clearFields": "Vider les champs",
"showInactiveMembers": "Afficher les membres inactifs",
+ "showTrainingParticipationsColumn": "Afficher la colonne « Participations »",
"ageGroup": "Catégorie d'âge",
"ttSeasonFilter": "Saison (date limite)",
"ttSeasonCurrentTag": "actuelle",
diff --git a/frontend/src/i18n/locales/it.json b/frontend/src/i18n/locales/it.json
index 8fba25f3..7cec8147 100644
--- a/frontend/src/i18n/locales/it.json
+++ b/frontend/src/i18n/locales/it.json
@@ -433,6 +433,7 @@
"create": "Crea",
"clearFields": "Svuota campi",
"showInactiveMembers": "Mostra membri inattivi",
+ "showTrainingParticipationsColumn": "Mostra colonna « Partecipazioni »",
"ageGroup": "Fascia d’età",
"ttSeasonFilter": "Stagione (data di riferimento)",
"ttSeasonCurrentTag": "attuale",
diff --git a/frontend/src/i18n/locales/ja.json b/frontend/src/i18n/locales/ja.json
index d755aea3..e98547a9 100644
--- a/frontend/src/i18n/locales/ja.json
+++ b/frontend/src/i18n/locales/ja.json
@@ -433,6 +433,7 @@
"create": "作成",
"clearFields": "入力欄をクリア",
"showInactiveMembers": "非アクティブメンバーを表示",
+ "showTrainingParticipationsColumn": "「参加回数」列を表示",
"ageGroup": "年齢区分",
"ttSeasonFilter": "シーズン(基準日)",
"ttSeasonCurrentTag": "今季",
diff --git a/frontend/src/i18n/locales/pl.json b/frontend/src/i18n/locales/pl.json
index f0676ad3..be38f837 100644
--- a/frontend/src/i18n/locales/pl.json
+++ b/frontend/src/i18n/locales/pl.json
@@ -433,6 +433,7 @@
"create": "Utwórz",
"clearFields": "Wyczyść pola",
"showInactiveMembers": "Pokaż nieaktywnych członków",
+ "showTrainingParticipationsColumn": "Pokaż kolumnę „Udziały w treningu”",
"ageGroup": "Kategoria wiekowa",
"ttSeasonFilter": "Sezon (termin)",
"ttSeasonCurrentTag": "bieżący",
diff --git a/frontend/src/i18n/locales/th.json b/frontend/src/i18n/locales/th.json
index b9e1936c..02d90b76 100644
--- a/frontend/src/i18n/locales/th.json
+++ b/frontend/src/i18n/locales/th.json
@@ -433,6 +433,7 @@
"create": "สร้าง",
"clearFields": "ล้างช่องข้อมูล",
"showInactiveMembers": "แสดงสมาชิกที่ไม่ใช้งาน",
+ "showTrainingParticipationsColumn": "แสดงคอลัมน์ « การเข้าร่วมเทรนนิง »",
"ageGroup": "กลุ่มอายุ",
"ttSeasonFilter": "ฤดูกาล (วันตัดสิทธิ์)",
"ttSeasonCurrentTag": "ปัจจุบัน",
diff --git a/frontend/src/i18n/locales/tl.json b/frontend/src/i18n/locales/tl.json
index 46e18de5..86c43456 100644
--- a/frontend/src/i18n/locales/tl.json
+++ b/frontend/src/i18n/locales/tl.json
@@ -433,6 +433,7 @@
"create": "Lumikha",
"clearFields": "Burahin ang mga field",
"showInactiveMembers": "Ipakita ang mga hindi aktibong miyembro",
+ "showTrainingParticipationsColumn": "Ipakita ang column na « Training participations »",
"ageGroup": "Pangkat ng edad",
"ttSeasonFilter": "Season (cutoff)",
"ttSeasonCurrentTag": "kasalukuyan",
diff --git a/frontend/src/i18n/locales/zh.json b/frontend/src/i18n/locales/zh.json
index 8ade7071..4362f5ff 100644
--- a/frontend/src/i18n/locales/zh.json
+++ b/frontend/src/i18n/locales/zh.json
@@ -433,6 +433,7 @@
"create": "创建",
"clearFields": "清空字段",
"showInactiveMembers": "显示非活跃成员",
+ "showTrainingParticipationsColumn": "显示「训练参与」列",
"ageGroup": "年龄组",
"ttSeasonFilter": "赛季(截止日)",
"ttSeasonCurrentTag": "当前",
diff --git a/frontend/src/utils/ttAgeClass.js b/frontend/src/utils/ttAgeClass.js
index 018a6c7c..b78516c8 100644
--- a/frontend/src/utils/ttAgeClass.js
+++ b/frontend/src/utils/ttAgeClass.js
@@ -26,6 +26,13 @@ export function getStichtagDate(seasonStartYear, classNum) {
return new Date(y, 0, 1);
}
+/** @param {object} member */
+export function isFemaleGender(member) {
+ const g = member?.gender;
+ if (g == null || g === '') return false;
+ return String(g).toLowerCase() === 'female';
+}
+
function birthDateToTime(birthDate) {
if (!birthDate) return null;
let d;
@@ -98,7 +105,7 @@ export function memberMatchesTtAgeClass(member, filterKey, seasonStartYear) {
}
if (filterKey.startsWith('M')) {
- if (member.gender !== 'female') return false;
+ if (!isFemaleGender(member)) return false;
const want = `J${filterKey.slice(1)}`;
return jClass === want;
}
@@ -113,7 +120,7 @@ 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') {
+ if (isFemaleGender(member)) {
const m = `M${j.slice(1)}`;
return { primary: j, secondary: m };
}
diff --git a/frontend/src/views/MembersView.vue b/frontend/src/views/MembersView.vue
index 5948b8c8..594beda4 100644
--- a/frontend/src/views/MembersView.vue
+++ b/frontend/src/views/MembersView.vue
@@ -11,6 +11,7 @@
:member-scope-options="memberScopeOptions"
:selected-member-scope="selectedMemberScope"
:show-inactive-members="showInactiveMembers"
+ :show-training-participations-column="showTrainingParticipationsColumn"
:selected-age-group="selectedAgeGroup"
:selected-season-start-year="selectedSeasonStartYear"
:season-filter-options="ttSeasonFilterOptions"
@@ -33,6 +34,7 @@
@update:search-query="searchQuery = $event"
@update:selected-member-scope="selectedMemberScope = $event"
@update:show-inactive-members="showInactiveMembers = $event"
+ @update:show-training-participations-column="setShowTrainingParticipationsColumn"
@update:selected-age-group="selectedAgeGroup = $event"
@update:selected-season-start-year="selectedSeasonStartYear = $event"
@update:selected-age-from="selectedAgeFrom = $event"
@@ -336,8 +338,8 @@
{{ $t('members.birthdate') }} |
{{ $t('members.age') }} |
{{ $t('members.ttAgeClassCol') }} |
-
{{ $t('members.lastTraining') }} |
-
{{ $t('members.trainingParticipations') }} |
+
{{ $t('members.previewLastTraining') }} |
+
{{ $t('members.trainingParticipations') }} |
{{ $t('members.actions') }} |
@@ -421,11 +423,18 @@
{{ getFormattedBirthdate(member.birthDate) }} |
{{ getAgeLabel(member.birthDate) }} |
-
{{ formatMemberTtAgeClassCell(member) }} |
+
+
+ {{ line }}
+
+ |
{{ getOptionalFormattedDate(member.lastTraining, 'members.previewNoLastTraining') }} |
-
- {{ member.trainingParticipations || 0 }}
- -
+ |
+ {{ trainingParticipationsDisplay(member) }}
|
@@ -572,6 +581,7 @@
import { mapGetters } from 'vuex';
import apiClient from '../apiClient.js';
import { getSafeErrorMessage, getSafeMessage } from '../utils/errorMessages.js';
+import { safeLocalStorage } from '../utils/storage.js';
import {
getSeasonStartYearFromDate,
memberMatchesTtAgeClass,
@@ -887,9 +897,6 @@ export default {
return members;
},
- hasTestMembers() {
- return this.members.some(member => member.testMembership);
- },
availableGroupsForMember() {
if (!Array.isArray(this.trainingGroups) || this.trainingGroups.length === 0) {
@@ -947,6 +954,7 @@ export default {
selectedMemberForImages: null,
testMembership: false,
showInactiveMembers: false,
+ showTrainingParticipationsColumn: false,
newPicsInInternetAllowed: false,
newMemberFormHandedOver: false,
newAdultReleaseApproved: false,
@@ -980,6 +988,12 @@ export default {
selectedPreviewTrainingGroups: []
}
},
+ created() {
+ const v = safeLocalStorage.getItem('membersShowTrainingParticipationsColumn');
+ if (v === '1') {
+ this.showTrainingParticipationsColumn = true;
+ }
+ },
async mounted() {
await this.loadTrainingGroups();
await this.init();
@@ -1081,7 +1095,10 @@ export default {
async loadTrainingParticipations() {
try {
const response = await apiClient.get(`/training-stats/${this.currentClub}`);
- const trainingStats = response.data.members || [];
+ if (response.status < 200 || response.status >= 300 || !response.data || typeof response.data !== 'object') {
+ throw new Error('training-stats invalid response');
+ }
+ const trainingStats = Array.isArray(response.data.members) ? response.data.members : [];
// Erstelle eine Map für schnellen Zugriff: memberId -> participationTotal
// Speichere sowohl String- als auch Number-Keys, um Typ-Probleme zu vermeiden
@@ -1233,7 +1250,7 @@ export default {
this.getMemberStatusBadges(member).map(badge => badge.label).join(' | '),
this.getFormattedBirthdate(member.birthDate),
this.getAgeLabel(member.birthDate),
- this.formatMemberTtAgeClassCell(member),
+ this.formatMemberTtAgeClassCsv(member),
this.getFormattedPhoneNumbers(member),
this.getFormattedEmails(member)
]);
@@ -2406,18 +2423,34 @@ export default {
: age;
},
- formatMemberTtAgeClassCell(member) {
- const { primary, secondary } = formatMemberTtAgeClassLabels(member, this.selectedSeasonStartYear);
- if (primary === null) {
+ formatMemberTtAgeClassLines(member) {
+ const y0 = getSeasonStartYearFromDate();
+ return [y0, y0 + 1].map((seasonYear) => {
+ const { primary, secondary } = formatMemberTtAgeClassLabels(member, seasonYear);
+ const prefix = formatSeasonSlash(seasonYear);
+ if (primary === null) {
+ return `${prefix}: –`;
+ }
+ if (primary === 'adult') {
+ return `${prefix}: ${this.$t('members.ttAdult')}`;
+ }
+ const body = secondary ? `${primary} · ${secondary}` : primary;
+ return `${prefix}: ${body}`;
+ });
+ },
+ formatMemberTtAgeClassCsv(member) {
+ return this.formatMemberTtAgeClassLines(member).join(' | ');
+ },
+ trainingParticipationsDisplay(member) {
+ const n = member.trainingParticipations;
+ if (n == null || Number.isNaN(Number(n))) {
return '–';
}
- if (primary === 'adult') {
- return this.$t('members.ttAdult');
- }
- if (secondary) {
- return `${primary} · ${secondary}`;
- }
- return primary;
+ return String(n);
+ },
+ setShowTrainingParticipationsColumn(value) {
+ this.showTrainingParticipationsColumn = !!value;
+ safeLocalStorage.setItem('membersShowTrainingParticipationsColumn', this.showTrainingParticipationsColumn ? '1' : '0');
},
clearFilters() {
@@ -3348,7 +3381,18 @@ table td {
.tt-age-class-cell {
font-size: 0.88rem;
- white-space: nowrap;
+ line-height: 1.35;
+ vertical-align: top;
+}
+
+.tt-age-class-line + .tt-age-class-line {
+ margin-top: 0.2rem;
+ opacity: 0.92;
+}
+
+.training-participations-cell {
+ text-align: right;
+ font-variant-numeric: tabular-nums;
}
.action-icons-row {
|