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:
152
frontend/src/components/MemberActivityStatsDialog.vue
Normal file
152
frontend/src/components/MemberActivityStatsDialog.vue
Normal 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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user