Fügt Sortierfunktionalität für die Mitgliederstatistik hinzu. Die Tabellenüberschriften sind jetzt klickbar und ermöglichen eine Sortierung nach Name und Teilnahmezahlen. Zudem wurde die Sortierreihenfolge implementiert und visuell durch Icons angezeigt.

This commit is contained in:
Torsten Schulz (local)
2025-08-22 16:06:56 +02:00
parent ed96fc5f27
commit 6a8b0e35d7

View File

@@ -23,16 +23,36 @@
<table class="members-table">
<thead>
<tr>
<th>Name</th>
<th @click="sortBy('name')" class="sortable-header">
<div class="header-content">
<span>Name</span>
<span class="sort-icon">{{ getSortIcon('name') }}</span>
</div>
</th>
<th>Geburtsdatum</th>
<th>Teilnahmen (12 Monate)</th>
<th>Teilnahmen (3 Monate)</th>
<th>Teilnahmen (Gesamt)</th>
<th @click="sortBy('participation12Months')" class="sortable-header">
<div class="header-content">
<span>Teilnahmen (12 Monate)</span>
<span class="sort-icon">{{ getSortIcon('participation12Months') }}</span>
</div>
</th>
<th @click="sortBy('participation3Months')" class="sortable-header">
<div class="header-content">
<span>Teilnahmen (3 Monate)</span>
<span class="sort-icon">{{ getSortIcon('participation3Months') }}</span>
</div>
</th>
<th @click="sortBy('participationTotal')" class="sortable-header">
<div class="header-content">
<span>Teilnahmen (Gesamt)</span>
<span class="sort-icon">{{ getSortIcon('participationTotal') }}</span>
</div>
</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<tr v-for="member in activeMembers" :key="member.id" class="member-row">
<tr v-for="member in sortedMembers" :key="member.id" class="member-row">
<td>{{ member.firstName }} {{ member.lastName }}</td>
<td>{{ formatBirthdate(member.birthDate) }}</td>
<td>{{ member.participation12Months }}</td>
@@ -108,6 +128,32 @@ export default {
if (this.activeMembers.length === 0) return 0;
const total = this.activeMembers.reduce((sum, member) => sum + member.participation3Months, 0);
return total / this.activeMembers.length;
},
sortedMembers() {
if (!this.activeMembers.length) return [];
return [...this.activeMembers].sort((a, b) => {
let aValue, bValue;
if (this.sortField === 'name') {
// Case-insensitive Namens-Sortierung mit localeCompare
// Sortiere nach firstName lastName (wie in der Anzeige)
const aName = `${a.firstName} ${a.lastName}`;
const bName = `${b.firstName} ${b.lastName}`;
return aName.localeCompare(bName, 'de', { sensitivity: 'base' }) * (this.sortDirection === 'asc' ? 1 : -1);
} else {
// Numerische Sortierung
aValue = a[this.sortField] || 0;
bValue = b[this.sortField] || 0;
if (this.sortDirection === 'asc') {
return aValue > bValue ? 1 : -1;
} else {
return aValue < bValue ? 1 : -1;
}
}
});
}
},
@@ -116,7 +162,9 @@ export default {
activeMembers: [],
showDetailsModal: false,
selectedMember: {},
loading: false
loading: false,
sortField: 'name',
sortDirection: 'asc'
};
},
@@ -177,6 +225,24 @@ export default {
formatDate(dateString) {
const date = new Date(dateString);
return `${String(date.getDate()).padStart(2, '0')}.${String(date.getMonth() + 1).padStart(2, '0')}.${date.getFullYear()}`;
},
sortBy(field) {
if (this.sortField === field) {
// Wenn bereits nach diesem Feld sortiert wird, Richtung umkehren
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
} else {
// Neues Feld, Standardrichtung setzen
this.sortField = field;
this.sortDirection = 'desc';
}
},
getSortIcon(field) {
if (this.sortField !== field) {
return '↕️'; // Neutral
}
return this.sortDirection === 'asc' ? '↑' : '↓';
}
}
};
@@ -245,6 +311,29 @@ export default {
border-bottom: 1px solid var(--border-color);
}
.sortable-header {
cursor: pointer;
user-select: none;
transition: background-color 0.2s ease;
}
.sortable-header:hover {
background: var(--primary-color) !important;
color: white !important;
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
}
.sort-icon {
font-size: 0.875rem;
opacity: 0.7;
}
.members-table td {
padding: 1rem;
border-bottom: 1px solid var(--border-color);