feat(MembersOverview): add training participations column toggle and localization updates
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 35s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 35s
- Introduced a checkbox in the MembersOverviewSection to toggle the visibility of the training participations column. - Updated the MembersView to handle the new toggle state and display training participations accordingly. - Enhanced localization files across multiple languages to include the new term for the training participations column. - Refactored member age class display logic to improve clarity and maintainability.
This commit is contained in:
@@ -58,6 +58,14 @@
|
||||
>
|
||||
<span>{{ $t('members.showInactiveMembers') }}</span>
|
||||
</label>
|
||||
<label class="checkbox-item">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="showTrainingParticipationsColumn"
|
||||
@change="$emit('update:show-training-participations-column', $event.target.checked)"
|
||||
>
|
||||
<span>{{ $t('members.showTrainingParticipationsColumn') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>{{ $t('members.ttSeasonFilter') }}</label>
|
||||
@@ -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',
|
||||
|
||||
@@ -466,6 +466,7 @@
|
||||
"create": "Anlegen",
|
||||
"clearFields": "Felder leeren",
|
||||
"showInactiveMembers": "Inaktive Mitglieder anzeigen",
|
||||
"showTrainingParticipationsColumn": "Spalte „Trainingsteilnahmen“ anzeigen",
|
||||
"ageGroup": "Altersklasse",
|
||||
"ttSeasonFilter": "Saison (Stichtag)",
|
||||
"ttSeasonCurrentTag": "aktuell",
|
||||
|
||||
@@ -241,6 +241,7 @@
|
||||
"create": "Anlegen",
|
||||
"clearFields": "Felder leeren",
|
||||
"showInactiveMembers": "Inaktive Mitglieder anzeigen",
|
||||
"showTrainingParticipationsColumn": "Spalte „Trainingsteilnahmen“ anzeigen",
|
||||
"ageGroup": "Altersklasse",
|
||||
"ttSeasonFilter": "Saison (Stichtag)",
|
||||
"ttSeasonCurrentTag": "aktuell",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -433,6 +433,7 @@
|
||||
"create": "作成",
|
||||
"clearFields": "入力欄をクリア",
|
||||
"showInactiveMembers": "非アクティブメンバーを表示",
|
||||
"showTrainingParticipationsColumn": "「参加回数」列を表示",
|
||||
"ageGroup": "年齢区分",
|
||||
"ttSeasonFilter": "シーズン(基準日)",
|
||||
"ttSeasonCurrentTag": "今季",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -433,6 +433,7 @@
|
||||
"create": "สร้าง",
|
||||
"clearFields": "ล้างช่องข้อมูล",
|
||||
"showInactiveMembers": "แสดงสมาชิกที่ไม่ใช้งาน",
|
||||
"showTrainingParticipationsColumn": "แสดงคอลัมน์ « การเข้าร่วมเทรนนิง »",
|
||||
"ageGroup": "กลุ่มอายุ",
|
||||
"ttSeasonFilter": "ฤดูกาล (วันตัดสิทธิ์)",
|
||||
"ttSeasonCurrentTag": "ปัจจุบัน",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -433,6 +433,7 @@
|
||||
"create": "创建",
|
||||
"clearFields": "清空字段",
|
||||
"showInactiveMembers": "显示非活跃成员",
|
||||
"showTrainingParticipationsColumn": "显示「训练参与」列",
|
||||
"ageGroup": "年龄组",
|
||||
"ttSeasonFilter": "赛季(截止日)",
|
||||
"ttSeasonCurrentTag": "当前",
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
<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.previewLastTraining') }}</th>
|
||||
<th v-if="showTrainingParticipationsColumn">{{ $t('members.trainingParticipations') }}</th>
|
||||
<th>{{ $t('members.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -421,11 +423,18 @@
|
||||
</td>
|
||||
<td>{{ getFormattedBirthdate(member.birthDate) }}</td>
|
||||
<td>{{ getAgeLabel(member.birthDate) }}</td>
|
||||
<td class="tt-age-class-cell">{{ formatMemberTtAgeClassCell(member) }}</td>
|
||||
<td class="tt-age-class-cell">
|
||||
<div
|
||||
v-for="(line, idx) in formatMemberTtAgeClassLines(member)"
|
||||
:key="idx"
|
||||
class="tt-age-class-line"
|
||||
>
|
||||
{{ line }}
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ getOptionalFormattedDate(member.lastTraining, 'members.previewNoLastTraining') }}</td>
|
||||
<td v-if="hasTestMembers">
|
||||
<span v-if="member.testMembership">{{ member.trainingParticipations || 0 }}</span>
|
||||
<span v-else>-</span>
|
||||
<td v-if="showTrainingParticipationsColumn" class="training-participations-cell">
|
||||
{{ trainingParticipationsDisplay(member) }}
|
||||
</td>
|
||||
<td>
|
||||
<div class="member-actions-cell">
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user