Enhance match management functionality by adding player selection capabilities. Introduce new endpoints for updating match players and retrieving player match statistics in matchController and matchService. Update Match model to include fields for players ready, planned, and played. Modify frontend components to support player selection dialog, allowing users to manage player statuses effectively. Improve UI for better user experience and data visibility.
This commit is contained in:
@@ -102,3 +102,46 @@ export const fetchLeagueTableFromMyTischtennis = async (req, res) => {
|
||||
return res.status(500).json({ error: 'Failed to fetch league table from MyTischtennis' });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateMatchPlayers = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { matchId } = req.params;
|
||||
const { playersReady, playersPlanned, playersPlayed } = req.body;
|
||||
|
||||
const result = await MatchService.updateMatchPlayers(
|
||||
userToken,
|
||||
matchId,
|
||||
playersReady,
|
||||
playersPlanned,
|
||||
playersPlayed
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'Match players updated successfully',
|
||||
data: result
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error updating match players:', error);
|
||||
return res.status(error.statusCode || 500).json({
|
||||
error: error.message || 'Failed to update match players'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const getPlayerMatchStats = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, leagueId } = req.params;
|
||||
const { seasonid: seasonId } = req.query;
|
||||
|
||||
const stats = await MatchService.getPlayerMatchStats(userToken, clubId, leagueId, seasonId);
|
||||
|
||||
return res.status(200).json(stats);
|
||||
} catch (error) {
|
||||
console.error('Error retrieving player match stats:', error);
|
||||
return res.status(error.statusCode || 500).json({
|
||||
error: error.message || 'Failed to retrieve player match stats'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
8
backend/migrations/add_player_tracking_to_match.sql
Normal file
8
backend/migrations/add_player_tracking_to_match.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- Add player tracking fields to match table
|
||||
-- These fields store arrays of member IDs for different participation states
|
||||
|
||||
ALTER TABLE `match`
|
||||
ADD COLUMN `players_ready` JSON NULL COMMENT 'Array of member IDs who are ready to play' AFTER `pdf_url`,
|
||||
ADD COLUMN `players_planned` JSON NULL COMMENT 'Array of member IDs who are planned to play' AFTER `players_ready`,
|
||||
ADD COLUMN `players_played` JSON NULL COMMENT 'Array of member IDs who actually played' AFTER `players_planned`;
|
||||
|
||||
@@ -109,6 +109,24 @@ const Match = sequelize.define('Match', {
|
||||
comment: 'PDF URL from myTischtennis',
|
||||
field: 'pdf_url'
|
||||
},
|
||||
playersReady: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: 'Array of member IDs who are ready to play',
|
||||
field: 'players_ready'
|
||||
},
|
||||
playersPlanned: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: 'Array of member IDs who are planned to play',
|
||||
field: 'players_planned'
|
||||
},
|
||||
playersPlayed: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: 'Array of member IDs who actually played',
|
||||
field: 'players_played'
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'match',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import express from 'express';
|
||||
import { uploadCSV, getLeaguesForCurrentSeason, getMatchesForLeagues, getMatchesForLeague, getLeagueTable, fetchLeagueTableFromMyTischtennis } from '../controllers/matchController.js';
|
||||
import { uploadCSV, getLeaguesForCurrentSeason, getMatchesForLeagues, getMatchesForLeague, getLeagueTable, fetchLeagueTableFromMyTischtennis, updateMatchPlayers, getPlayerMatchStats } from '../controllers/matchController.js';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import multer from 'multer';
|
||||
|
||||
@@ -13,6 +13,8 @@ router.get('/leagues/:clubId/matches/:leagueId', authenticate, getMatchesForLeag
|
||||
router.get('/leagues/:clubId/matches', authenticate, getMatchesForLeagues);
|
||||
router.get('/leagues/:clubId/table/:leagueId', authenticate, getLeagueTable);
|
||||
router.post('/leagues/:clubId/table/:leagueId/fetch', authenticate, fetchLeagueTableFromMyTischtennis);
|
||||
router.patch('/:matchId/players', authenticate, updateMatchPlayers);
|
||||
router.get('/leagues/:clubId/stats/:leagueId', authenticate, getPlayerMatchStats);
|
||||
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -49,7 +49,7 @@ const __dirname = path.dirname(__filename);
|
||||
app.use(cors({
|
||||
origin: true,
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'authcode', 'userid']
|
||||
}));
|
||||
app.use(express.json());
|
||||
|
||||
@@ -241,6 +241,9 @@ class MatchService {
|
||||
guestMatchPoints: match.guestMatchPoints || 0,
|
||||
isCompleted: match.isCompleted || false,
|
||||
pdfUrl: match.pdfUrl,
|
||||
playersReady: match.playersReady || [],
|
||||
playersPlanned: match.playersPlanned || [],
|
||||
playersPlayed: match.playersPlayed || [],
|
||||
homeTeam: { name: 'Unbekannt' },
|
||||
guestTeam: { name: 'Unbekannt' },
|
||||
location: { name: 'Unbekannt', address: '', city: '', zip: '' },
|
||||
@@ -353,6 +356,9 @@ class MatchService {
|
||||
guestMatchPoints: match.guestMatchPoints || 0,
|
||||
isCompleted: match.isCompleted || false,
|
||||
pdfUrl: match.pdfUrl,
|
||||
playersReady: match.playersReady || [],
|
||||
playersPlanned: match.playersPlanned || [],
|
||||
playersPlayed: match.playersPlayed || [],
|
||||
homeTeam: { name: 'Unbekannt' },
|
||||
guestTeam: { name: 'Unbekannt' },
|
||||
location: { name: 'Unbekannt', address: '', city: '', zip: '' },
|
||||
@@ -430,6 +436,118 @@ class MatchService {
|
||||
}
|
||||
}
|
||||
|
||||
async updateMatchPlayers(userToken, matchId, playersReady, playersPlanned, playersPlayed) {
|
||||
// Find the match and verify access
|
||||
const match = await Match.findByPk(matchId, {
|
||||
include: [
|
||||
{ model: Club, as: 'club' }
|
||||
]
|
||||
});
|
||||
|
||||
if (!match) {
|
||||
throw new HttpError(404, 'Match not found');
|
||||
}
|
||||
|
||||
await checkAccess(userToken, match.clubId);
|
||||
|
||||
// Update player arrays
|
||||
await match.update({
|
||||
playersReady: playersReady || [],
|
||||
playersPlanned: playersPlanned || [],
|
||||
playersPlayed: playersPlayed || []
|
||||
});
|
||||
|
||||
return {
|
||||
id: match.id,
|
||||
playersReady: match.playersReady,
|
||||
playersPlanned: match.playersPlanned,
|
||||
playersPlayed: match.playersPlayed
|
||||
};
|
||||
}
|
||||
|
||||
async getPlayerMatchStats(userToken, clubId, leagueId, seasonId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
|
||||
// Get all matches for this league/season
|
||||
const matches = await Match.findAll({
|
||||
where: {
|
||||
clubId: clubId,
|
||||
leagueId: leagueId
|
||||
},
|
||||
attributes: ['id', 'date', 'playersPlayed']
|
||||
});
|
||||
|
||||
// Get all members
|
||||
const Member = (await import('../models/Member.js')).default;
|
||||
const members = await Member.findAll({
|
||||
where: { clubId: clubId, active: true },
|
||||
attributes: ['id', 'firstName', 'lastName']
|
||||
});
|
||||
|
||||
// Calculate stats
|
||||
const stats = {};
|
||||
const now = new Date();
|
||||
|
||||
// Saison startet am 1. Juli
|
||||
const seasonStart = new Date();
|
||||
seasonStart.setMonth(6, 1); // 1. Juli
|
||||
seasonStart.setHours(0, 0, 0, 0);
|
||||
if (seasonStart > now) {
|
||||
seasonStart.setFullYear(seasonStart.getFullYear() - 1);
|
||||
}
|
||||
|
||||
// Vorrunde: 1. Juli bis 31. Dezember
|
||||
const firstHalfEnd = new Date(seasonStart.getFullYear(), 11, 31, 23, 59, 59, 999); // 31. Dezember
|
||||
|
||||
// Rückrunde startet am 1. Januar (im Jahr nach Saisonstart)
|
||||
const secondHalfStart = new Date(seasonStart.getFullYear() + 1, 0, 1, 0, 0, 0, 0); // 1. Januar
|
||||
|
||||
for (const member of members) {
|
||||
stats[member.id] = {
|
||||
memberId: member.id,
|
||||
firstName: member.firstName,
|
||||
lastName: member.lastName,
|
||||
totalSeason: 0,
|
||||
totalFirstHalf: 0,
|
||||
totalSecondHalf: 0
|
||||
};
|
||||
}
|
||||
|
||||
for (const match of matches) {
|
||||
// Parse playersPlayed if it's a JSON string
|
||||
let playersPlayed = match.playersPlayed;
|
||||
if (typeof playersPlayed === 'string') {
|
||||
try {
|
||||
playersPlayed = JSON.parse(playersPlayed);
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!playersPlayed || !Array.isArray(playersPlayed) || playersPlayed.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const matchDate = new Date(match.date);
|
||||
const isInSeason = matchDate >= seasonStart;
|
||||
const isInFirstHalf = matchDate >= seasonStart && matchDate <= firstHalfEnd;
|
||||
const isInSecondHalf = matchDate >= secondHalfStart;
|
||||
|
||||
for (const playerId of playersPlayed) {
|
||||
if (stats[playerId]) {
|
||||
if (isInSeason) stats[playerId].totalSeason++;
|
||||
if (isInFirstHalf) stats[playerId].totalFirstHalf++;
|
||||
if (isInSecondHalf) stats[playerId].totalSecondHalf++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to array, filter out players with 0 matches, and sort by totalSeason descending
|
||||
return Object.values(stats)
|
||||
.filter(player => player.totalSeason > 0)
|
||||
.sort((a, b) => b.totalSeason - a.totalSeason);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new MatchService();
|
||||
|
||||
Reference in New Issue
Block a user