Implement last participations endpoint and enhance member activity retrieval

Added a new endpoint to fetch the last participations of a member, including group assignment checks for activities. Updated the member activity controller to include logic for filtering activities based on participant group IDs. Enhanced the DiaryView component to display activity statistics and last participations in a modal, improving user experience and data accessibility.
This commit is contained in:
Torsten Schulz (local)
2025-10-31 16:33:20 +01:00
parent 7e85926aa1
commit a8318c74cf
4 changed files with 306 additions and 13 deletions

View File

@@ -0,0 +1,152 @@
<template>
<BaseDialog
:model-value="modelValue"
@update:model-value="$emit('update:modelValue', $event)"
:title="`Übungs-Statistiken ${member ? member.firstName + ' ' + member.lastName : ''}`"
size="medium"
@close="handleClose"
>
<div class="activity-stats-content">
<!-- Letzte 3 Teilnahmen -->
<div class="section">
<h3 class="section-title">Letzte 3 Teilnahmen</h3>
<div v-if="lastParticipations && lastParticipations.length" class="participations-list">
<div v-for="participation in lastParticipations" :key="participation.id" class="participation-item">
<div class="participation-name">{{ participation.activityName }}</div>
<div class="participation-date">{{ formatDate(participation.date) }}</div>
</div>
</div>
<div v-else class="no-data">
<em>Keine Teilnahmen vorhanden</em>
</div>
</div>
<!-- Statistik der Übungen -->
<div class="section">
<h3 class="section-title">Statistik der Übungen</h3>
<div v-if="activityStats && activityStats.length" class="stats-list">
<div v-for="stat in activityStats" :key="stat.name" class="stat-item">
<div class="stat-name">{{ stat.name }}</div>
<div class="stat-count">{{ stat.count }}x</div>
</div>
</div>
<div v-else class="no-data">
<em>Keine Statistiken vorhanden</em>
</div>
</div>
</div>
</BaseDialog>
</template>
<script>
import BaseDialog from './BaseDialog.vue';
export default {
name: 'MemberActivityStatsDialog',
components: {
BaseDialog
},
props: {
modelValue: {
type: Boolean,
default: false
},
member: {
type: Object,
default: null
},
lastParticipations: {
type: Array,
default: () => []
},
activityStats: {
type: Array,
default: () => []
}
},
emits: ['update:modelValue', 'close'],
methods: {
handleClose() {
this.$emit('update:modelValue', false);
this.$emit('close');
},
formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
return date.toLocaleDateString('de-DE');
}
}
};
</script>
<style scoped>
.activity-stats-content {
display: flex;
flex-direction: column;
gap: 2rem;
}
.section {
display: flex;
flex-direction: column;
gap: 1rem;
}
.section-title {
font-weight: 600;
font-size: 1.1rem;
color: var(--primary-color);
margin: 0;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--background-light);
}
.participations-list,
.stats-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.participation-item,
.stat-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
background: var(--background-light);
border-radius: 4px;
transition: background-color 0.3s ease;
}
.participation-item:hover,
.stat-item:hover {
background: var(--background-hover, rgba(0, 0, 0, 0.05));
}
.participation-name,
.stat-name {
font-weight: 500;
color: var(--text-color);
flex: 1;
}
.participation-date {
color: var(--text-muted);
font-size: 0.9rem;
}
.stat-count {
font-weight: 600;
color: var(--primary-color);
font-size: 1.1rem;
}
.no-data {
padding: 2rem;
text-align: center;
color: var(--text-muted);
font-style: italic;
}
</style>

View File

@@ -361,6 +361,15 @@
@close="closeTagHistoryModal"
/>
<!-- Activity Stats Modal -->
<MemberActivityStatsDialog
v-model="showActivityStatsModal"
:member="activityStatsMember"
:last-participations="lastParticipations"
:activity-stats="activityStats"
@close="closeActivityStatsModal"
/>
<!-- Notizen Modal -->
<MemberNotesDialog
v-model="showNotesModal"
@@ -454,6 +463,7 @@ import ImageDialog from '../components/ImageDialog.vue';
import BaseDialog from '../components/BaseDialog.vue';
import MemberNotesDialog from '../components/MemberNotesDialog.vue';
import TagHistoryDialog from '../components/TagHistoryDialog.vue';
import MemberActivityStatsDialog from '../components/MemberActivityStatsDialog.vue';
import AccidentFormDialog from '../components/AccidentFormDialog.vue';
import QuickAddMemberDialog from '../components/QuickAddMemberDialog.vue';
@@ -469,6 +479,7 @@ export default {
BaseDialog,
MemberNotesDialog,
TagHistoryDialog,
MemberActivityStatsDialog,
AccidentFormDialog,
QuickAddMemberDialog
},
@@ -538,6 +549,10 @@ export default {
showTagHistoryModal: false,
tagHistoryMember: null,
tagHistory: null,
showActivityStatsModal: false,
activityStatsMember: null,
lastParticipations: [],
activityStats: [],
intermediateTimes: [],
bellSound: null,
thumbSound: null,
@@ -1527,21 +1542,34 @@ export default {
if (!member) {
return;
}
this.showTagHistoryModal = true;
this.tagHistoryMember = member;
const tags = await apiClient.get(`/diarydatetags/${this.currentClub}/${member.id}`);
this.tagHistory = tags.data;
this.selectedMemberDayTags = [];
this.selectedMemberDayTags = this.tagHistory.filter(tag => {
return tag.diaryMemberTags.some(memberTag =>
memberTag.diaryDates && memberTag.diaryDates.id === this.date.id
);
});
this.showActivityStatsModal = true;
this.activityStatsMember = member;
try {
// Lade letzte 3 Teilnahmen
const lastParticipationsResponse = await apiClient.get(`/member-activities/${this.currentClub}/${member.id}/last-participations?limit=3`);
this.lastParticipations = lastParticipationsResponse.data || [];
// Lade Statistik der Übungen
const statsResponse = await apiClient.get(`/member-activities/${this.currentClub}/${member.id}?period=all`);
this.activityStats = statsResponse.data || [];
} catch (error) {
console.error('Error loading activity stats:', error);
this.showInfo('Fehler', 'Fehler beim Laden der Statistiken.', '', 'error');
this.lastParticipations = [];
this.activityStats = [];
}
},
closeTagHistoryModal() {
this.showTagHistoryModal = false;
this.tagHistoryMember = null;
},
closeActivityStatsModal() {
this.showActivityStatsModal = false;
this.activityStatsMember = null;
this.lastParticipations = [];
this.activityStats = [];
},
async addNewTagForDay(tag) {
await apiClient.post(`/diarydatetags/${this.currentClub}`, {
dateId: this.date.id,