Refactor member activity display to group participations by date

This commit updates the `MemberActivityStatsDialog` component to group member participations by date, enhancing the presentation of activity data. The logic is introduced to aggregate activities under their respective dates, ensuring a clearer and more organized display. Additionally, CSS styles are added to improve the visual hierarchy and user experience when viewing recent participations.
This commit is contained in:
Torsten Schulz (local)
2025-11-14 23:27:46 +01:00
parent 5dda346fd7
commit ce2bda37ac
2 changed files with 206 additions and 121 deletions

View File

@@ -99,74 +99,73 @@ export const getMemberActivities = async (req, res) => {
// 2. Get all group activities for groups the member belongs to
const groupActivities = [];
if (memberGroupIds.size > 0) {
const groupActivitiesData = await DiaryDateActivity.findAll({
// Suche direkt nach GroupActivity-Einträgen für die Gruppen des Members
const groupActivitiesData = await GroupActivity.findAll({
where: {
groupId: {
[Op.in]: Array.from(memberGroupIds)
}
},
include: [
{
model: DiaryDates,
as: 'diaryDate',
where: startDate ? {
date: {
[Op.gte]: startDate
model: DiaryDateActivity,
as: 'activityGroupActivity',
include: [
{
model: DiaryDates,
as: 'diaryDate',
where: startDate ? {
date: {
[Op.gte]: startDate
}
} : {}
},
{
model: PredefinedActivity,
as: 'predefinedActivity',
required: false
}
} : {}
]
},
{
model: PredefinedActivity,
as: 'predefinedActivity',
required: false // Kann null sein für Gruppen-Aktivitäten
},
{
model: GroupActivity,
as: 'groupActivities',
where: {
groupId: {
[Op.in]: Array.from(memberGroupIds)
}
},
required: true,
include: [
{
model: PredefinedActivity,
as: 'groupPredefinedActivity',
required: false // Kann null sein
}
]
as: 'groupPredefinedActivity',
required: false
}
]
});
// Erstelle virtuelle DiaryMemberActivity-Objekte für Gruppen-Aktivitäten
for (const activity of groupActivitiesData) {
// Finde den entsprechenden Participant für diese Aktivität
for (const groupActivity of groupActivitiesData) {
if (!groupActivity.activityGroupActivity || !groupActivity.activityGroupActivity.diaryDate) {
continue; // Überspringe, wenn keine DiaryDateActivity oder kein DiaryDate vorhanden
}
const activity = groupActivity.activityGroupActivity;
const diaryDateId = activity.diaryDateId;
const relevantParticipants = participants.filter(p => p.diaryDateId === diaryDateId);
// Finde alle relevanten Participants für dieses DiaryDate
const relevantParticipants = participants.filter(p =>
p.diaryDateId === diaryDateId &&
p.groupId === groupActivity.groupId
);
for (const participant of relevantParticipants) {
// Prüfe, ob die Aktivität für die Gruppe des Participants ist
const activityGroupIds = activity.groupActivities?.map(ga => ga.groupId) || [];
if (participant.groupId !== null && activityGroupIds.includes(participant.groupId)) {
// Für Gruppen-Aktivitäten: Verwende die PredefinedActivity aus GroupActivity
// Falls vorhanden, sonst die aus DiaryDateActivity
const groupActivity = activity.groupActivities?.find(ga => ga.groupId === participant.groupId);
if (groupActivity && groupActivity.groupPredefinedActivity) {
// Erstelle ein modifiziertes Activity-Objekt mit der PredefinedActivity aus GroupActivity
const modifiedActivity = {
...activity.toJSON(),
predefinedActivity: groupActivity.groupPredefinedActivity
};
groupActivities.push({
activity: modifiedActivity,
participant: participant,
id: null // Virtuell, nicht in DB
});
} else if (activity.predefinedActivity) {
// Fallback: Verwende die PredefinedActivity aus DiaryDateActivity
groupActivities.push({
activity: activity,
participant: participant,
id: null // Virtuell, nicht in DB
});
}
// Verwende die PredefinedActivity aus GroupActivity, falls vorhanden
// Sonst die aus DiaryDateActivity
const predefinedActivity = groupActivity.groupPredefinedActivity || activity.predefinedActivity;
if (predefinedActivity) {
// Erstelle ein modifiziertes Activity-Objekt
const modifiedActivity = {
...activity.toJSON(),
predefinedActivity: predefinedActivity
};
groupActivities.push({
activity: modifiedActivity,
participant: participant,
id: null // Virtuell, nicht in DB
});
}
}
}
@@ -299,71 +298,70 @@ export const getMemberLastParticipations = async (req, res) => {
// 2. Get all group activities for groups the member belongs to
const groupActivities = [];
if (memberGroupIds.size > 0) {
const groupActivitiesData = await DiaryDateActivity.findAll({
// Suche direkt nach GroupActivity-Einträgen für die Gruppen des Members
const groupActivitiesData = await GroupActivity.findAll({
where: {
groupId: {
[Op.in]: Array.from(memberGroupIds)
}
},
include: [
{
model: DiaryDates,
as: 'diaryDate'
model: DiaryDateActivity,
as: 'activityGroupActivity',
include: [
{
model: DiaryDates,
as: 'diaryDate'
},
{
model: PredefinedActivity,
as: 'predefinedActivity',
required: false
}
]
},
{
model: PredefinedActivity,
as: 'predefinedActivity',
required: false // Kann null sein für Gruppen-Aktivitäten
},
{
model: GroupActivity,
as: 'groupActivities',
where: {
groupId: {
[Op.in]: Array.from(memberGroupIds)
}
},
required: true,
include: [
{
model: PredefinedActivity,
as: 'groupPredefinedActivity',
required: false // Kann null sein
}
]
as: 'groupPredefinedActivity',
required: false
}
],
order: [[{ model: DiaryDates, as: 'diaryDate' }, 'date', 'DESC']],
order: [[{ model: DiaryDateActivity, as: 'activityGroupActivity' }, { model: DiaryDates, as: 'diaryDate' }, 'date', 'DESC']],
limit: parseInt(limit) * 10 // Get more to filter
});
// Erstelle virtuelle DiaryMemberActivity-Objekte für Gruppen-Aktivitäten
for (const activity of groupActivitiesData) {
// Finde den entsprechenden Participant für diese Aktivität
for (const groupActivity of groupActivitiesData) {
if (!groupActivity.activityGroupActivity || !groupActivity.activityGroupActivity.diaryDate) {
continue; // Überspringe, wenn keine DiaryDateActivity oder kein DiaryDate vorhanden
}
const activity = groupActivity.activityGroupActivity;
const diaryDateId = activity.diaryDateId;
const relevantParticipants = participants.filter(p => p.diaryDateId === diaryDateId);
// Finde alle relevanten Participants für dieses DiaryDate
const relevantParticipants = participants.filter(p =>
p.diaryDateId === diaryDateId &&
p.groupId === groupActivity.groupId
);
for (const participant of relevantParticipants) {
// Prüfe, ob die Aktivität für die Gruppe des Participants ist
const activityGroupIds = activity.groupActivities?.map(ga => ga.groupId) || [];
if (participant.groupId !== null && activityGroupIds.includes(participant.groupId)) {
// Für Gruppen-Aktivitäten: Verwende die PredefinedActivity aus GroupActivity
// Falls vorhanden, sonst die aus DiaryDateActivity
const groupActivity = activity.groupActivities?.find(ga => ga.groupId === participant.groupId);
if (groupActivity && groupActivity.groupPredefinedActivity) {
// Erstelle ein modifiziertes Activity-Objekt mit der PredefinedActivity aus GroupActivity
const modifiedActivity = {
...activity.toJSON(),
predefinedActivity: groupActivity.groupPredefinedActivity
};
groupActivities.push({
activity: modifiedActivity,
participant: participant,
id: null // Virtuell, nicht in DB
});
} else if (activity.predefinedActivity) {
// Fallback: Verwende die PredefinedActivity aus DiaryDateActivity
groupActivities.push({
activity: activity,
participant: participant,
id: null // Virtuell, nicht in DB
});
}
// Verwende die PredefinedActivity aus GroupActivity, falls vorhanden
// Sonst die aus DiaryDateActivity
const predefinedActivity = groupActivity.groupPredefinedActivity || activity.predefinedActivity;
if (predefinedActivity) {
// Erstelle ein modifiziertes Activity-Objekt
const modifiedActivity = {
...activity.toJSON(),
predefinedActivity: predefinedActivity
};
groupActivities.push({
activity: modifiedActivity,
participant: participant,
id: null // Virtuell, nicht in DB
});
}
}
}
@@ -392,27 +390,57 @@ export const getMemberLastParticipations = async (req, res) => {
// Kombiniere beide Listen
const allActivities = [...memberActivities, ...uniqueGroupActivities];
// Format the results
const participations = allActivities
// Gruppiere nach Datum
const participationsByDate = new Map();
allActivities
.filter(ma => {
if (!ma.activity || !ma.activity.predefinedActivity || !ma.activity.diaryDate || !ma.participant) {
return false;
}
return true;
})
.forEach(ma => {
const date = ma.activity.diaryDate.date;
const diaryDateId = ma.activity.diaryDate.id;
const activityName = ma.activity.predefinedActivity.name;
if (!participationsByDate.has(date)) {
participationsByDate.set(date, {
date: date,
diaryDateId: diaryDateId,
activities: []
});
}
const dateEntry = participationsByDate.get(date);
// Füge Aktivität nur hinzu, wenn sie noch nicht vorhanden ist (vermeide Duplikate)
if (!dateEntry.activities.find(a => a === activityName)) {
dateEntry.activities.push(activityName);
}
});
// Sortiere nach Datum (neueste zuerst) und nehme die letzten N Daten
const sortedDates = Array.from(participationsByDate.values())
.sort((a, b) => {
// Sortiere nach Datum (neueste zuerst)
const dateA = new Date(a.activity.diaryDate.date);
const dateB = new Date(b.activity.diaryDate.date);
const dateA = new Date(a.date);
const dateB = new Date(b.date);
return dateB - dateA;
})
.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
}));
.slice(0, parseInt(limit));
// Formatiere für das Frontend: Flache Liste mit Datum und Aktivität
const participations = [];
sortedDates.forEach(dateEntry => {
dateEntry.activities.forEach(activityName => {
participations.push({
id: null, // Virtuell
activityName: activityName,
date: dateEntry.date,
diaryDateId: dateEntry.diaryDateId
});
});
});
return res.status(200).json(participations);