Erweitert die Trainingsstatistik-Funktionalität im TrainingStatsController um die Abfrage und Formatierung von Trainingstagen der letzten 12 Monate. Aktualisiert die Benutzeroberfläche in TrainingStatsView.vue zur Anzeige dieser Trainingstage in einer aufklappbaren Tabelle. Fügt Funktionen zum Umschalten der Sichtbarkeit von Trainingstagen und Mitgliedern hinzu.
This commit is contained in:
@@ -135,11 +135,35 @@ class TrainingStatsController {
|
||||
// Nach Gesamtteilnahme absteigend sortieren
|
||||
stats.sort((a, b) => b.participationTotal - a.participationTotal);
|
||||
|
||||
// Trainingstage mit Teilnehmerzahlen abrufen (letzte 12 Monate, absteigend sortiert)
|
||||
const trainingDays = await DiaryDate.findAll({
|
||||
where: {
|
||||
clubId: parseInt(clubId),
|
||||
date: {
|
||||
[Op.gte]: twelveMonthsAgo
|
||||
}
|
||||
},
|
||||
include: [{
|
||||
model: Participant,
|
||||
as: 'participantList',
|
||||
attributes: ['id']
|
||||
}],
|
||||
order: [['date', 'DESC']]
|
||||
});
|
||||
|
||||
// Formatiere Trainingstage mit Teilnehmerzahl
|
||||
const formattedTrainingDays = trainingDays.map(day => ({
|
||||
id: day.id,
|
||||
date: day.date,
|
||||
participantCount: day.participantList ? day.participantList.length : 0
|
||||
}));
|
||||
|
||||
// Zusätzliche Metadaten mit Trainingsanzahl zurückgeben
|
||||
res.json({
|
||||
members: stats,
|
||||
trainingsCount12Months,
|
||||
trainingsCount3Months
|
||||
trainingsCount3Months,
|
||||
trainingDays: formattedTrainingDays
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -19,8 +19,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="members-table-container">
|
||||
<table class="members-table">
|
||||
<!-- Trainingstage-Tabelle (standardmäßig aufgeklappt) -->
|
||||
<div class="collapsible-section">
|
||||
<div class="section-header" @click="toggleTrainingDays">
|
||||
<h3>Trainingstage (letzte 12 Monate)</h3>
|
||||
<span class="toggle-icon">{{ showTrainingDays ? '▼' : '▶' }}</span>
|
||||
</div>
|
||||
<div v-if="showTrainingDays" class="section-content">
|
||||
<div class="training-days-container">
|
||||
<table class="training-days-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Datum</th>
|
||||
<th>Wochentag</th>
|
||||
<th>Teilnehmer</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="day in trainingDays" :key="day.id">
|
||||
<td>{{ formatDate(day.date) }}</td>
|
||||
<td>{{ getWeekday(day.date) }}</td>
|
||||
<td>{{ day.participantCount }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mitglieder-Tabelle (standardmäßig eingeklappt) -->
|
||||
<div class="collapsible-section">
|
||||
<div class="section-header" @click="toggleMembers">
|
||||
<h3>Mitglieder-Teilnahmen</h3>
|
||||
<span class="toggle-icon">{{ showMembers ? '▼' : '▶' }}</span>
|
||||
</div>
|
||||
<div v-if="showMembers" class="section-content">
|
||||
<div class="members-table-container">
|
||||
<table class="members-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th @click="sortBy('name')" class="sortable-header">
|
||||
@@ -74,6 +109,8 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal für Mitgliedsdetails -->
|
||||
<div v-if="showDetailsModal" class="modal">
|
||||
@@ -169,11 +206,14 @@ export default {
|
||||
activeMembers: [],
|
||||
trainingsCount12Months: 0,
|
||||
trainingsCount3Months: 0,
|
||||
trainingDays: [],
|
||||
showDetailsModal: false,
|
||||
selectedMember: {},
|
||||
loading: false,
|
||||
sortField: 'name',
|
||||
sortDirection: 'asc'
|
||||
sortDirection: 'asc',
|
||||
showTrainingDays: true,
|
||||
showMembers: false
|
||||
};
|
||||
},
|
||||
|
||||
@@ -204,6 +244,7 @@ export default {
|
||||
this.activeMembers = response.data.members || [];
|
||||
this.trainingsCount12Months = response.data.trainingsCount12Months || 0;
|
||||
this.trainingsCount3Months = response.data.trainingsCount3Months || 0;
|
||||
this.trainingDays = response.data.trainingDays || [];
|
||||
} catch (error) {
|
||||
// Kein Alert - es ist normal, dass nicht alle Daten verfügbar sind
|
||||
} finally {
|
||||
@@ -211,6 +252,20 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
toggleTrainingDays() {
|
||||
this.showTrainingDays = !this.showTrainingDays;
|
||||
},
|
||||
|
||||
toggleMembers() {
|
||||
this.showMembers = !this.showMembers;
|
||||
},
|
||||
|
||||
getWeekday(dateString) {
|
||||
const date = new Date(dateString);
|
||||
const weekdays = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
|
||||
return weekdays[date.getDay()];
|
||||
},
|
||||
|
||||
showMemberDetails(member) {
|
||||
this.selectedMember = member;
|
||||
this.showDetailsModal = true;
|
||||
@@ -300,12 +355,84 @@ export default {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.members-table-container {
|
||||
.collapsible-section {
|
||||
background: white;
|
||||
border-radius: var(--border-radius-large);
|
||||
box-shadow: var(--shadow-light);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-color);
|
||||
margin-bottom: 1.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: 1.25rem 1.5rem;
|
||||
background: var(--bg-light);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.section-header:hover {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.section-header:hover h3 {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.section-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.125rem;
|
||||
color: var(--text-primary);
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.training-days-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.training-days-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.training-days-table th,
|
||||
.training-days-table td {
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.training-days-table th {
|
||||
background: var(--bg-light);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.training-days-table tbody tr:hover {
|
||||
background: var(--bg-light);
|
||||
}
|
||||
|
||||
.training-days-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.members-table-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.members-table {
|
||||
|
||||
Reference in New Issue
Block a user