feat(tournament): add number of tables feature and update related logic

- Introduced a new field `numberOfTables` in the Tournament model to track the number of tables for tournaments.
- Updated the tournament update logic to include `numberOfTables` when modifying tournament details.
- Added a new endpoint to set the table number for matches, enhancing match management.
- Updated frontend components to support the new `numberOfTables` feature, including input fields and table distribution logic.
- Enhanced internationalization with new translation keys for table-related features.
This commit is contained in:
Torsten Schulz (local)
2026-02-06 15:12:05 +01:00
parent 1191636d92
commit 566361e46a
14 changed files with 352 additions and 3139 deletions

View File

@@ -271,9 +271,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, winningSets } = req.body;
const { name, date, winningSets, numberOfTables } = req.body;
try {
const tournament = await tournamentService.updateTournament(token, clubId, tournamentId, name, date, winningSets);
const tournament = await tournamentService.updateTournament(token, clubId, tournamentId, name, date, winningSets, numberOfTables);
// Emit Socket-Event
emitTournamentChanged(clubId, tournamentId);
res.status(200).json(tournament);
@@ -542,6 +542,21 @@ export const setMatchActive = async (req, res) => {
}
};
export const setMatchTableNumber = async (req, res) => {
const { authcode: token } = req.headers;
const { clubId, tournamentId, matchId } = req.params;
const { tableNumber } = req.body;
try {
await tournamentService.setMatchTableNumber(token, clubId, tournamentId, matchId, tableNumber);
// Emit Socket-Event
emitTournamentChanged(clubId, tournamentId);
res.status(200).json({ message: 'Tischnummer aktualisiert' });
} catch (err) {
console.error('[setMatchTableNumber] Error:', err);
res.status(500).json({ error: err.message });
}
};
// Externe Teilnehmer hinzufügen
export const addExternalParticipant = async (req, res) => {
const { authcode: token } = req.headers;

View File

@@ -0,0 +1,9 @@
-- Anzahl der Tische im Turnier
ALTER TABLE tournament
ADD COLUMN number_of_tables INT NULL DEFAULT NULL
COMMENT 'Anzahl der Tische, auf denen gespielt wird';
-- Tischnummer pro Match
ALTER TABLE tournament_match
ADD COLUMN table_number INT NULL DEFAULT NULL
COMMENT 'Tischnummer, an der das Match stattfindet';

View File

@@ -45,6 +45,12 @@ const Tournament = sequelize.define('Tournament', {
field: 'mini_championship_year',
comment: 'Jahr der Minimeisterschaft; nur gesetzt bei Minimeisterschaften'
},
numberOfTables: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: 'Anzahl der Tische, auf denen gespielt wird'
},
}, {
underscored: true,
tableName: 'tournament',

View File

@@ -63,6 +63,12 @@ const TournamentMatch = sequelize.define('TournamentMatch', {
type: DataTypes.STRING,
allowNull: true,
},
tableNumber: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: 'Tischnummer, an der das Match stattfindet'
},
}, {
underscored: true,
tableName: 'tournament_match',

3147
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@
"description": "",
"dependencies": {
"axios": "^1.12.2",
"bcrypt": "^5.1.1",
"bcrypt": "^6.0.0",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"csv-parser": "^3.0.0",

View File

@@ -27,6 +27,7 @@ import {
reopenMatch,
deleteKnockoutMatches,
setMatchActive,
setMatchTableNumber,
addExternalParticipant,
getExternalParticipants,
removeExternalParticipant,
@@ -76,6 +77,7 @@ router.delete('/match/result', authenticate, deleteMatchResult);
router.post("/match/reopen", authenticate, reopenMatch);
router.post('/match/finish', authenticate, finishMatch);
router.put('/match/:clubId/:tournamentId/:matchId/active', authenticate, setMatchActive);
router.put('/match/:clubId/:tournamentId/:matchId/table', authenticate, setMatchTableNumber);
router.get('/matches/:clubId/:tournamentId', authenticate, getTournamentMatches);
router.post('/knockout', authenticate, startKnockout);
router.delete("/matches/knockout", authenticate, deleteKnockoutMatches);

View File

@@ -2138,8 +2138,8 @@ Ve // 2. Neues Turnier anlegen
return JSON.parse(JSON.stringify(t));
}
// Update Turnier (Name, Datum und Gewinnsätze)
async updateTournament(userToken, clubId, tournamentId, name, date, winningSets) {
// Update Turnier (Name, Datum, Gewinnsätze und Tischanzahl)
async updateTournament(userToken, clubId, tournamentId, name, date, winningSets, numberOfTables) {
await checkAccess(userToken, clubId);
const tournament = await Tournament.findOne({ where: { id: tournamentId, clubId } });
if (!tournament) {
@@ -2162,6 +2162,12 @@ Ve // 2. Neues Turnier anlegen
}
tournament.winningSets = winningSets;
}
if (numberOfTables !== undefined) {
if (numberOfTables !== null && numberOfTables < 1) {
throw new Error('Anzahl der Tische muss mindestens 1 sein');
}
tournament.numberOfTables = numberOfTables;
}
await tournament.save();
return JSON.parse(JSON.stringify(tournament));
@@ -3355,6 +3361,20 @@ Ve // 2. Neues Turnier anlegen
await match.save();
}
async setMatchTableNumber(userToken, clubId, tournamentId, matchId, tableNumber) {
await checkAccess(userToken, clubId);
const match = await TournamentMatch.findOne({
where: { id: matchId, tournamentId }
});
if (!match) {
throw new Error("Match nicht gefunden");
}
match.tableNumber = tableNumber != null && tableNumber !== '' ? Number(tableNumber) : null;
await match.save();
}
async resetKnockout(userToken, clubId, tournamentId, classId = null) {
await checkAccess(userToken, clubId);
// lösche alle Matches außer Gruppenphase