From 7a35a0a1d3f998b4652b7ffeb54c65f384fad2cc Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 29 Oct 2025 11:48:24 +0100 Subject: [PATCH] Update server port and enhance participant management features Changed the server port from 3000 to 3005 for local development. Enhanced the participant management functionality by adding a new endpoint to update participant group assignments, including error handling for non-existent participants. Updated the participant model to include a groupId reference, and modified the participant retrieval logic to include group information. Additionally, improved the frontend API client to accommodate the new backend structure and added filtering options in the MembersView for better user experience. --- backend/controllers/participantController.js | 31 ++- .../add_group_id_to_participants.sql | 9 + backend/models/Participant.js | 11 + backend/routes/participantRoutes.js | 3 +- backend/server.js | 2 +- frontend/src/apiClient.js | 2 +- frontend/src/views/DiaryView.vue | 47 +++- frontend/src/views/Login.vue | 2 +- frontend/src/views/MembersView.vue | 200 +++++++++++++++++- frontend/src/views/PredefinedActivities.vue | 2 +- frontend/src/views/Register.vue | 2 +- 11 files changed, 288 insertions(+), 23 deletions(-) create mode 100644 backend/migrations/add_group_id_to_participants.sql diff --git a/backend/controllers/participantController.js b/backend/controllers/participantController.js index fe5b83d..4787477 100644 --- a/backend/controllers/participantController.js +++ b/backend/controllers/participantController.js @@ -4,7 +4,10 @@ import { devLog } from '../utils/logger.js'; export const getParticipants = async (req, res) => { try { const { dateId } = req.params; - const participants = await Participant.findAll({ where: { diaryDateId: dateId } }); + const participants = await Participant.findAll({ + where: { diaryDateId: dateId }, + attributes: ['id', 'diaryDateId', 'memberId', 'groupId', 'notes', 'createdAt', 'updatedAt'] + }); res.status(200).json(participants); } catch (error) { devLog(error); @@ -12,6 +15,32 @@ export const getParticipants = async (req, res) => { } }; +export const updateParticipantGroup = async (req, res) => { + try { + const { dateId, memberId } = req.params; + const { groupId } = req.body; + + const participant = await Participant.findOne({ + where: { + diaryDateId: dateId, + memberId: memberId + } + }); + + if (!participant) { + return res.status(404).json({ error: 'Teilnehmer nicht gefunden' }); + } + + participant.groupId = groupId || null; + await participant.save(); + + res.status(200).json(participant); + } catch (error) { + devLog(error); + res.status(500).json({ error: 'Fehler beim Aktualisieren der Teilnehmer-Gruppenzuordnung' }); + } +}; + export const addParticipant = async (req, res) => { try { const { diaryDateId, memberId } = req.body; diff --git a/backend/migrations/add_group_id_to_participants.sql b/backend/migrations/add_group_id_to_participants.sql new file mode 100644 index 0000000..2e6fa4c --- /dev/null +++ b/backend/migrations/add_group_id_to_participants.sql @@ -0,0 +1,9 @@ +-- Migration: Add group_id to participants table +-- This allows assigning participants to groups for training organization + +ALTER TABLE participants +ADD COLUMN group_id INTEGER NULL REFERENCES "group"(id) ON DELETE SET NULL ON UPDATE CASCADE; + +-- Add index for better query performance +CREATE INDEX IF NOT EXISTS idx_participants_group_id ON participants(group_id); + diff --git a/backend/models/Participant.js b/backend/models/Participant.js index 5bbcb85..b5438b5 100644 --- a/backend/models/Participant.js +++ b/backend/models/Participant.js @@ -2,6 +2,7 @@ import { DataTypes } from 'sequelize'; import sequelize from '../database.js'; import Member from './Member.js'; import DiaryDate from './DiaryDates.js'; +import Group from './Group.js'; import { encryptData, decryptData } from '../utils/encrypt.js'; const Participant = sequelize.define('Participant', { @@ -27,6 +28,16 @@ const Participant = sequelize.define('Participant', { key: 'id' } }, + groupId: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: Group, + key: 'id' + }, + onDelete: 'SET NULL', + onUpdate: 'CASCADE' + }, notes: { type: DataTypes.STRING(4096), allowNull: true, diff --git a/backend/routes/participantRoutes.js b/backend/routes/participantRoutes.js index 96979b7..023a67f 100644 --- a/backend/routes/participantRoutes.js +++ b/backend/routes/participantRoutes.js @@ -1,5 +1,5 @@ import express from 'express'; -import { getParticipants, addParticipant, removeParticipant } from '../controllers/participantController.js'; +import { getParticipants, addParticipant, removeParticipant, updateParticipantGroup } from '../controllers/participantController.js'; import { authenticate } from '../middleware/authMiddleware.js'; const router = express.Router(); @@ -7,5 +7,6 @@ const router = express.Router(); router.get('/:dateId', authenticate, getParticipants); router.post('/add', authenticate, addParticipant); router.post('/remove', authenticate, removeParticipant); +router.put('/:dateId/:memberId/group', authenticate, updateParticipantGroup); export default router; diff --git a/backend/server.js b/backend/server.js index 1aadafe..03134a6 100644 --- a/backend/server.js +++ b/backend/server.js @@ -43,7 +43,7 @@ import permissionRoutes from './routes/permissionRoutes.js'; import schedulerService from './services/schedulerService.js'; const app = express(); -const port = process.env.PORT || 3000; +const port = process.env.PORT || 3005; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); diff --git a/frontend/src/apiClient.js b/frontend/src/apiClient.js index e284cf1..75d5f4e 100644 --- a/frontend/src/apiClient.js +++ b/frontend/src/apiClient.js @@ -2,7 +2,7 @@ import axios from 'axios'; import store from './store'; const apiClient = axios.create({ - baseURL: `${import.meta.env.VITE_BACKEND}/api`, + baseURL: `${import.meta.env.VITE_BACKEND || 'http://localhost:3005'}/api`, }); apiClient.interceptors.request.use(config => { diff --git a/frontend/src/views/DiaryView.vue b/frontend/src/views/DiaryView.vue index 7159064..ef0368d 100644 --- a/frontend/src/views/DiaryView.vue +++ b/frontend/src/views/DiaryView.vue @@ -848,6 +848,17 @@ export default { this.participants = response.data.map(participant => participant.memberId); // Map für memberId -> participantId speichern this.participantMapByMemberId = response.data.reduce((map, p) => { map[p.memberId] = p.id; return map; }, {}); + // Map für memberId -> groupId speichern und mit Reaktivität initialisieren + this.memberGroupsMap = {}; + response.data.forEach(p => { + if (p.groupId) { + if (this.$set) { + this.$set(this.memberGroupsMap, p.memberId, p.groupId); + } else { + this.memberGroupsMap[p.memberId] = p.groupId; + } + } + }); }, async loadActivities(dateId) { @@ -1391,12 +1402,19 @@ export default { }, async createGroups() { try { - // Erstelle die gewünschte Anzahl Gruppen mit Namen 1 bis X - for (let i = 1; i <= this.newGroupCount; i++) { + // Bestimme Startnummer basierend auf vorhandenen Gruppen + const existingNumbers = (this.groups || []) + .map(g => parseInt((g.name || '').trim(), 10)) + .filter(n => Number.isFinite(n)); + const startNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1; + + // Erstelle die gewünschte Anzahl Gruppen mit fortlaufender Nummerierung + for (let i = 0; i < this.newGroupCount; i++) { + const groupNumber = startNumber + i; const form = { clubid: this.currentClub, dateid: this.date.id, - name: i.toString(), + name: groupNumber.toString(), lead: '', // Leiter wird leer gelassen } await apiClient.post('/group', form); @@ -1933,14 +1951,25 @@ export default { async updateMemberGroup(memberId, groupId) { try { - // Hier würde normalerweise ein API-Call gemacht werden - // Für jetzt speichern wir es nur lokal - this.memberGroupsMap[memberId] = groupId || ''; + const selectedGroupId = groupId || ''; - // TODO: API-Call zum Speichern der Teilnehmer-Gruppenzuordnung - // await apiClient.put(`/participants/${this.date.id}/${memberId}/group`, { groupId }); + // Verwende Vue.set für Reaktivität (Vue 2) + if (this.$set) { + this.$set(this.memberGroupsMap, memberId, selectedGroupId); + } else { + // Vue 3 oder Fallback + this.memberGroupsMap = { + ...this.memberGroupsMap, + [memberId]: selectedGroupId + }; + } - console.log(`Teilnehmer ${memberId} wurde Gruppe ${groupId} zugewiesen`); + // API-Call zum Speichern der Teilnehmer-Gruppenzuordnung + await apiClient.put(`/participants/${this.date.id}/${memberId}/group`, { + groupId: selectedGroupId || null + }); + + console.log(`Teilnehmer ${memberId} wurde Gruppe ${selectedGroupId} zugewiesen`); } catch (error) { console.error('Fehler beim Aktualisieren der Teilnehmer-Gruppenzuordnung:', error); this.showInfo('Fehler', 'Fehler beim Aktualisieren der Teilnehmer-Gruppenzuordnung', '', 'error'); diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue index 5b380a4..641e053 100644 --- a/frontend/src/views/Login.vue +++ b/frontend/src/views/Login.vue @@ -98,7 +98,7 @@ export default { ...mapActions(['login']), async executeLogin() { try { - const response = await axios.post(`${import.meta.env.VITE_BACKEND}/api/auth/login`, { email: this.email, password: this.password }, { + const response = await axios.post(`${import.meta.env.VITE_BACKEND || 'http://localhost:3005'}/api/auth/login`, { email: this.email, password: this.password }, { timeout: 5000, }); await this.login({ token: response.data.token, username: this.email }); diff --git a/frontend/src/views/MembersView.vue b/frontend/src/views/MembersView.vue index 9366b82..b4e66c1 100644 --- a/frontend/src/views/MembersView.vue +++ b/frontend/src/views/MembersView.vue @@ -68,11 +68,41 @@ -
- +
+
+ +
+ +
+
+ + +
+ +
+ + +
+ + +
@@ -90,7 +120,7 @@ -