From 3334d7668845ab191036d981ecfff3f822317230 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Fri, 14 Nov 2025 14:36:21 +0100 Subject: [PATCH] Enhance tournament management with new features and UI improvements This commit introduces several enhancements to the tournament management system, including the addition of winning sets to tournament creation and updates. The `updateTournament` and `addTournament` methods in the backend now accept winning sets as a parameter, ensuring proper validation and handling. New functionality for updating participant seeded status and setting match activity is also implemented, along with corresponding routes and controller methods. The frontend is updated to reflect these changes, featuring new input fields for winning sets and improved participant management UI, enhancing overall user experience and interactivity. --- backend/controllers/participantController.js | 1 - backend/controllers/tournamentController.js | 38 +- .../add_is_active_to_tournament_match.sql | 22 ++ .../add_seeded_to_tournament_member.sql | 24 ++ .../add_winning_sets_to_tournament.sql | 22 ++ backend/models/Tournament.js | 7 +- backend/models/TournamentMatch.js | 5 + backend/models/TournamentMember.js | 5 + backend/routes/tournamentRoutes.js | 4 + backend/services/socketService.js | 10 +- backend/services/tournamentService.js | 203 +++++++++- frontend/src/services/socketService.js | 29 +- frontend/src/views/DiaryView.vue | 76 +--- frontend/src/views/TournamentsView.vue | 371 ++++++++++++++---- 14 files changed, 609 insertions(+), 208 deletions(-) create mode 100644 backend/migrations/add_is_active_to_tournament_match.sql create mode 100644 backend/migrations/add_seeded_to_tournament_member.sql create mode 100644 backend/migrations/add_winning_sets_to_tournament.sql diff --git a/backend/controllers/participantController.js b/backend/controllers/participantController.js index c06d1e9..7eb3689 100644 --- a/backend/controllers/participantController.js +++ b/backend/controllers/participantController.js @@ -55,7 +55,6 @@ export const updateParticipantGroup = async (req, res) => { // Emit Socket-Event mit dem aktualisierten Participant if (updatedParticipant?.diaryDate?.clubId) { - console.log('📡 [Backend] Emit participant:updated mit groupId:', updatedParticipant.groupId); emitParticipantUpdated(updatedParticipant.diaryDate.clubId, dateId, updatedParticipant); } diff --git a/backend/controllers/tournamentController.js b/backend/controllers/tournamentController.js index b216376..4f1cfe1 100644 --- a/backend/controllers/tournamentController.js +++ b/backend/controllers/tournamentController.js @@ -18,9 +18,9 @@ export const getTournaments = async (req, res) => { // 2. Neues Turnier anlegen export const addTournament = async (req, res) => { const { authcode: token } = req.headers; - const { clubId, tournamentName, date } = req.body; + const { clubId, tournamentName, date, winningSets } = req.body; try { - const tournament = await tournamentService.addTournament(token, clubId, tournamentName, date); + const tournament = await tournamentService.addTournament(token, clubId, tournamentName, date, winningSets); // Emit Socket-Event if (clubId && tournament && tournament.id) { emitTournamentChanged(clubId, tournament.id); @@ -139,9 +139,9 @@ export const getTournament = async (req, res) => { export const updateTournament = async (req, res) => { const { authcode: token } = req.headers; const { clubId, tournamentId } = req.params; - const { name, date } = req.body; + const { name, date, winningSets } = req.body; try { - const tournament = await tournamentService.updateTournament(token, clubId, tournamentId, name, date); + const tournament = await tournamentService.updateTournament(token, clubId, tournamentId, name, date, winningSets); // Emit Socket-Event emitTournamentChanged(clubId, tournamentId); res.status(200).json(tournament); @@ -281,6 +281,21 @@ export const removeParticipant = async (req, res) => { } }; +export const updateParticipantSeeded = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentId, participantId } = req.params; + const { seeded } = req.body; + try { + await tournamentService.updateParticipantSeeded(token, clubId, tournamentId, participantId, seeded); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); + res.status(200).json({ message: 'Gesetzt-Status aktualisiert' }); + } catch (err) { + console.error('[updateParticipantSeeded] Error:', err); + res.status(500).json({ error: err.message }); + } +}; + export const deleteMatchResult = async (req, res) => { const { authcode: token } = req.headers; const { clubId, tournamentId, matchId, set } = req.body; @@ -330,4 +345,19 @@ export const deleteKnockoutMatches = async (req, res) => { res.status(500).json({ error: error.message }); } }; + +export const setMatchActive = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentId, matchId } = req.params; + const { isActive } = req.body; + try { + await tournamentService.setMatchActive(token, clubId, tournamentId, matchId, isActive); + // Emit Socket-Event + emitTournamentChanged(clubId, tournamentId); + res.status(200).json({ message: 'Match-Status aktualisiert' }); + } catch (err) { + console.error('[setMatchActive] Error:', err); + res.status(500).json({ error: err.message }); + } +}; \ No newline at end of file diff --git a/backend/migrations/add_is_active_to_tournament_match.sql b/backend/migrations/add_is_active_to_tournament_match.sql new file mode 100644 index 0000000..10bbad8 --- /dev/null +++ b/backend/migrations/add_is_active_to_tournament_match.sql @@ -0,0 +1,22 @@ +-- Migration: Add 'is_active' column to tournament_match table +-- Date: 2025-01-14 +-- For MariaDB/MySQL + +SET @dbname = DATABASE(); +SET @tablename = 'tournament_match'; +SET @columnname = 'is_active'; +SET @preparedStatement = (SELECT IF( + ( + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS + WHERE + (TABLE_SCHEMA = @dbname) + AND (TABLE_NAME = @tablename) + AND (COLUMN_NAME = @columnname) + ) > 0, + 'SELECT 1', + CONCAT('ALTER TABLE `', @tablename, '` ADD COLUMN `', @columnname, '` TINYINT(1) NOT NULL DEFAULT 0 AFTER `is_finished`') +)); +PREPARE alterIfNotExists FROM @preparedStatement; +EXECUTE alterIfNotExists; +DEALLOCATE PREPARE alterIfNotExists; + diff --git a/backend/migrations/add_seeded_to_tournament_member.sql b/backend/migrations/add_seeded_to_tournament_member.sql new file mode 100644 index 0000000..21c39c3 --- /dev/null +++ b/backend/migrations/add_seeded_to_tournament_member.sql @@ -0,0 +1,24 @@ +-- Migration: Add seeded column to tournament_member table +-- Date: 2025-01-13 +-- For MariaDB/MySQL + +-- Add seeded column if it doesn't exist +-- Check if column exists and add it if not +SET @dbname = DATABASE(); +SET @tablename = 'tournament_member'; +SET @columnname = 'seeded'; +SET @preparedStatement = (SELECT IF( + ( + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS + WHERE + (TABLE_SCHEMA = @dbname) + AND (TABLE_NAME = @tablename) + AND (COLUMN_NAME = @columnname) + ) > 0, + 'SELECT 1', + CONCAT('ALTER TABLE `', @tablename, '` ADD COLUMN `', @columnname, '` TINYINT(1) NOT NULL DEFAULT 0 AFTER `club_member_id`') +)); +PREPARE alterIfNotExists FROM @preparedStatement; +EXECUTE alterIfNotExists; +DEALLOCATE PREPARE alterIfNotExists; + diff --git a/backend/migrations/add_winning_sets_to_tournament.sql b/backend/migrations/add_winning_sets_to_tournament.sql new file mode 100644 index 0000000..60106d7 --- /dev/null +++ b/backend/migrations/add_winning_sets_to_tournament.sql @@ -0,0 +1,22 @@ +-- Migration: Add 'winning_sets' column to tournament table +-- Date: 2025-01-14 +-- For MariaDB/MySQL + +SET @dbname = DATABASE(); +SET @tablename = 'tournament'; +SET @columnname = 'winning_sets'; +SET @preparedStatement = (SELECT IF( + ( + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS + WHERE + (TABLE_SCHEMA = @dbname) + AND (TABLE_NAME = @tablename) + AND (COLUMN_NAME = @columnname) + ) > 0, + 'SELECT 1', + CONCAT('ALTER TABLE `', @tablename, '` ADD COLUMN `', @columnname, '` INT NOT NULL DEFAULT 3 AFTER `advancing_per_group`') +)); +PREPARE alterIfNotExists FROM @preparedStatement; +EXECUTE alterIfNotExists; +DEALLOCATE PREPARE alterIfNotExists; + diff --git a/backend/models/Tournament.js b/backend/models/Tournament.js index 277c468..4b18e07 100644 --- a/backend/models/Tournament.js +++ b/backend/models/Tournament.js @@ -17,6 +17,7 @@ const Tournament = sequelize.define('Tournament', { advancingPerGroup: { type: DataTypes.INTEGER, allowNull: false, + defaultValue: 1, }, numberOfGroups: { type: DataTypes.INTEGER, @@ -28,7 +29,11 @@ const Tournament = sequelize.define('Tournament', { allowNull: false, defaultValue: 1 }, - advancingPerGroup: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 1 }, + winningSets: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 3, + }, }, { underscored: true, tableName: 'tournament', diff --git a/backend/models/TournamentMatch.js b/backend/models/TournamentMatch.js index 4b69075..05b1a31 100644 --- a/backend/models/TournamentMatch.js +++ b/backend/models/TournamentMatch.js @@ -46,6 +46,11 @@ const TournamentMatch = sequelize.define('TournamentMatch', { allowNull: false, defaultValue: false, }, + isActive: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, result: { type: DataTypes.STRING, allowNull: true, diff --git a/backend/models/TournamentMember.js b/backend/models/TournamentMember.js index 528a53d..827f8af 100644 --- a/backend/models/TournamentMember.js +++ b/backend/models/TournamentMember.js @@ -16,6 +16,11 @@ const TournamentMember = sequelize.define('TournamentMember', { type: DataTypes.INTEGER, autoIncrement: false, allowNull: false + }, + seeded: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false } }, { underscored: true, diff --git a/backend/routes/tournamentRoutes.js b/backend/routes/tournamentRoutes.js index 4fe493f..861aae6 100644 --- a/backend/routes/tournamentRoutes.js +++ b/backend/routes/tournamentRoutes.js @@ -18,9 +18,11 @@ import { resetGroups, resetMatches, removeParticipant, + updateParticipantSeeded, deleteMatchResult, reopenMatch, deleteKnockoutMatches, + setMatchActive, } from '../controllers/tournamentController.js'; import { authenticate } from '../middleware/authMiddleware.js'; @@ -29,6 +31,7 @@ const router = express.Router(); router.post('/participant', authenticate, addParticipant); router.post('/participants', authenticate, getParticipants); router.delete('/participant', authenticate, removeParticipant); +router.put('/participant/:clubId/:tournamentId/:participantId/seeded', authenticate, updateParticipantSeeded); router.post('/modus', authenticate, setModus); router.post('/groups/reset', authenticate, resetGroups); router.post('/matches/reset', authenticate, resetMatches); @@ -39,6 +42,7 @@ router.post('/match/result', authenticate, addMatchResult); router.delete('/match/result', authenticate, deleteMatchResult); router.post("/match/reopen", reopenMatch); router.post('/match/finish', authenticate, finishMatch); +router.put('/match/:clubId/:tournamentId/:matchId/active', authenticate, setMatchActive); router.get('/matches/:clubId/:tournamentId', authenticate, getTournamentMatches); router.put('/:clubId/:tournamentId', authenticate, updateTournament); router.get('/:clubId/:tournamentId', authenticate, getTournament); diff --git a/backend/services/socketService.js b/backend/services/socketService.js index e49cd9c..de9f33b 100644 --- a/backend/services/socketService.js +++ b/backend/services/socketService.js @@ -12,24 +12,20 @@ export const initializeSocketIO = (httpServer) => { }); io.on('connection', (socket) => { - console.log('🔌 Socket verbunden:', socket.id); - // Client tritt einem Club-Raum bei socket.on('join-club', (clubId) => { const room = `club-${clubId}`; socket.join(room); - console.log(`👤 Socket ${socket.id} ist Club ${clubId} beigetreten`); }); // Client verlässt einen Club-Raum socket.on('leave-club', (clubId) => { const room = `club-${clubId}`; socket.leave(room); - console.log(`👤 Socket ${socket.id} hat Club ${clubId} verlassen`); }); socket.on('disconnect', () => { - console.log('🔌 Socket getrennt:', socket.id); + // Socket getrennt }); }); @@ -46,11 +42,10 @@ export const getIO = () => { // Helper-Funktionen zum Emittieren von Events export const emitToClub = (clubId, event, data) => { if (!io) { - console.warn('⚠️ [Socket] emitToClub: io nicht initialisiert'); + console.warn('emitToClub: io nicht initialisiert'); return; } const room = `club-${clubId}`; - console.log(`📡 [Socket] Emit ${event} an Raum ${room}:`, data); io.to(room).emit(event, data); }; @@ -64,7 +59,6 @@ export const emitParticipantRemoved = (clubId, dateId, participantId) => { }; export const emitParticipantUpdated = (clubId, dateId, participant) => { - console.log('📡 [Socket] Emit participant:updated für Club', clubId, 'Date', dateId, 'Participant:', participant); emitToClub(clubId, 'participant:updated', { dateId, participant }); }; diff --git a/backend/services/tournamentService.js b/backend/services/tournamentService.js index 793fb0f..e5f1a0e 100644 --- a/backend/services/tournamentService.js +++ b/backend/services/tournamentService.js @@ -41,7 +41,7 @@ class TournamentService { } // 2. Neues Turnier anlegen (prüft Duplikat) - async addTournament(userToken, clubId, tournamentName, date) { + async addTournament(userToken, clubId, tournamentName, date, winningSets) { await checkAccess(userToken, clubId); const existing = await Tournament.findOne({ where: { clubId, date } }); if (existing) { @@ -52,7 +52,8 @@ class TournamentService { date, clubId: +clubId, bestOfEndroundSize: 0, - type: '' + type: '', + winningSets: winningSets || 3 // Default: 3 Sätze }); return JSON.parse(JSON.stringify(t)); } @@ -183,21 +184,108 @@ class TournamentService { { where: { tournamentId } } ); - // 4) Zufällig verteilen - const shuffled = members.slice(); - for (let i = shuffled.length - 1; i > 0; i--) { + // 4) Gleichmäßige Verteilung: Zuerst alle Spieler gleichmäßig verteilen, + // dann gesetzte Spieler gleichmäßig umverteilen, um sie gleichmäßig zu verteilen + const seededMembers = members.filter(m => m.seeded); + const unseededMembers = members.filter(m => !m.seeded); + + // Alle Spieler zufällig mischen + const allMembers = members.slice(); + for (let i = allMembers.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); - [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; + [allMembers[i], allMembers[j]] = [allMembers[j], allMembers[i]]; } - // Warte auf alle Updates, damit die Zuordnungen korrekt sind + // Berechne die gewünschte Anzahl pro Gruppe + const totalMembers = allMembers.length; + const numGroups = groups.length; + const baseSize = Math.floor(totalMembers / numGroups); + const remainder = totalMembers % numGroups; + + // Verteile alle Spieler gleichmäßig (round-robin) + const groupAssignments = groups.map(() => []); + allMembers.forEach((m, idx) => { + const targetGroup = idx % numGroups; + groupAssignments[targetGroup].push(m); + }); + + // Jetzt optimiere die Verteilung: Stelle sicher, dass die Gruppen gleichmäßig groß sind + // und die gesetzten Spieler gleichmäßig verteilt sind + + // Zuerst: Stelle sicher, dass die Gruppen gleichmäßig groß sind + // Sortiere Gruppen nach Größe (größte zuerst) + const groupSizes = groupAssignments.map((group, idx) => ({ idx, size: group.length })); + groupSizes.sort((a, b) => b.size - a.size); + + // Verschiebe Spieler von größeren zu kleineren Gruppen + for (let i = 0; i < numGroups - 1; i++) { + const largeGroupIdx = groupSizes[i].idx; + const smallGroupIdx = groupSizes[numGroups - 1].idx; + + while (groupAssignments[largeGroupIdx].length > groupAssignments[smallGroupIdx].length + 1) { + // Verschiebe einen Spieler von der größeren zur kleineren Gruppe + const memberToMove = groupAssignments[largeGroupIdx].pop(); + groupAssignments[smallGroupIdx].push(memberToMove); + } + } + + // Jetzt optimiere die Verteilung der gesetzten Spieler: + // Zähle gesetzte Spieler pro Gruppe + const seededCounts = groupAssignments.map(group => + group.filter(m => m.seeded).length + ); + + // Verschiebe gesetzte Spieler, um eine gleichmäßigere Verteilung zu erreichen + for (let round = 0; round < 10; round++) { // Maximal 10 Runden für Optimierung + let changed = false; + + // Finde Gruppe mit den meisten gesetzten Spielern + let maxSeededIdx = 0; + let maxSeededCount = seededCounts[0]; + for (let i = 1; i < numGroups; i++) { + if (seededCounts[i] > maxSeededCount) { + maxSeededCount = seededCounts[i]; + maxSeededIdx = i; + } + } + + // Finde Gruppe mit den wenigsten gesetzten Spielern + let minSeededIdx = 0; + let minSeededCount = seededCounts[0]; + for (let i = 1; i < numGroups; i++) { + if (seededCounts[i] < minSeededCount) { + minSeededCount = seededCounts[i]; + minSeededIdx = i; + } + } + + // Wenn die Differenz größer als 1 ist, verschiebe einen gesetzten Spieler + if (maxSeededCount - minSeededCount > 1) { + // Finde einen gesetzten Spieler in der Gruppe mit den meisten + const seededInMax = groupAssignments[maxSeededIdx].filter(m => m.seeded); + if (seededInMax.length > 0) { + const memberToMove = seededInMax[0]; + // Entferne aus max-Gruppe + groupAssignments[maxSeededIdx] = groupAssignments[maxSeededIdx].filter(m => m.id !== memberToMove.id); + seededCounts[maxSeededIdx]--; + // Füge zu min-Gruppe hinzu + groupAssignments[minSeededIdx].push(memberToMove); + seededCounts[minSeededIdx]++; + changed = true; + } + } + + if (!changed) break; + } + + // Warte auf alle Updates const updatePromises = []; - groups.forEach((g, idx) => { - const groupMembers = shuffled.filter((_, i) => i % groups.length === idx); - groupMembers.forEach(m => { - updatePromises.push(m.update({ groupId: g.id })); + groupAssignments.forEach((groupMembers, groupIdx) => { + groupMembers.forEach(member => { + updatePromises.push(member.update({ groupId: groups[groupIdx].id })); }); }); + await Promise.all(updatePromises); // 5) Round‑Robin anlegen wie gehabt - NUR innerhalb jeder Gruppe @@ -270,7 +358,8 @@ class TournamentService { groupNumber: idx + 1, // jetzt definiert participants: g.tournamentGroupMembers.map(m => ({ id: m.id, - name: `${m.member.firstName} ${m.member.lastName}` + name: `${m.member.firstName} ${m.member.lastName}`, + seeded: m.seeded || false })) })); } @@ -281,11 +370,17 @@ class TournamentService { await checkAccess(userToken, clubId); const t = await Tournament.findOne({ where: { id: tournamentId, clubId } }); if (!t) throw new Error('Turnier nicht gefunden'); - return t; + // Stelle sicher, dass winningSets vorhanden ist (für alte Turniere) + // Nur setzen, wenn es wirklich null/undefined ist (nicht 0 oder andere Werte) + if (t.winningSets == null) { + t.winningSets = 3; + await t.save(); + } + return JSON.parse(JSON.stringify(t)); } - // Update Turnier (Name und Datum) - async updateTournament(userToken, clubId, tournamentId, name, date) { + // Update Turnier (Name, Datum und Gewinnsätze) + async updateTournament(userToken, clubId, tournamentId, name, date, winningSets) { await checkAccess(userToken, clubId); const tournament = await Tournament.findOne({ where: { id: tournamentId, clubId } }); if (!tournament) { @@ -302,6 +397,12 @@ class TournamentService { if (name !== undefined) tournament.name = name; if (date !== undefined) tournament.date = date; + if (winningSets !== undefined) { + if (winningSets < 1) { + throw new Error('Anzahl der Gewinnsätze muss mindestens 1 sein'); + } + tournament.winningSets = winningSets; + } await tournament.save(); return JSON.parse(JSON.stringify(tournament)); @@ -331,8 +432,16 @@ class TournamentService { // 12. Satz-Ergebnis hinzufügen/überschreiben async addMatchResult(userToken, clubId, tournamentId, matchId, set, result) { await checkAccess(userToken, clubId); - const [match] = await TournamentMatch.findAll({ where: { id: matchId, tournamentId } }); - if (!match) throw new Error('Match nicht gefunden'); + + // Lade Turnier, um winningSets zu erhalten + const tournament = await Tournament.findByPk(tournamentId); + if (!tournament) throw new Error('Turnier nicht gefunden'); + const winningSets = tournament.winningSets || 3; // Default: 3 Sätze + + // Lade Match, um isFinished zu prüfen + const match = await TournamentMatch.findByPk(matchId); + if (!match || match.tournamentId != tournamentId) throw new Error('Match nicht gefunden'); + const existing = await TournamentResult.findOne({ where: { matchId, set } }); if (existing) { existing.pointsPlayer1 = +result.split(':')[0]; @@ -342,6 +451,32 @@ class TournamentService { const [p1, p2] = result.split(':').map(Number); await TournamentResult.create({ matchId, set, pointsPlayer1: p1, pointsPlayer2: p2 }); } + + // Prüfe, ob ein Spieler die Gewinnsätze erreicht hat + // Lade Match neu, um sicherzustellen, dass wir den aktuellen Status haben + await match.reload(); + if (!match.isFinished) { + const allResults = await TournamentResult.findAll({ + where: { matchId }, + order: [['set', 'ASC']] + }); + + let setsWonPlayer1 = 0; + let setsWonPlayer2 = 0; + + for (const r of allResults) { + if (r.pointsPlayer1 > r.pointsPlayer2) { + setsWonPlayer1++; + } else if (r.pointsPlayer2 > r.pointsPlayer1) { + setsWonPlayer2++; + } + } + + // Wenn ein Spieler die Gewinnsätze erreicht hat, schließe das Match automatisch ab + if (setsWonPlayer1 >= winningSets || setsWonPlayer2 >= winningSets) { + await this.finishMatch(userToken, clubId, tournamentId, matchId); + } + } } async finishMatch(userToken, clubId, tournamentId, matchId) { @@ -572,6 +707,18 @@ class TournamentService { }); } + async updateParticipantSeeded(userToken, clubId, tournamentId, participantId, seeded) { + await checkAccess(userToken, clubId); + const participant = await TournamentMember.findOne({ + where: { id: participantId, tournamentId } + }); + if (!participant) { + throw new Error('Teilnehmer nicht gefunden'); + } + participant.seeded = seeded; + await participant.save(); + } + // services/tournamentService.js async deleteMatchResult(userToken, clubId, tournamentId, matchId, setToDelete) { await checkAccess(userToken, clubId); @@ -614,6 +761,28 @@ class TournamentService { await match.save(); } + async setMatchActive(userToken, clubId, tournamentId, matchId, isActive) { + await checkAccess(userToken, clubId); + + const match = await TournamentMatch.findOne({ + where: { id: matchId, tournamentId } + }); + if (!match) { + throw new Error("Match nicht gefunden"); + } + + // Wenn ein Match als aktiv gesetzt wird, setze alle anderen Matches des Turniers auf inaktiv + if (isActive) { + await TournamentMatch.update( + { isActive: false }, + { where: { tournamentId, id: { [Op.ne]: matchId } } } + ); + } + + match.isActive = isActive; + await match.save(); + } + async resetKnockout(userToken, clubId, tournamentId) { await checkAccess(userToken, clubId); // lösche alle Matches außer Gruppenphase diff --git a/frontend/src/services/socketService.js b/frontend/src/services/socketService.js index 5c7b342..cc77871 100644 --- a/frontend/src/services/socketService.js +++ b/frontend/src/services/socketService.js @@ -20,20 +20,18 @@ export const connectSocket = (clubId) => { }); socket.on('connect', () => { - console.log('🔌 Socket.IO verbunden:', socket.id); // Wenn bereits ein Club ausgewählt war, trete dem Raum bei if (socket.currentClubId) { socket.emit('join-club', socket.currentClubId); - console.log(`👤 Club ${socket.currentClubId} beigetreten (nach Reconnect)`); } }); socket.on('disconnect', () => { - console.log('🔌 Socket.IO getrennt'); + // Socket getrennt }); socket.on('connect_error', (error) => { - console.error('❌ Socket.IO Verbindungsfehler:', error); + console.error('Socket.IO Verbindungsfehler:', error); }); } @@ -41,7 +39,6 @@ export const connectSocket = (clubId) => { if (clubId) { socket.emit('join-club', clubId); socket.currentClubId = clubId; - console.log(`👤 Club ${clubId} beigetreten`); } return socket; @@ -54,7 +51,6 @@ export const disconnectSocket = () => { } socket.disconnect(); socket = null; - console.log('🔌 Socket.IO getrennt'); } }; @@ -78,11 +74,8 @@ export const onParticipantRemoved = (callback) => { export const onParticipantUpdated = (callback) => { if (socket) { socket.on('participant:updated', (data) => { - console.log('📡 [Socket] participant:updated empfangen:', data); callback(data); }); - } else { - console.warn('⚠️ [Socket] onParticipantUpdated: Socket nicht verbunden'); } }; @@ -125,66 +118,48 @@ export const onDiaryDateUpdated = (callback) => { export const onActivityMemberAdded = (callback) => { if (socket) { socket.on('activity:member:added', (data) => { - console.log('📡 [Socket] activity:member:added empfangen:', data); callback(data); }); - } else { - console.warn('⚠️ [Socket] onActivityMemberAdded: Socket nicht verbunden'); } }; export const onActivityMemberRemoved = (callback) => { if (socket) { socket.on('activity:member:removed', (data) => { - console.log('📡 [Socket] activity:member:removed empfangen:', data); callback(data); }); - } else { - console.warn('⚠️ [Socket] onActivityMemberRemoved: Socket nicht verbunden'); } }; export const onActivityChanged = (callback) => { if (socket) { socket.on('activity:changed', (data) => { - console.log('📡 [Socket] activity:changed empfangen:', data); callback(data); }); - } else { - console.warn('⚠️ [Socket] onActivityChanged: Socket nicht verbunden'); } }; export const onMemberChanged = (callback) => { if (socket) { socket.on('member:changed', (data) => { - console.log('📡 [Socket] member:changed empfangen:', data); callback(data); }); - } else { - console.warn('⚠️ [Socket] onMemberChanged: Socket nicht verbunden'); } }; 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'); } }; diff --git a/frontend/src/views/DiaryView.vue b/frontend/src/views/DiaryView.vue index 641ad9b..09ebddd 100644 --- a/frontend/src/views/DiaryView.vue +++ b/frontend/src/views/DiaryView.vue @@ -1175,7 +1175,6 @@ export default { const response = await apiClient.get(`/clubmembers/get/${this.currentClub}/false`); // Erstelle ein neues Array, um Vue-Reaktivität sicherzustellen this.members = Array.isArray(response.data) ? [...response.data] : []; - console.log('📡 [DiaryView] loadMembers: Mitglieder geladen, Anzahl:', this.members?.length || 0); }, async loadParticipants(dateId) { @@ -2251,16 +2250,11 @@ export default { }, async toggleActivityMembers(item) { - console.log('🔍 [toggleActivityMembers] Aufgerufen für item.id:', item.id); - console.log('🔍 [toggleActivityMembers] Aktuelle activityMembersOpenId:', this.activityMembersOpenId); - if (this.activityMembersOpenId === item.id) { - console.log('🔍 [toggleActivityMembers] Schließe Dropdown'); this.activityMembersOpenId = null; return; } - console.log('🔍 [toggleActivityMembers] Öffne Dropdown für item.id:', item.id); this.activityMembersOpenId = item.id; // Verwende $set für Vue 2 Reaktivität @@ -2273,7 +2267,6 @@ export default { // Force update um sicherzustellen, dass das Dropdown angezeigt wird await this.$nextTick(); this.$forceUpdate(); - console.log('🔍 [toggleActivityMembers] Dropdown sollte jetzt sichtbar sein'); }, async ensureActivityMembersLoaded(activityId) { @@ -2397,16 +2390,11 @@ export default { // Group Activity Members Methods async toggleGroupActivityMembers(groupItem) { - console.log('🔍 [toggleGroupActivityMembers] Aufgerufen für groupItem.id:', groupItem.id); - console.log('🔍 [toggleGroupActivityMembers] Aktuelle groupActivityMembersOpenId:', this.groupActivityMembersOpenId); - if (this.groupActivityMembersOpenId === groupItem.id) { - console.log('🔍 [toggleGroupActivityMembers] Schließe Dropdown'); this.groupActivityMembersOpenId = null; return; } - console.log('🔍 [toggleGroupActivityMembers] Öffne Dropdown für groupItem.id:', groupItem.id); this.groupActivityMembersOpenId = groupItem.id; // Verwende $set für Vue 2 Reaktivität @@ -2419,7 +2407,6 @@ export default { // Force update um sicherzustellen, dass das Dropdown angezeigt wird await this.$nextTick(); this.$forceUpdate(); - console.log('🔍 [toggleGroupActivityMembers] Dropdown sollte jetzt sichtbar sein'); }, async ensureGroupActivityMembersLoaded(groupActivityId) { @@ -2650,7 +2637,6 @@ export default { onDiaryDateUpdated(this.handleDiaryDateUpdated); // Event-Handler für Aktivitäts-Änderungen - console.log('🔧 [DiaryView] Registriere Activity-Event-Handler'); onActivityMemberAdded(this.handleActivityMemberAdded); onActivityMemberRemoved(this.handleActivityMemberRemoved); onActivityChanged(this.handleActivityChanged); @@ -2660,7 +2646,6 @@ export default { // Event-Handler für Gruppen-Änderungen onGroupChanged(this.handleGroupChanged); - console.log('✅ [DiaryView] Alle Event-Handler registriert'); }, removeSocketListeners() { @@ -2682,7 +2667,6 @@ export default { async handleParticipantAdded(data) { // Nur aktualisieren, wenn das aktuelle Datum betroffen ist if (this.date && this.date !== 'new' && this.date.id === data.dateId) { - console.log('📡 Teilnehmer hinzugefügt (Socket):', data); // Lade Teilnehmer neu await this.loadParticipants(data.dateId); } @@ -2691,7 +2675,6 @@ export default { async handleParticipantRemoved(data) { // Nur aktualisieren, wenn das aktuelle Datum betroffen ist if (this.date && this.date !== 'new' && this.date.id === data.dateId) { - console.log('📡 Teilnehmer entfernt (Socket):', data); // Entferne aus participants-Array this.participants = this.participants.filter(memberId => memberId !== data.participantId); // Entferne aus Maps @@ -2703,47 +2686,33 @@ export default { async handleParticipantUpdated(data) { // Nur aktualisieren, wenn das aktuelle Datum betroffen ist if (this.date && this.date !== 'new' && String(this.date.id) === String(data.dateId)) { - console.log('📡 Teilnehmer aktualisiert (Socket):', data); - console.log('📡 [DiaryView] Aktuelle memberGroupsMap vor Update:', JSON.parse(JSON.stringify(this.memberGroupsMap))); - // Aktualisiere groupId in memberGroupsMap const groupValue = (data.participant.groupId !== null && data.participant.groupId !== undefined) ? String(data.participant.groupId) : ''; - console.log('📡 [DiaryView] Setze groupValue für memberId', data.participant.memberId, 'auf:', groupValue); - console.log('📡 [DiaryView] data.participant:', JSON.parse(JSON.stringify(data.participant))); - // Verwende $set für Vue 2 - das ist wichtig für Reaktivität if (this.$set) { this.$set(this.memberGroupsMap, data.participant.memberId, groupValue); - console.log('📡 [DiaryView] Verwendet $set für Vue 2'); } else { // Vue 3: Erstelle neues Objekt für Reaktivität this.memberGroupsMap = { ...this.memberGroupsMap, [data.participant.memberId]: groupValue }; - console.log('📡 [DiaryView] Verwendet Spread-Operator für Vue 3'); } // Warte auf Vue-Update und force dann ein Re-Render await this.$nextTick(); - console.log('📡 [DiaryView] memberGroupsMap nach Update:', JSON.parse(JSON.stringify(this.memberGroupsMap))); - console.log('📡 [DiaryView] getMemberGroup für memberId', data.participant.memberId, ':', this.getMemberGroup(data.participant.memberId)); // Force Vue update um sicherzustellen, dass die UI aktualisiert wird this.$forceUpdate(); - console.log('✅ [DiaryView] UI-Update erzwungen'); - } else { - console.log('⚠️ [DiaryView] Datum stimmt nicht überein - Event dateId:', data.dateId, 'Aktuelles Datum:', this.date?.id); } }, async handleDiaryNoteAdded(data) { // Nur aktualisieren, wenn das aktuelle Datum betroffen ist if (this.date && this.date !== 'new' && this.date.id === data.dateId) { - console.log('📡 Tagebuch-Notiz hinzugefügt (Socket):', data); // Lade Notizen neu, falls das betroffene Mitglied ausgewählt ist if (this.selectedMember && data.note.memberId === this.selectedMember.id) { try { @@ -2759,7 +2728,6 @@ export default { async handleDiaryNoteDeleted(data) { // Nur aktualisieren, wenn das aktuelle Datum betroffen ist if (this.date && this.date !== 'new' && this.date.id === data.dateId) { - console.log('📡 Tagebuch-Notiz gelöscht (Socket):', data); // Entferne Notiz aus notes-Array this.notes = this.notes.filter(note => note.id !== data.noteId); } @@ -2768,7 +2736,6 @@ export default { async handleDiaryTagAdded(data) { // Nur aktualisieren, wenn das aktuelle Datum betroffen ist if (this.date && this.date !== 'new' && this.date.id === data.dateId) { - console.log('📡 Tagebuch-Tag hinzugefügt (Socket):', data); // Lade Tags neu await this.loadTags(); // Aktualisiere selectedActivityTags @@ -2784,7 +2751,6 @@ export default { async handleDiaryTagRemoved(data) { // Nur aktualisieren, wenn das aktuelle Datum betroffen ist if (this.date && this.date !== 'new' && this.date.id === data.dateId) { - console.log('📡 Tagebuch-Tag entfernt (Socket):', data); // Entferne Tag aus selectedActivityTags this.selectedActivityTags = this.selectedActivityTags.filter(t => t.id !== data.tagId); } @@ -2793,7 +2759,6 @@ export default { async handleDiaryDateUpdated(data) { // Nur aktualisieren, wenn das aktuelle Datum betroffen ist if (this.date && this.date !== 'new' && this.date.id === data.dateId) { - console.log('📡 Tagebuch-Datum aktualisiert (Socket):', data); // Aktualisiere Trainingszeiten if (data.updates.trainingStart !== undefined) { this.trainingStart = data.updates.trainingStart; @@ -2805,99 +2770,68 @@ export default { }, async handleActivityMemberAdded(data) { - console.log('📡 [DiaryView] handleActivityMemberAdded aufgerufen:', data); - console.log('📡 [DiaryView] Aktuelles Datum:', this.date?.id, 'Event dateId:', data.dateId); // Nur aktualisieren, wenn das aktuelle Datum betroffen ist if (this.date && this.date !== 'new' && this.date.id === data.dateId) { - console.log('✅ [DiaryView] Datum stimmt überein, lade Training Plan neu'); // Lade Training Plan neu try { this.trainingPlan = await apiClient.get(`/diary-date-activities/${this.currentClub}/${this.date.id}`).then(response => response.data); this.calculateIntermediateTimes(); - console.log('✅ [DiaryView] Training Plan neu geladen'); } catch (error) { - console.error('❌ [DiaryView] Fehler beim Neuladen des Trainingsplans:', error); + console.error('Fehler beim Neuladen des Trainingsplans:', error); } - } else { - console.log('⚠️ [DiaryView] Datum stimmt nicht überein oder kein Datum ausgewählt'); } }, async handleActivityMemberRemoved(data) { - console.log('📡 [DiaryView] handleActivityMemberRemoved aufgerufen:', data); - console.log('📡 [DiaryView] Aktuelles Datum:', this.date?.id, 'Event dateId:', data.dateId); // Nur aktualisieren, wenn das aktuelle Datum betroffen ist if (this.date && this.date !== 'new' && this.date.id === data.dateId) { - console.log('✅ [DiaryView] Datum stimmt überein, lade Training Plan neu'); // Lade Training Plan neu try { this.trainingPlan = await apiClient.get(`/diary-date-activities/${this.currentClub}/${this.date.id}`).then(response => response.data); this.calculateIntermediateTimes(); - console.log('✅ [DiaryView] Training Plan neu geladen'); } catch (error) { - console.error('❌ [DiaryView] Fehler beim Neuladen des Trainingsplans:', error); + console.error('Fehler beim Neuladen des Trainingsplans:', error); } - } else { - console.log('⚠️ [DiaryView] Datum stimmt nicht überein oder kein Datum ausgewählt'); } }, async handleActivityChanged(data) { - console.log('📡 [DiaryView] handleActivityChanged aufgerufen:', data); - console.log('📡 [DiaryView] Aktuelles Datum:', this.date?.id, 'Event dateId:', data.dateId); // Nur aktualisieren, wenn das aktuelle Datum betroffen ist if (this.date && this.date !== 'new' && this.date.id === data.dateId) { - console.log('✅ [DiaryView] Datum stimmt überein, lade Training Plan neu'); // Lade Training Plan neu try { this.trainingPlan = await apiClient.get(`/diary-date-activities/${this.currentClub}/${this.date.id}`).then(response => response.data); this.calculateIntermediateTimes(); - console.log('✅ [DiaryView] Training Plan neu geladen'); } catch (error) { - console.error('❌ [DiaryView] Fehler beim Neuladen des Trainingsplans:', error); + console.error('Fehler beim Neuladen des Trainingsplans:', error); } - } else { - console.log('⚠️ [DiaryView] Datum stimmt nicht überein oder kein Datum ausgewählt'); } }, async handleMemberChanged(data) { - console.log('📡 [DiaryView] handleMemberChanged aufgerufen:', data); - console.log('📡 [DiaryView] Aktueller Club:', this.currentClub, 'Event Club:', data.clubId); // Prüfe, ob der Club übereinstimmt if (data.clubId && String(data.clubId) === String(this.currentClub)) { - console.log('✅ [DiaryView] Club stimmt überein, lade Mitgliederliste neu'); - console.log('📡 [DiaryView] Aktuelle Mitgliederanzahl vor Reload:', this.members?.length || 0); // Lade Mitgliederliste neu try { await this.loadMembers(); - console.log('✅ [DiaryView] Mitgliederliste neu geladen, neue Anzahl:', this.members?.length || 0); - console.log('📡 [DiaryView] Mitgliederliste:', this.members); // Force Vue update this.$forceUpdate(); } catch (error) { - console.error('❌ [DiaryView] Fehler beim Neuladen der Mitgliederliste:', error); + console.error('Fehler beim Neuladen der Mitgliederliste:', error); } - } else { - 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); + console.error('Fehler beim Neuladen der Gruppenliste:', error); } - } else { - console.log('⚠️ [DiaryView] Datum stimmt nicht überein - Event dateId:', data.dateId, 'Aktuelles Datum:', this.date?.id); } }, }, diff --git a/frontend/src/views/TournamentsView.vue b/frontend/src/views/TournamentsView.vue index 5946382..8d642a1 100644 --- a/frontend/src/views/TournamentsView.vue +++ b/frontend/src/views/TournamentsView.vue @@ -31,6 +31,10 @@ Datum: + @@ -38,11 +42,15 @@
+