feat(socket): implement match report submission and schedule update events

- Added WebSocket events for match report submission and schedule updates, enhancing real-time communication between clients and the server.
- Updated matchController to emit schedule updates when match players are modified.
- Enhanced nuscoreApiRoutes to emit match report submissions with relevant data for other clients.
- Implemented socket service methods for handling incoming match report submissions and schedule updates in the frontend.
- Updated MatchReportApiDialog and ScheduleView components to handle new WebSocket events, ensuring data synchronization across clients.
This commit is contained in:
Torsten Schulz (local)
2026-02-26 17:07:54 +01:00
parent 0ee9e486b5
commit b3bbca3887
7 changed files with 275 additions and 8 deletions

View File

@@ -1,6 +1,6 @@
import MatchService from '../services/matchService.js';
import fs from 'fs';
import { emitScheduleMatchUpdated } from '../services/socketService.js';
import { devLog } from '../utils/logger.js';
export const uploadCSV = async (req, res) => {
try {
@@ -116,7 +116,11 @@ export const updateMatchPlayers = async (req, res) => {
playersPlanned,
playersPlayed
);
if (result.clubId) {
emitScheduleMatchUpdated(result.clubId, result.id, result.match || null);
}
return res.status(200).json({
message: 'Match players updated successfully',
data: result

View File

@@ -1,5 +1,6 @@
import express from 'express';
import fetch from 'node-fetch';
import { emitMatchReportSubmitted } from '../services/socketService.js';
const router = express.Router();
@@ -256,6 +257,12 @@ router.put('/submit/:uuid', async (req, res) => {
'Cache-Control': 'no-cache, no-store, must-revalidate'
});
const clubId = reportData.clubId;
const matchCode = reportData.gameCode || reportData.code;
if (clubId && (matchCode || uuid)) {
emitMatchReportSubmitted(clubId, matchCode || uuid, reportData);
}
res.json({
success: true,
data: responseData,
@@ -271,6 +278,17 @@ router.put('/submit/:uuid', async (req, res) => {
}
});
// Nur Broadcast: aktueller Spielberichtsentwurf (z. B. Satzergebnisse) an andere Clients senden, ohne bei nuscore zu speichern
router.post('/broadcast-draft', (req, res) => {
const { clubId, gameCode, matchData } = req.body || {};
if (!clubId || !(gameCode ?? matchData?.gameCode ?? matchData?.code)) {
return res.status(400).json({ error: 'clubId und gameCode erforderlich' });
}
const code = String(gameCode ?? matchData?.gameCode ?? matchData?.code ?? '');
emitMatchReportSubmitted(clubId, code, matchData || null);
res.json({ ok: true });
});
// Validate Meeting Report API-Endpunkt (für Zwischenspeicherung)
router.put('/validate/:uuid', async (req, res) => {
const { uuid } = req.params;

View File

@@ -467,12 +467,56 @@ class MatchService {
playersPlanned: plannedList !== null ? plannedList : (match.playersPlanned || []),
playersPlayed: playedList !== null ? playedList : (match.playersPlayed || [])
});
// Aktualisiertes Match nochmals laden und für WebSocket-Broadcast anreichern (gleiche Struktur wie getMatchesForLeague)
const updated = await Match.findByPk(matchId);
const enriched = {
id: updated.id,
date: updated.date,
time: updated.time,
homeTeamId: updated.homeTeamId,
guestTeamId: updated.guestTeamId,
locationId: updated.locationId,
leagueId: updated.leagueId,
code: updated.code,
homePin: updated.homePin,
guestPin: updated.guestPin,
homeMatchPoints: updated.homeMatchPoints || 0,
guestMatchPoints: updated.guestMatchPoints || 0,
isCompleted: updated.isCompleted || false,
pdfUrl: updated.pdfUrl,
playersReady: updated.playersReady || [],
playersPlanned: updated.playersPlanned || [],
playersPlayed: updated.playersPlayed || [],
homeTeam: { name: 'Unbekannt' },
guestTeam: { name: 'Unbekannt' },
location: { name: 'Unbekannt', address: '', city: '', zip: '' },
leagueDetails: { name: 'Unbekannt' }
};
if (updated.homeTeamId) {
const homeTeam = await Team.findByPk(updated.homeTeamId, { attributes: ['name'] });
if (homeTeam) enriched.homeTeam = homeTeam;
}
if (updated.guestTeamId) {
const guestTeam = await Team.findByPk(updated.guestTeamId, { attributes: ['name'] });
if (guestTeam) enriched.guestTeam = guestTeam;
}
if (updated.locationId) {
const location = await Location.findByPk(updated.locationId, { attributes: ['name', 'address', 'city', 'zip'] });
if (location) enriched.location = location;
}
if (updated.leagueId) {
const league = await League.findByPk(updated.leagueId, { attributes: ['name'] });
if (league) enriched.leagueDetails = league;
}
return {
id: match.id,
playersReady: match.playersReady,
playersPlanned: match.playersPlanned,
playersPlayed: match.playersPlayed
id: updated.id,
clubId: updated.clubId,
playersReady: updated.playersReady,
playersPlanned: updated.playersPlanned,
playersPlayed: updated.playersPlayed,
match: enriched
};
}

View File

@@ -225,3 +225,13 @@ export const emitTournamentChanged = (clubId, tournamentId) => {
emitToClub(clubId, 'tournament:changed', { tournamentId });
};
// Event wenn Spielerauswahl (Bereit/Geplant/Gespielt) für ein Match geändert wurde (match = vollständiges angereichertes Match-Objekt)
export const emitScheduleMatchUpdated = (clubId, matchId, match = null) => {
emitToClub(clubId, 'schedule:match:updated', { clubId, matchId, match });
};
// Event wenn Spielbericht (nuscore) abgesendet wurde matchData = vollständiges Objekt für andere Clients
export const emitMatchReportSubmitted = (clubId, matchCode, matchData = null) => {
emitToClub(clubId, 'schedule:match-report:submitted', { clubId, matchCode, matchData });
};