Merge branch 'httv'

This commit is contained in:
Torsten Schulz (local)
2025-10-01 13:31:41 +02:00
2 changed files with 157 additions and 6 deletions

View File

@@ -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) {

View File

@@ -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 {