Enhance diary member activity management by adding validation and logging in addMembersToActivity function. Implement checks for participantIds to ensure they are an array, and log relevant information for better debugging. Update DiaryDateActivityService to improve error handling and logging for group activity associations. Modify frontend DiaryView to support group activity member assignment, including new methods for toggling and assigning members to group activities, enhancing user experience and functionality.

This commit is contained in:
Torsten Schulz (local)
2025-10-16 22:20:51 +02:00
parent 24aaa9c150
commit 01bbb85485
5 changed files with 190 additions and 6 deletions

View File

@@ -19,20 +19,44 @@ export const addMembersToActivity = async (req, res) => {
const { authcode: userToken } = req.headers;
const { clubId, diaryDateActivityId } = req.params;
const { participantIds } = req.body; // array of participant ids
console.log('[addMembersToActivity] Request:', {
clubId,
diaryDateActivityId,
participantIds,
bodyKeys: Object.keys(req.body)
});
await checkAccess(userToken, clubId);
if (!participantIds || !Array.isArray(participantIds)) {
console.error('[addMembersToActivity] Invalid participantIds:', participantIds);
return res.status(400).json({ error: 'participantIds must be an array' });
}
const validParticipants = await Participant.findAll({ where: { id: participantIds } });
console.log('[addMembersToActivity] Valid participants found:', validParticipants.length);
const validIds = new Set(validParticipants.map(p => p.id));
const created = [];
for (const pid of participantIds) {
if (!validIds.has(pid)) continue;
if (!validIds.has(pid)) {
console.log('[addMembersToActivity] Participant not found:', pid);
continue;
}
const existing = await DiaryMemberActivity.findOne({ where: { diaryDateActivityId, participantId: pid } });
if (!existing) {
const rec = await DiaryMemberActivity.create({ diaryDateActivityId, participantId: pid });
console.log('[addMembersToActivity] Created:', rec.id);
created.push(rec);
} else {
console.log('[addMembersToActivity] Already exists:', pid);
}
}
console.log('[addMembersToActivity] Success, created:', created.length);
res.status(201).json(created);
} catch (e) {
console.error('[addMembersToActivity] Error:', e);
res.status(500).json({ error: 'Error adding members to activity' });
}
};

View File

