From b906ac64b3bd2e4862758638731d15e04db938e3 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Mon, 17 Nov 2025 10:12:21 +0100 Subject: [PATCH] Add updateGroupActivity method and corresponding route for editing group activities This commit introduces the updateGroupActivity method in the diaryDateActivityController, allowing users to update existing group activities by linking them to predefined activities. The method includes error handling and emits a socket event upon successful updates. Additionally, the diaryDateActivityRoutes file is updated to include a new PUT route for updating group activities. Frontend changes in DiaryView enhance the user experience by enabling inline editing of group activities, including search functionality for predefined activities. --- .../diaryDateActivityController.js | 30 ++ backend/routes/diaryDateActivityRoutes.js | 2 + backend/services/diaryDateActivityService.js | 18 ++ frontend/src/i18n/locales/de.json | 4 +- frontend/src/views/DiaryView.vue | 271 +++++++++++++++--- 5 files changed, 285 insertions(+), 40 deletions(-) diff --git a/backend/controllers/diaryDateActivityController.js b/backend/controllers/diaryDateActivityController.js index 38629ec..9f80f51 100644 --- a/backend/controllers/diaryDateActivityController.js +++ b/backend/controllers/diaryDateActivityController.js @@ -137,6 +137,36 @@ export const addGroupActivity = async(req, res) => { } } +export const updateGroupActivity = async(req, res) => { + try { + const { authcode: userToken } = req.headers; + const { clubId, groupActivityId } = req.params; + const { predefinedActivityId } = req.body; + const activityItem = await diaryDateActivityService.updateGroupActivity(userToken, clubId, groupActivityId, predefinedActivityId); + + // Emit Socket-Event + const GroupActivity = (await import('../models/GroupActivity.js')).default; + const DiaryDateActivity = (await import('../models/DiaryDateActivity.js')).default; + const groupActivity = await GroupActivity.findByPk(groupActivityId); + let diaryDateId = null; + if (groupActivity?.diaryDateActivity) { + const activity = await DiaryDateActivity.findByPk(groupActivity.diaryDateActivity); + diaryDateId = activity?.diaryDateId; + } + if (diaryDateId) { + const diaryDate = await DiaryDate.findByPk(diaryDateId); + if (diaryDate?.clubId) { + emitActivityChanged(diaryDate.clubId, diaryDateId); + } + } + + res.status(200).json(activityItem); + } catch (error) { + devLog(error); + res.status(500).json({ error: 'Error updating group activity' }); + } +} + export const deleteGroupActivity = async(req, res) => { try { const { authcode: userToken } = req.headers; diff --git a/backend/routes/diaryDateActivityRoutes.js b/backend/routes/diaryDateActivityRoutes.js index 3f9ecc0..44ac94e 100644 --- a/backend/routes/diaryDateActivityRoutes.js +++ b/backend/routes/diaryDateActivityRoutes.js @@ -6,6 +6,7 @@ import { updateDiaryDateActivityOrder, getDiaryDateActivities, addGroupActivity, + updateGroupActivity, deleteGroupActivity, } from '../controllers/diaryDateActivityController.js'; import { authenticate } from '../middleware/authMiddleware.js'; @@ -15,6 +16,7 @@ const router = express.Router(); router.use(authenticate); router.post('/group', addGroupActivity); +router.put('/group/:clubId/:groupActivityId', updateGroupActivity); router.delete('/group/:clubId/:groupActivityId', deleteGroupActivity); router.post('/:clubId/', createDiaryDateActivity); router.put('/:clubId/:id/order', updateDiaryDateActivityOrder); diff --git a/backend/services/diaryDateActivityService.js b/backend/services/diaryDateActivityService.js index 2c381c0..3a3b072 100644 --- a/backend/services/diaryDateActivityService.js +++ b/backend/services/diaryDateActivityService.js @@ -391,6 +391,24 @@ class DiaryDateActivityService { return await GroupActivity.create(activityData); } + async updateGroupActivity(userToken, clubId, groupActivityId, predefinedActivityId) { + await checkAccess(userToken, clubId); + const groupActivity = await GroupActivity.findByPk(groupActivityId); + if (!groupActivity) { + throw new Error('Group activity not found'); + } + + // Prüfe, ob die PredefinedActivity existiert + const predefinedActivity = await PredefinedActivity.findByPk(predefinedActivityId); + if (!predefinedActivity) { + throw new Error('Predefined activity not found'); + } + + // Aktualisiere die customActivity (die auf die PredefinedActivity verweist) + groupActivity.customActivity = predefinedActivityId; + return await groupActivity.save(); + } + async deleteGroupActivity(userToken, clubId, groupActivityId) { await checkAccess(userToken, clubId); const groupActivity = await GroupActivity.findByPk(groupActivityId); diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index 63bb9a6..52b8e55 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -352,7 +352,9 @@ "trainingTimesUpdated": "Trainingszeiten erfolgreich aktualisiert.", "formMarkedAsHandedOver": "Mitgliedsformular als ausgehändigt markiert", "errorMarkingForm": "Fehler beim Markieren des Mitgliedsformulars", - "dateNoLongerCurrent": "Ausgewähltes Datum war nicht mehr aktuell. Bitte erneut versuchen." + "dateNoLongerCurrent": "Ausgewähltes Datum war nicht mehr aktuell. Bitte erneut versuchen.", + "activityRequired": "Bitte geben Sie eine Aktivität ein.", + "activityNotFound": "Aktivität nicht gefunden. Bitte wählen Sie eine aus der Liste aus." }, "home": { "welcome": "Willkommen im TrainingsTagebuch", diff --git a/frontend/src/views/DiaryView.vue b/frontend/src/views/DiaryView.vue index 337d7eb..4b46616 100644 --- a/frontend/src/views/DiaryView.vue +++ b/frontend/src/views/DiaryView.vue @@ -176,6 +176,15 @@
+
-
@@ -290,6 +294,11 @@
+
@@ -303,7 +312,38 @@ - +
+ +
+ + +
+ + +
+
+
- @@ -369,7 +407,7 @@ - +
@@ -425,6 +449,11 @@
+ @@ -675,9 +704,9 @@ 0) return true; + } + // Prüfe auch über predefinedActivity + if (item.predefinedActivity && this.hasActivityVisual(item.predefinedActivity)) return true; + } catch (e) { } + return false; + }, + hasActivityVisual(pa) { if (!pa) return false; try { @@ -2483,28 +2528,60 @@ export default { try { const code = result.code.trim(); - // Wenn eine Gruppenaktivität bearbeitet wird + // Wenn eine Gruppenaktivität oder normale Aktivität bearbeitet wird if (this.editingGroupActivity && this.editingGroupActivity.groupPredefinedActivity) { const predefinedActivityId = this.editingGroupActivity.groupPredefinedActivity.id; - // Aktualisiere die PredefinedActivity - const updateData = { - name: result.name || (result.fields && result.fields.name) || this.editingGroupActivity.groupPredefinedActivity.name || '', - code: code, - description: result.description || (result.fields && result.fields.description) || this.editingGroupActivity.groupPredefinedActivity.description || '', - durationText: this.editingGroupActivity.groupPredefinedActivity.durationText || '', - duration: this.editingGroupActivity.groupPredefinedActivity.duration || null, - imageLink: this.editingGroupActivity.groupPredefinedActivity.imageLink || '', - drawingData: result.drawingData || null - }; + let finalPredefinedActivityId = predefinedActivityId; - await apiClient.put(`/predefined-activities/${predefinedActivityId}`, updateData); + // Wenn keine predefinedActivityId vorhanden ist, erstelle eine neue + if (!predefinedActivityId) { + const newActivityData = { + name: result.name || (result.fields && result.fields.name) || this.editingGroupActivity.groupPredefinedActivity.name || '', + code: code, + description: result.description || (result.fields && result.fields.description) || this.editingGroupActivity.groupPredefinedActivity.description || '', + drawingData: result.drawingData || null + }; + + const createResponse = await apiClient.post('/predefined-activities', newActivityData); + finalPredefinedActivityId = createResponse.data.id; + + // Wenn es eine normale Aktivität war, verknüpfe die DiaryDateActivity mit der neuen PredefinedActivity + if (this.editingGroupActivity.isNormalActivity && this.editingGroupActivity.activityItem) { + const activityItem = this.editingGroupActivity.activityItem; + if (activityItem.id && this.currentClub) { + // currentClub ist die Club-ID (Zahl), nicht ein Objekt + await apiClient.put(`/diary-date-activities/${this.currentClub}/${activityItem.id}`, { + predefinedActivityId: finalPredefinedActivityId + }); + } + } + } else { + // Aktualisiere die existierende PredefinedActivity + const updateData = { + name: result.name || (result.fields && result.fields.name) || this.editingGroupActivity.groupPredefinedActivity.name || '', + code: code, + description: result.description || (result.fields && result.fields.description) || this.editingGroupActivity.groupPredefinedActivity.description || '', + durationText: this.editingGroupActivity.groupPredefinedActivity.durationText || '', + duration: this.editingGroupActivity.groupPredefinedActivity.duration || null, + imageLink: this.editingGroupActivity.groupPredefinedActivity.imageLink || '', + drawingData: result.drawingData || null + }; + + await apiClient.put(`/predefined-activities/${predefinedActivityId}`, updateData); + } // Lade den Trainingsplan neu if (this.date && this.date.id) { await this.loadTrainingPlan(); } + // Wenn es eine normale Aktivität war, beende auch den Inline-Edit-Modus + if (this.editingGroupActivity.isNormalActivity && this.editingGroupActivity.activityItem) { + this.editingActivityId = null; + this.editingActivityText = ''; + } + this.editingGroupActivity = null; return; } @@ -2570,6 +2647,39 @@ export default { } }, + editActivity(item) { + if (!item) { + this.showInfo('Fehler', 'Keine Aktivität zum Bearbeiten gefunden', '', 'error'); + return; + } + + // Wenn keine predefinedActivity vorhanden ist, aber drawingData, erstelle eine temporäre Struktur + let predefinedActivity = item.predefinedActivity; + if (!predefinedActivity && item.drawingData) { + // Erstelle eine temporäre Struktur für die Bearbeitung + predefinedActivity = { + id: null, + name: item.activity || '', + code: item.activity || '', + description: '', + drawingData: item.drawingData + }; + } + + if (!predefinedActivity) { + this.showInfo('Fehler', 'Keine Aktivität zum Bearbeiten gefunden', '', 'error'); + return; + } + + // Verwende editingGroupActivity auch für normale Aktivitäten, da die Struktur ähnlich ist + this.editingGroupActivity = { + groupPredefinedActivity: predefinedActivity, + isNormalActivity: true, + activityItem: item + }; + this.showDrawingDialog = true; + }, + editGroupActivity(groupItem) { if (!groupItem || !groupItem.groupPredefinedActivity) { this.showInfo('Fehler', 'Keine Aktivität zum Bearbeiten gefunden', '', 'error'); @@ -2578,6 +2688,89 @@ export default { this.editingGroupActivity = groupItem; this.showDrawingDialog = true; }, + + startGroupActivityEdit(groupItem) { + console.log('[startGroupActivityEdit] groupItem.id:', groupItem.id, 'type:', typeof groupItem.id); + this.editingGroupActivityId = Number(groupItem.id); + this.editSearchForId = Number(groupItem.id); + console.log('[startGroupActivityEdit] editingGroupActivityId set to:', this.editingGroupActivityId); + this.$nextTick(() => { + const inputs = this.$el.querySelectorAll('input[type="text"]'); + const lastInput = inputs[inputs.length - 1]; + if (lastInput) { + lastInput.focus(); + } + }); + this.editingGroupActivityText = groupItem.groupPredefinedActivity + ? (groupItem.groupPredefinedActivity.code && groupItem.groupPredefinedActivity.code.trim() !== '' + ? groupItem.groupPredefinedActivity.code + : groupItem.groupPredefinedActivity.name) + : ''; + }, + + async onEditGroupActivityInputChange(groupItem) { + const term = this.editingGroupActivityText; + this.editSearchForId = groupItem.id; + if (!term || term.trim().length < 2) { + this.editShowDropdown = false; + this.editSearchResults = []; + return; + } + const results = await this.searchPredefinedActivities(term); + this.editSearchResults = results; + this.editShowDropdown = results.length > 0; + }, + + chooseEditGroupActivitySuggestion(s, groupItem) { + this.editingGroupActivityText = (s.code && s.code.trim() !== '') ? s.code : s.name; + this.editShowDropdown = false; + this.editSearchResults = []; + }, + + async saveGroupActivityEdit(groupItem) { + try { + if (!this.editingGroupActivityText || !this.editingGroupActivityText.trim()) { + this.showInfo(this.$t('messages.note'), this.$t('diary.activityRequired'), '', 'warning'); + return; + } + + // Suche nach der PredefinedActivity + const searchResults = await this.searchPredefinedActivities(this.editingGroupActivityText.trim()); + const existing = searchResults.find(a => { + const searchTerm = this.editingGroupActivityText.trim().toLowerCase(); + return (a.code && a.code.trim().toLowerCase() === searchTerm) || + (a.name && a.name.trim().toLowerCase() === searchTerm); + }); + + if (!existing) { + this.showInfo(this.$t('messages.note'), this.$t('diary.activityNotFound'), '', 'warning'); + return; + } + + // Aktualisiere die Gruppenaktivität mit der neuen PredefinedActivity + await apiClient.put(`/diary-date-activities/group/${this.currentClub}/${groupItem.id}`, { + predefinedActivityId: existing.id + }); + + // Lade den Trainingsplan neu + await this.loadTrainingPlan(); + + this.editingGroupActivityId = null; + this.editingGroupActivityText = ''; + this.editShowDropdown = false; + this.editSearchResults = []; + } catch (error) { + const msg = getSafeErrorMessage(error, 'Fehler beim Speichern der Gruppenaktivität'); + this.showInfo('Fehler', msg, '', 'error'); + } + }, + + cancelGroupActivityEdit() { + this.editingGroupActivityId = null; + this.editingGroupActivityText = ''; + this.editShowDropdown = false; + this.editSearchResults = []; + }, async loadTrainingPlan() { try {