diff --git a/backend/controllers/memberActivityController.js b/backend/controllers/memberActivityController.js index 337a9d0..d91791d 100644 --- a/backend/controllers/memberActivityController.js +++ b/backend/controllers/memberActivityController.js @@ -4,6 +4,7 @@ import DiaryDateActivity from '../models/DiaryDateActivity.js'; import DiaryDates from '../models/DiaryDates.js'; import Participant from '../models/Participant.js'; import PredefinedActivity from '../models/PredefinedActivity.js'; +import GroupActivity from '../models/GroupActivity.js'; import { Op } from 'sequelize'; export const getMemberActivities = async (req, res) => { @@ -56,6 +57,11 @@ export const getMemberActivities = async (req, res) => { const memberActivities = await DiaryMemberActivity.findAll({ where: whereClause, include: [ + { + model: Participant, + as: 'participant', + attributes: ['id', 'groupId', 'diaryDateId'] + }, { model: DiaryDateActivity, as: 'activity', @@ -72,6 +78,11 @@ export const getMemberActivities = async (req, res) => { { model: PredefinedActivity, as: 'predefinedActivity' + }, + { + model: GroupActivity, + as: 'groupActivities', + required: false } ] } @@ -79,11 +90,25 @@ export const getMemberActivities = async (req, res) => { order: [[{ model: DiaryDateActivity, as: 'activity' }, { model: DiaryDates, as: 'diaryDate' }, 'date', 'DESC']] }); - // Group activities by name and count occurrences + // Group activities by name and count occurrences, considering group assignment const activityMap = new Map(); for (const ma of memberActivities) { - if (!ma.activity || !ma.activity.predefinedActivity) { + if (!ma.activity || !ma.activity.predefinedActivity || !ma.participant) { + continue; + } + + // Check group assignment + const participantGroupId = ma.participant.groupId; + const activityGroupIds = ma.activity.groupActivities?.map(ga => ga.groupId) || []; + + // Filter: Only count if: + // 1. Activity has no group assignment (empty activityGroupIds) - activity is for all groups OR + // 2. Participant's group matches one of the activity's groups + const shouldCount = activityGroupIds.length === 0 || + (participantGroupId !== null && activityGroupIds.includes(participantGroupId)); + + if (!shouldCount) { continue; } @@ -118,3 +143,90 @@ export const getMemberActivities = async (req, res) => { } }; +export const getMemberLastParticipations = async (req, res) => { + try { + const { authcode: userToken } = req.headers; + const { clubId, memberId } = req.params; + const { limit = 3 } = req.query; + + await checkAccess(userToken, clubId); + + // Get participant ID for this member + const participants = await Participant.findAll({ + where: { memberId: memberId } + }); + + if (participants.length === 0) { + return res.status(200).json([]); + } + + const participantIds = participants.map(p => p.id); + + // Get last participations for this member + const memberActivities = await DiaryMemberActivity.findAll({ + where: { + participantId: participantIds + }, + include: [ + { + model: Participant, + as: 'participant', + attributes: ['id', 'groupId', 'diaryDateId'] + }, + { + model: DiaryDateActivity, + as: 'activity', + include: [ + { + model: DiaryDates, + as: 'diaryDate' + }, + { + model: PredefinedActivity, + as: 'predefinedActivity' + }, + { + model: GroupActivity, + as: 'groupActivities', + required: false + } + ] + } + ], + order: [[{ model: DiaryDateActivity, as: 'activity' }, { model: DiaryDates, as: 'diaryDate' }, 'date', 'DESC']], + limit: parseInt(limit) * 10 // Get more to filter by group + }); + + // Format the results, considering group assignment + const participations = memberActivities + .filter(ma => { + if (!ma.activity || !ma.activity.predefinedActivity || !ma.activity.diaryDate || !ma.participant) { + return false; + } + + // Check group assignment + const participantGroupId = ma.participant.groupId; + const activityGroupIds = ma.activity.groupActivities?.map(ga => ga.groupId) || []; + + // Filter: Only count if: + // 1. Activity has no group assignment (empty activityGroupIds) - activity is for all groups OR + // 2. Participant's group matches one of the activity's groups + return activityGroupIds.length === 0 || + (participantGroupId !== null && activityGroupIds.includes(participantGroupId)); + }) + .slice(0, parseInt(limit)) // Limit after filtering + .map(ma => ({ + id: ma.id, + activityName: ma.activity.predefinedActivity.name, + date: ma.activity.diaryDate.date, + diaryDateId: ma.activity.diaryDate.id + })); + + return res.status(200).json(participations); + + } catch (error) { + console.error('Error fetching member last participations:', error); + return res.status(500).json({ error: 'Failed to fetch member last participations' }); + } +}; + diff --git a/backend/routes/memberActivityRoutes.js b/backend/routes/memberActivityRoutes.js index 23355fb..a45fbe4 100644 --- a/backend/routes/memberActivityRoutes.js +++ b/backend/routes/memberActivityRoutes.js @@ -1,11 +1,12 @@ import express from 'express'; import { authenticate } from '../middleware/authMiddleware.js'; -import { getMemberActivities } from '../controllers/memberActivityController.js'; +import { getMemberActivities, getMemberLastParticipations } from '../controllers/memberActivityController.js'; const router = express.Router(); router.use(authenticate); +router.get('/:clubId/:memberId/last-participations', getMemberLastParticipations); router.get('/:clubId/:memberId', getMemberActivities); export default router; diff --git a/frontend/src/components/MemberActivityStatsDialog.vue b/frontend/src/components/MemberActivityStatsDialog.vue new file mode 100644 index 0000000..ccf4005 --- /dev/null +++ b/frontend/src/components/MemberActivityStatsDialog.vue @@ -0,0 +1,152 @@ + + + + + + diff --git a/frontend/src/views/DiaryView.vue b/frontend/src/views/DiaryView.vue index 2afaece..91f94b5 100644 --- a/frontend/src/views/DiaryView.vue +++ b/frontend/src/views/DiaryView.vue @@ -361,6 +361,15 @@ @close="closeTagHistoryModal" /> + + + { - 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,