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 @@