diff --git a/backend/controllers/groupController.js b/backend/controllers/groupController.js index 5ddb95a..c1bdfea 100644 --- a/backend/controllers/groupController.js +++ b/backend/controllers/groupController.js @@ -1,5 +1,7 @@ import HttpError from '../exceptions/HttpError.js'; import groupService from '../services/groupService.js'; +import { emitActivityChanged, emitGroupChanged } from '../services/socketService.js'; +import DiaryDate from '../models/DiaryDates.js'; import { devLog } from '../utils/logger.js'; const addGroup = async(req, res) => { @@ -7,6 +9,15 @@ const addGroup = async(req, res) => { const { authcode: userToken } = req.headers; const { clubid: clubId, dateid: dateId, name, lead } = req.body; const result = await groupService.addGroup(userToken, clubId, dateId, name, lead); + + // Emit Socket-Event für Gruppen-Änderungen + if (dateId) { + const diaryDate = await DiaryDate.findByPk(dateId); + if (diaryDate?.clubId) { + emitGroupChanged(diaryDate.clubId, dateId); + } + } + res.status(201).json(result); } catch (error) { console.error('[addGroup] - Error:', error); @@ -33,6 +44,15 @@ const changeGroup = async(req, res) => { const { groupId } = req.params; const { clubid: clubId, dateid: dateId, name, lead } = req.body; const result = await groupService.changeGroup(userToken, groupId, clubId, dateId, name, lead); + + // Emit Socket-Event für Gruppen-Änderungen + if (dateId) { + const diaryDate = await DiaryDate.findByPk(dateId); + if (diaryDate?.clubId) { + emitGroupChanged(diaryDate.clubId, dateId); + } + } + res.status(200).json(result); } catch (error) { console.error('[changeGroup] - Error:', error); @@ -40,4 +60,27 @@ const changeGroup = async(req, res) => { } } -export { addGroup, getGroups, changeGroup}; \ No newline at end of file +const deleteGroup = async(req, res) => { + try { + const { authcode: userToken } = req.headers; + const { groupId } = req.params; + const { clubid: clubId, dateid: dateId } = req.body; + const result = await groupService.deleteGroup(userToken, groupId, clubId, dateId); + + // Emit Socket-Events für Gruppen- und Aktivitäts-Änderungen (Gruppen werden in Aktivitäten verwendet) + if (dateId) { + const diaryDate = await DiaryDate.findByPk(dateId); + if (diaryDate?.clubId) { + emitGroupChanged(diaryDate.clubId, dateId); + emitActivityChanged(diaryDate.clubId, dateId); + } + } + + res.status(200).json(result); + } catch (error) { + console.error('[deleteGroup] - Error:', error); + res.status(error.statusCode || 500).json({ error: error.message }); + } +} + +export { addGroup, getGroups, changeGroup, deleteGroup}; \ No newline at end of file diff --git a/backend/controllers/tournamentController.js b/backend/controllers/tournamentController.js index dd197f1..dd5e36f 100644 --- a/backend/controllers/tournamentController.js +++ b/backend/controllers/tournamentController.js @@ -1,5 +1,6 @@ // controllers/tournamentController.js import tournamentService from "../services/tournamentService.js"; +import { emitTournamentChanged } from '../services/socketService.js'; // 1. Alle Turniere eines Vereins export const getTournaments = async (req, res) => { @@ -20,9 +21,13 @@ export const addTournament = async (req, res) => { const { clubId, tournamentName, date } = req.body; try { const tournament = await tournamentService.addTournament(token, clubId, tournamentName, date); + // Emit Socket-Event + if (clubId && tournament && tournament.id) { + emitTournamentChanged(clubId, tournament.id); + } res.status(201).json(tournament); } catch (error) { - console.error(error); + console.error('[addTournament] Error:', error); res.status(500).json({ error: error.message }); } }; @@ -32,11 +37,16 @@ export const addParticipant = async (req, res) => { const { authcode: token } = req.headers; const { clubId, tournamentId, participant: participantId } = req.body; try { + if (!participantId) { + return res.status(400).json({ error: 'Teilnehmer-ID ist erforderlich' }); + } await tournamentService.addParticipant(token, clubId, tournamentId, participantId); const participants = await tournamentService.getParticipants(token, clubId, tournamentId); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); res.status(200).json(participants); } catch (error) { - console.error(error); + console.error('[addParticipant] Error:', error); res.status(500).json({ error: error.message }); } }; @@ -60,6 +70,8 @@ export const setModus = async (req, res) => { const { clubId, tournamentId, type, numberOfGroups, advancingPerGroup } = req.body; try { await tournamentService.setModus(token, clubId, tournamentId, type, numberOfGroups, advancingPerGroup); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); res.sendStatus(204); } catch (error) { console.error(error); @@ -73,6 +85,8 @@ export const createGroups = async (req, res) => { const { clubId, tournamentId } = req.body; try { await tournamentService.createGroups(token, clubId, tournamentId); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); res.sendStatus(204); } catch (error) { console.error(error); @@ -86,6 +100,8 @@ export const fillGroups = async (req, res) => { const { clubId, tournamentId } = req.body; try { const updatedMembers = await tournamentService.fillGroups(token, clubId, tournamentId); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); res.status(200).json(updatedMembers); } catch (error) { console.error(error); @@ -138,6 +154,8 @@ export const addMatchResult = async (req, res) => { const { clubId, tournamentId, matchId, set, result } = req.body; try { await tournamentService.addMatchResult(token, clubId, tournamentId, matchId, set, result); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); res.status(200).json({ message: "Result added successfully" }); } catch (error) { console.error(error); @@ -151,6 +169,8 @@ export const finishMatch = async (req, res) => { const { clubId, tournamentId, matchId } = req.body; try { await tournamentService.finishMatch(token, clubId, tournamentId, matchId); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); res.status(200).json({ message: "Match finished successfully" }); } catch (error) { console.error(error); @@ -164,6 +184,8 @@ export const startKnockout = async (req, res) => { try { await tournamentService.startKnockout(token, clubId, tournamentId); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); res.status(200).json({ message: "K.o.-Runde erfolgreich gestartet" }); } catch (error) { const status = /Gruppenmodus|Zu wenige Qualifikanten/.test(error.message) ? 400 : 500; @@ -190,6 +212,8 @@ export const manualAssignGroups = async (req, res) => { numberOfGroups, // neu maxGroupSize // neu ); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); res.status(200).json(groupsWithParts); } catch (error) { console.error('Error in manualAssignGroups:', error); @@ -202,6 +226,8 @@ export const resetGroups = async (req, res) => { const { clubId, tournamentId } = req.body; try { await tournamentService.resetGroups(token, clubId, tournamentId); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); res.sendStatus(204); } catch (err) { console.error(err); @@ -214,6 +240,8 @@ export const resetMatches = async (req, res) => { const { clubId, tournamentId } = req.body; try { await tournamentService.resetMatches(token, clubId, tournamentId); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); res.sendStatus(204); } catch (err) { console.error(err); @@ -227,6 +255,8 @@ export const removeParticipant = async (req, res) => { try { await tournamentService.removeParticipant(token, clubId, tournamentId, participantId); const participants = await tournamentService.getParticipants(token, clubId, tournamentId); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); res.status(200).json(participants); } catch (err) { console.error(err); @@ -245,6 +275,8 @@ export const deleteMatchResult = async (req, res) => { matchId, set ); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); res.status(200).json({ message: 'Einzelsatz gelöscht' }); } catch (error) { console.error('Error in deleteMatchResult:', error); @@ -258,6 +290,8 @@ export const reopenMatch = async (req, res) => { const { clubId, tournamentId, matchId } = req.body; try { await tournamentService.reopenMatch(token, clubId, tournamentId, matchId); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); // Gib optional das aktualisierte Match zurück res.status(200).json({ message: "Match reopened" }); } catch (error) { @@ -271,6 +305,8 @@ export const deleteKnockoutMatches = async (req, res) => { const { clubId, tournamentId } = req.body; try { await tournamentService.resetKnockout(token, clubId, tournamentId); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); res.status(200).json({ message: "K.o.-Runde gelöscht" }); } catch (error) { console.error("Error in deleteKnockoutMatches:", error); diff --git a/backend/routes/groupRoutes.js b/backend/routes/groupRoutes.js index 5cc30d6..693a97f 100644 --- a/backend/routes/groupRoutes.js +++ b/backend/routes/groupRoutes.js @@ -1,6 +1,6 @@ import express from 'express'; import { authenticate } from '../middleware/authMiddleware.js'; -import { addGroup, getGroups, changeGroup } from '../controllers/groupController.js'; +import { addGroup, getGroups, changeGroup, deleteGroup } from '../controllers/groupController.js'; const router = express.Router(); @@ -9,5 +9,6 @@ router.use(authenticate); router.post('/', addGroup); router.get('/:clubId/:dateId', getGroups); router.put('/:groupId', changeGroup); +router.delete('/:groupId', deleteGroup); export default router; diff --git a/backend/services/groupService.js b/backend/services/groupService.js index 13f5a70..4f47e7b 100644 --- a/backend/services/groupService.js +++ b/backend/services/groupService.js @@ -58,6 +58,22 @@ class GroupService { await group.save(); return group; } + + async deleteGroup(userToken, groupId, clubId, dateId) { + await checkAccess(userToken, clubId); + await this.checkDiaryDateToClub(clubId, dateId); + const group = await Group.findOne({ + where: { + id: groupId, + diaryDateId: dateId + } + }); + if (!group) { + throw new HttpError('Gruppe nicht gefunden oder passt nicht zum angegebenen Datum und Verein', 404); + } + await group.destroy(); + return { success: true }; + } } export default new GroupService(); \ No newline at end of file diff --git a/backend/services/socketService.js b/backend/services/socketService.js index 0defb3b..e49cd9c 100644 --- a/backend/services/socketService.js +++ b/backend/services/socketService.js @@ -110,3 +110,13 @@ export const emitMemberChanged = (clubId) => { emitToClub(clubId, 'member:changed', { clubId }); }; +// Event für Gruppen-Änderungen (erstellen, aktualisieren, löschen) +export const emitGroupChanged = (clubId, dateId) => { + emitToClub(clubId, 'group:changed', { dateId }); +}; + +// Event für Tournament-Änderungen (alle Aktionen) +export const emitTournamentChanged = (clubId, tournamentId) => { + emitToClub(clubId, 'tournament:changed', { tournamentId }); +}; + diff --git a/frontend/src/services/socketService.js b/frontend/src/services/socketService.js index f6fea2a..5c7b342 100644 --- a/frontend/src/services/socketService.js +++ b/frontend/src/services/socketService.js @@ -166,6 +166,28 @@ export const onMemberChanged = (callback) => { } }; +export const onGroupChanged = (callback) => { + if (socket) { + socket.on('group:changed', (data) => { + console.log('📡 [Socket] group:changed empfangen:', data); + callback(data); + }); + } else { + console.warn('⚠️ [Socket] onGroupChanged: Socket nicht verbunden'); + } +}; + +export const onTournamentChanged = (callback) => { + if (socket) { + socket.on('tournament:changed', (data) => { + console.log('📡 [Socket] tournament:changed empfangen:', data); + callback(data); + }); + } else { + console.warn('⚠️ [Socket] onTournamentChanged: Socket nicht verbunden'); + } +}; + // Event-Listener entfernen export const offParticipantAdded = (callback) => { if (socket) { @@ -245,3 +267,15 @@ export const offMemberChanged = (callback) => { } }; +export const offGroupChanged = (callback) => { + if (socket) { + socket.off('group:changed', callback); + } +}; + +export const offTournamentChanged = (callback) => { + if (socket) { + socket.off('tournament:changed', callback); + } +}; + diff --git a/frontend/src/views/DiaryView.vue b/frontend/src/views/DiaryView.vue index d96c54b..641ad9b 100644 --- a/frontend/src/views/DiaryView.vue +++ b/frontend/src/views/DiaryView.vue @@ -96,6 +96,10 @@ + @@ -104,11 +108,11 @@
- +
- +
@@ -689,6 +693,7 @@ import { onActivityMemberRemoved, onActivityChanged, onMemberChanged, + onGroupChanged, offParticipantAdded, offParticipantRemoved, offParticipantUpdated, @@ -700,7 +705,8 @@ import { offActivityMemberAdded, offActivityMemberRemoved, offActivityChanged, - offMemberChanged + offMemberChanged, + offGroupChanged } from '../services/socketService.js'; export default { @@ -1215,6 +1221,8 @@ export default { try { const response = await apiClient.get(`/group/${this.currentClub}/${this.date.id}`); this.groups = response.data; + // Setze newGroupCount basierend auf vorhandenen Gruppen + this.newGroupCount = this.groups.length > 0 ? 1 : 2; } catch (error) { // ignore } @@ -1807,6 +1815,13 @@ export default { }, async createGroups() { try { + // Validierung: Wenn keine Gruppen existieren, müssen mindestens 2 erstellt werden + if (this.groups.length === 0 && this.newGroupCount < 2) { + this.showInfo('Fehler', 'Beim ersten Erstellen müssen mindestens 2 Gruppen erstellt werden!', '', 'error'); + this.newGroupCount = 2; + return; + } + // Bestimme Startnummer basierend auf vorhandenen Gruppen const existingNumbers = (this.groups || []) .map(g => parseInt((g.name || '').trim(), 10)) @@ -1824,8 +1839,14 @@ export default { } await apiClient.post('/group', form); } + const countCreated = this.newGroupCount; await this.loadGroups(); - this.showInfo('Erfolg', `${this.newGroupCount} Gruppen wurden erfolgreich erstellt!`, '', 'success'); + // Setze newGroupCount zurück: 1 wenn bereits Gruppen existieren, 2 wenn keine + this.newGroupCount = this.groups.length > 0 ? 1 : 2; + const message = countCreated === 1 + ? '1 Gruppe wurde erfolgreich hinzugefügt!' + : `${countCreated} Gruppen wurden erfolgreich erstellt!`; + this.showInfo('Erfolg', message, '', 'success'); } catch (error) { console.error('Fehler beim Erstellen der Gruppen:', error); this.showInfo('Fehler', 'Fehler beim Erstellen der Gruppen', '', 'error'); @@ -1887,6 +1908,32 @@ export default { cancelEditGroup() { this.editingGroupId = null; }, + async deleteGroup(groupId) { + try { + const group = this.groups.find(g => g.id === groupId); + if (!group) { + return; + } + + const confirmed = confirm(`Möchten Sie die Gruppe "${group.name}" wirklich löschen?`); + if (!confirmed) { + return; + } + + await apiClient.delete(`/group/${groupId}`, { + data: { + clubid: this.currentClub, + dateid: this.date.id + } + }); + + await this.loadGroups(); + this.showInfo('Erfolg', 'Gruppe wurde erfolgreich gelöscht!', '', 'success'); + } catch (error) { + console.error('Fehler beim Löschen der Gruppe:', error); + this.showInfo('Fehler', 'Fehler beim Löschen der Gruppe', '', 'error'); + } + }, async openTagInfos(member) { if (!member) { return; @@ -2610,6 +2657,9 @@ export default { // Event-Handler für Member-Änderungen onMemberChanged(this.handleMemberChanged); + + // Event-Handler für Gruppen-Änderungen + onGroupChanged(this.handleGroupChanged); console.log('✅ [DiaryView] Alle Event-Handler registriert'); }, @@ -2626,6 +2676,7 @@ export default { offActivityMemberRemoved(this.handleActivityMemberRemoved); offActivityChanged(this.handleActivityChanged); offMemberChanged(this.handleMemberChanged); + offGroupChanged(this.handleGroupChanged); }, async handleParticipantAdded(data) { @@ -2831,6 +2882,24 @@ export default { console.log('⚠️ [DiaryView] Club stimmt nicht überein - Event Club:', data.clubId, 'Aktueller Club:', this.currentClub); } }, + + async handleGroupChanged(data) { + console.log('📡 [DiaryView] handleGroupChanged aufgerufen:', data); + // Nur aktualisieren, wenn das aktuelle Datum betroffen ist + if (this.date && this.date !== 'new' && String(this.date.id) === String(data.dateId)) { + console.log('✅ [DiaryView] Datum stimmt überein, lade Gruppenliste neu'); + try { + await this.loadGroups(); + console.log('✅ [DiaryView] Gruppenliste neu geladen'); + // Force Vue update + this.$forceUpdate(); + } catch (error) { + console.error('❌ [DiaryView] Fehler beim Neuladen der Gruppenliste:', error); + } + } else { + console.log('⚠️ [DiaryView] Datum stimmt nicht überein - Event dateId:', data.dateId, 'Aktuelles Datum:', this.date?.id); + } + }, }, async mounted() { await this.init(); diff --git a/frontend/src/views/TournamentsView.vue b/frontend/src/views/TournamentsView.vue index 3979af6..254691a 100644 --- a/frontend/src/views/TournamentsView.vue +++ b/frontend/src/views/TournamentsView.vue @@ -30,7 +30,7 @@