@@ -246,8 +246,20 @@ class DiaryDateActivityService {
throw new Error('Activity not found');
}
const group = await Group.findByPk(groupId);
if (!group || group.diaryDateId !== diaryDateActivity.diaryDateId) {
if (!group) {
console.error('[DiaryDateActivityService::addGroupActivity] Group not found:', groupId);
throw new Error('Group not found');
}
console.log('[DiaryDateActivityService::addGroupActivity] Group found:', {
groupId: group.id,
groupDiaryDateId: group.diaryDateId,
activityDiaryDateId: diaryDateActivity.diaryDateId
});
if (group.diaryDateId !== diaryDateActivity.diaryDateId) {
console.error('[DiaryDateActivityService::addGroupActivity] Group and date don\'t fit');
console.error('Group diaryDateId:', group.diaryDateId, 'Activity diaryDateId:', diaryDateActivity.diaryDateId);
throw new Error('Group isn\'t related to date');
}
const [predefinedActivity, created] = await PredefinedActivity.findOrCreate({

View File

@@ -17,7 +17,7 @@ class SchedulerService {
devLog('Scheduler is already running');
return;
}
devLog('Starting scheduler service...');
// Schedule automatic rating updates at 6:00 AM daily

View File

@@ -1 +1,19 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
{
"name": "Trainingstagebuch",
"short_name": "TT-Tagebuch",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#2c3e50",
"background_color": "#ffffff",
"display": "standalone"
}

View File

@@ -144,7 +144,7 @@
item.durationText }})</span>
</td>
<td>
<button @click="toggleActivityMembers(item)" title="Teilnehmer zuordnen"
<button v-if="!item.isTimeblock" @click="toggleActivityMembers(item)" title="Teilnehmer zuordnen"
class="person-btn">👤</button>
<button @click="removePlanItem(item.id)" class="trash-btn">🗑</button>
<div v-if="activityMembersOpenId === item.id" class="dropdown"
@@ -198,7 +198,38 @@
</td>
<td>{{ groupItem.groupsGroupActivity.name }}</td>
<td></td>
<td></td>
<td>
<button @click="toggleGroupActivityMembers(groupItem)" title="Teilnehmer zuordnen"
class="person-btn">👤</button>
<div v-if="groupActivityMembersOpenId === groupItem.id" class="dropdown"
style="max-height: 12em; padding: 0.25rem;">
<div style="margin-bottom: 0.25rem; font-weight: 600; display: flex; align-items: center; gap: 0.5rem;">
<span>Teilnehmer zuordnen</span>
<button @click="assignAllMembersToGroupActivity(groupItem.id)"
style="font-size: 10px; padding: 1px 4px; background: #28a745; color: white; border: none; border-radius: 2px;">
Alle
</button>
<select v-if="groups.length > 0"
@change="assignGroupToGroupActivity(groupItem.id, $event.target.value)"
style="font-size: 10px; width: 80px;">
<option value="">Gruppe...</option>
<option v-for="group in groups" :key="group.id" :value="group.id">
{{ group.name }}
</option>
</select>
</div>
<div style="max-height: 9.5em; overflow-y: auto;">
<label v-for="m in presentMembers" :key="m.id"
style="display:flex; align-items:center; gap:0.5rem;">
<input type="checkbox"
:checked="isAssignedToGroupActivity(groupItem.id, m.id)"
@change="toggleMemberForGroupActivity(groupItem.id, m.id, $event.target.checked)">
<span>{{ m.firstName }} {{ m.lastName }}</span>
</label>
</div>
</div>
</td>
</tr>
</template>
</template>
@@ -518,6 +549,8 @@ export default {
activityMembersMap: {}, // key: activityId, value: Set(participantIds)
activityGroupsMap: {}, // key: activityId, value: groupId
memberGroupsMap: {}, // key: memberId, value: groupId
groupActivityMembersOpenId: null,
groupActivityMembersMap: {}, // key: groupActivityId, value: Set(participantIds)
// Schnell hinzufügen Dialog
showQuickAddDialog: false,
newMember: {
@@ -1796,6 +1829,103 @@ export default {
}
},
// Group Activity Members Methods
async toggleGroupActivityMembers(groupItem) {
if (this.groupActivityMembersOpenId === groupItem.id) {
this.groupActivityMembersOpenId = null;
return;
}
this.groupActivityMembersOpenId = groupItem.id;
await this.ensureGroupActivityMembersLoaded(groupItem.id);
},
async ensureGroupActivityMembersLoaded(groupActivityId) {
if (this.groupActivityMembersMap[groupActivityId]) return;
try {
const r = await apiClient.get(`/diary-member-activities/${this.currentClub}/${groupActivityId}`);
const setIds = new Set(r.data.map(x => x.participantId));
this.$set ? this.$set(this.groupActivityMembersMap, groupActivityId, setIds) : (this.groupActivityMembersMap[groupActivityId] = setIds);
} catch (e) {
this.groupActivityMembersMap[groupActivityId] = new Set();
}
},
isAssignedToGroupActivity(groupActivityId, memberId) {
const participantId = this.participantIdForMember(memberId);
const setIds = this.groupActivityMembersMap[groupActivityId];
return setIds ? setIds.has(participantId) : false;
},
async toggleMemberForGroupActivity(groupActivityId, memberId, checked) {
let participantId = this.participantIdForMember(memberId);
try {
if (checked) {
// Falls Mitglied noch kein Participant ist: zuerst hinzufügen
if (!participantId) {
const created = await apiClient.post('/participants/add', {
diaryDateId: this.date.id,
memberId: memberId,
});
participantId = created.data.id;
if (!this.participantMapByMemberId) this.participantMapByMemberId = {};
this.participantMapByMemberId[memberId] = participantId;
if (!this.participants.includes(memberId)) this.participants.push(memberId);
}
// Zuordnung erstellen
await apiClient.post(`/diary-member-activities/${this.currentClub}/${groupActivityId}`, {
participantIds: [participantId],
});
const setIds = this.groupActivityMembersMap[groupActivityId] || new Set();
setIds.add(participantId);
this.$set ? this.$set(this.groupActivityMembersMap, groupActivityId, setIds) : (this.groupActivityMembersMap[groupActivityId] = setIds);
} else {
// Zuordnung entfernen
await apiClient.delete(`/diary-member-activities/${this.currentClub}/${groupActivityId}/${participantId}`);
const setIds = this.groupActivityMembersMap[groupActivityId];
if (setIds) {
setIds.delete(participantId);
}
}
} catch (error) {
console.error('Fehler beim Zuordnen des Teilnehmers zu Gruppen-Aktivität:', error);
this.showInfo('Fehler', 'Teilnehmer konnte nicht zugeordnet werden.', '', 'error');
}
},
async assignAllMembersToGroupActivity(groupActivityId) {
try {
// Alle anwesenden Mitglieder zur Gruppen-Aktivität hinzufügen
for (const member of this.presentMembers) {
if (!this.isAssignedToGroupActivity(groupActivityId, member.id)) {
await this.toggleMemberForGroupActivity(groupActivityId, member.id, true);
}
}
} catch (error) {
console.error('Fehler beim Zuordnen aller Teilnehmer:', error);
this.showInfo('Fehler', 'Fehler beim Zuordnen aller Teilnehmer', '', 'error');
}
},
async assignGroupToGroupActivity(groupActivityId, groupId) {
if (!groupId) return; // Leere Auswahl ignorieren
try {
// Alle Mitglieder der gewählten Gruppe zur Gruppen-Aktivität hinzufügen
const groupMembers = this.presentMembers.filter(member =>
this.getMemberGroup(member.id) === groupId
);
for (const member of groupMembers) {
if (!this.isAssignedToGroupActivity(groupActivityId, member.id)) {
await this.toggleMemberForGroupActivity(groupActivityId, member.id, true);
}
}
} catch (error) {
console.error('Fehler beim Zuordnen der Gruppe:', error);
this.showInfo('Fehler', 'Fehler beim Zuordnen der Gruppe', '', 'error');
}
},
// Gruppenzuordnung für Teilnehmer
getMemberGroup(memberId) {
return this.memberGroupsMap[memberId] || '';