some enhancements for tournaments
This commit is contained in:
@@ -1,29 +1,33 @@
|
||||
// controllers/tournamentController.js
|
||||
import tournamentService from "../services/tournamentService.js";
|
||||
|
||||
// 1. Alle Turniere eines Vereins
|
||||
export const getTournaments = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const clubId = req.params.clubId;
|
||||
const { clubId } = req.params;
|
||||
try {
|
||||
const tournaments = await tournamentService.getTournaments(token, clubId);
|
||||
res.status(200).json(tournaments);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 2. Neues Turnier anlegen
|
||||
export const addTournament = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentName, date } = req.body;
|
||||
try {
|
||||
const tournament = await tournamentService.addTournament(token, clubId, tournamentName, date);
|
||||
res.status(200).json(tournament);
|
||||
res.status(201).json(tournament);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 3. Teilnehmer hinzufügen
|
||||
export const addParticipant = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId, participant: participantId } = req.body;
|
||||
@@ -32,11 +36,12 @@ export const addParticipant = async (req, res) => {
|
||||
const participants = await tournamentService.getParticipants(token, clubId, tournamentId);
|
||||
res.status(200).json(participants);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 4. Teilnehmerliste abrufen
|
||||
export const getParticipants = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.body;
|
||||
@@ -44,33 +49,38 @@ export const getParticipants = async (req, res) => {
|
||||
const participants = await tournamentService.getParticipants(token, clubId, tournamentId);
|
||||
res.status(200).json(participants);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 5. Turniermodus (Gruppen/K.O.) setzen
|
||||
export const setModus = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId, type, numberOfGroups } = req.body;
|
||||
try {
|
||||
await tournamentService.setModus(token, clubId, tournamentId, type, numberOfGroups);
|
||||
res.sendStatus(204);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 6. Gruppen-Strukturen anlegen (leere Gruppen)
|
||||
export const createGroups = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.body;
|
||||
try {
|
||||
await tournamentService.createGroups(token, clubId, tournamentId);
|
||||
res.sendStatus(204);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 7. Teilnehmer zufällig auf Gruppen verteilen & Gruppenspiele anlegen
|
||||
export const fillGroups = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.body;
|
||||
@@ -78,11 +88,12 @@ export const fillGroups = async (req, res) => {
|
||||
const updatedMembers = await tournamentService.fillGroups(token, clubId, tournamentId);
|
||||
res.status(200).json(updatedMembers);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 8. Gruppen mit ihren Teilnehmern abfragen
|
||||
export const getGroups = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.query;
|
||||
@@ -90,11 +101,12 @@ export const getGroups = async (req, res) => {
|
||||
const groups = await tournamentService.getGroupsWithParticipants(token, clubId, tournamentId);
|
||||
res.status(200).json(groups);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 9. Einzelnes Turnier abrufen
|
||||
export const getTournament = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.params;
|
||||
@@ -102,11 +114,12 @@ export const getTournament = async (req, res) => {
|
||||
const tournament = await tournamentService.getTournament(token, clubId, tournamentId);
|
||||
res.status(200).json(tournament);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 10. Alle Spiele eines Turniers abfragen
|
||||
export const getTournamentMatches = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.params;
|
||||
@@ -114,15 +127,15 @@ export const getTournamentMatches = async (req, res) => {
|
||||
const matches = await tournamentService.getTournamentMatches(token, clubId, tournamentId);
|
||||
res.status(200).json(matches);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 11. Satz-Ergebnis speichern
|
||||
export const addMatchResult = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId, matchId, set, result } = req.body;
|
||||
|
||||
try {
|
||||
await tournamentService.addMatchResult(token, clubId, tournamentId, matchId, set, result);
|
||||
res.status(200).json({ message: "Result added successfully" });
|
||||
@@ -132,15 +145,28 @@ export const addMatchResult = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 12. Spiel abschließen (Endergebnis ermitteln)
|
||||
export const finishMatch = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId, matchId } = req.body;
|
||||
|
||||
try {
|
||||
await tournamentService.finishMatch(token, clubId, tournamentId, matchId);
|
||||
res.status(200).json({ message: "Match finished successfully" });
|
||||
} catch (error) {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const startKnockout = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.body;
|
||||
|
||||
try {
|
||||
await tournamentService.startKnockout(token, clubId, tournamentId);
|
||||
res.status(200).json({ message: 'K.O.-Runde erfolgreich gestartet' });
|
||||
} catch (error) {
|
||||
console.error('Error in startKnockout:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -27,7 +27,8 @@ const Tournament = sequelize.define('Tournament', {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 1
|
||||
}
|
||||
},
|
||||
advancingPerGroup: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 1 },
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'tournament',
|
||||
|
||||
@@ -1,36 +1,51 @@
|
||||
// models/TournamentMatch.js
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import Tournament from './Tournament.js';
|
||||
import TournamentGroup from './TournamentGroup.js';
|
||||
|
||||
const TournamentMatch = sequelize.define('TournamentMatch', {
|
||||
tournamentId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
tournamentId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Tournament,
|
||||
key: 'id'
|
||||
},
|
||||
groupId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
round: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
player1Id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
player2Id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
isFinished: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
result: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
},
|
||||
groupId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: TournamentGroup,
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'SET NULL',
|
||||
onUpdate: 'CASCADE'
|
||||
},
|
||||
round: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
player1Id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
player2Id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
isFinished: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
result: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'tournament_match',
|
||||
|
||||
@@ -149,8 +149,19 @@ Tournament.hasMany(TournamentMatch, { foreignKey: 'tournamentId', as: 'tournamen
|
||||
TournamentMatch.belongsTo(TournamentGroup, { foreignKey: 'groupId', as: 'group' });
|
||||
TournamentGroup.hasMany(TournamentMatch, { foreignKey: 'groupId', as: 'tournamentMatches' });
|
||||
|
||||
TournamentResult.belongsTo(TournamentMatch, { foreignKey: 'matchId', as: 'match' });
|
||||
TournamentMatch.hasMany(TournamentResult, { foreignKey: 'matchId', as: 'tournamentResults' });
|
||||
TournamentMatch.hasMany(TournamentResult, {
|
||||
foreignKey: 'matchId',
|
||||
as: 'tournamentResults',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
});
|
||||
|
||||
TournamentResult.belongsTo(TournamentMatch, {
|
||||
foreignKey: 'matchId',
|
||||
as: 'match',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
});
|
||||
|
||||
TournamentMatch.belongsTo(TournamentMember, { foreignKey: 'player1Id', as: 'player1' });
|
||||
TournamentMatch.belongsTo(TournamentMember, { foreignKey: 'player2Id', as: 'player2' });
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
getTournamentMatches,
|
||||
addMatchResult,
|
||||
finishMatch,
|
||||
startKnockout,
|
||||
} from '../controllers/tournamentController.js';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
|
||||
@@ -28,6 +29,8 @@ router.post('/match/finish', authenticate, finishMatch);
|
||||
router.get('/matches/:clubId/:tournamentId', authenticate, getTournamentMatches);
|
||||
router.get('/:clubId/:tournamentId', authenticate, getTournament);
|
||||
router.get('/:clubId', authenticate, getTournaments);
|
||||
router.post('/knockout', authenticate, startKnockout);
|
||||
router.post('/', authenticate, addTournament);
|
||||
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -6,300 +6,330 @@ import TournamentMatch from "../models/TournamentMatch.js";
|
||||
import TournamentMember from "../models/TournamentMember.js";
|
||||
import TournamentResult from "../models/TournamentResult.js";
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import { Op, literal } from 'sequelize';
|
||||
|
||||
class TournamentService {
|
||||
// 1. Turniere listen
|
||||
async getTournaments(userToken, clubId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const tournaments = await Tournament.findAll(
|
||||
{
|
||||
where: { clubId },
|
||||
order: [['date', 'DESC']],
|
||||
attributes: ['id', 'name', 'date']
|
||||
}
|
||||
);
|
||||
const tournaments = await Tournament.findAll({
|
||||
where: { clubId },
|
||||
order: [['date', 'DESC']],
|
||||
attributes: ['id', 'name', 'date']
|
||||
});
|
||||
return JSON.parse(JSON.stringify(tournaments));
|
||||
}
|
||||
|
||||
// 2. Neues Turnier anlegen (prüft Duplikat)
|
||||
async addTournament(userToken, clubId, tournamentName, date) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const club = await Club.findByPk(clubId);
|
||||
await Tournament.create({
|
||||
const existing = await Tournament.findOne({ where: { clubId, date } });
|
||||
if (existing) {
|
||||
throw new Error('Ein Turnier mit diesem Datum existiert bereits');
|
||||
}
|
||||
const t = await Tournament.create({
|
||||
name: tournamentName,
|
||||
date: date,
|
||||
clubId: club.id,
|
||||
date,
|
||||
clubId: +clubId,
|
||||
bestOfEndroundSize: 0,
|
||||
type: '',
|
||||
name: '',
|
||||
type: ''
|
||||
});
|
||||
return await this.getTournaments(userToken, clubId);
|
||||
}
|
||||
return JSON.parse(JSON.stringify(t));
|
||||
}
|
||||
|
||||
async addParticipant(token, clubId, tournamentId, participantId) {
|
||||
await checkAccess(token, clubId);
|
||||
// 3. Teilnehmer hinzufügen (kein Duplikat)
|
||||
async addParticipant(userToken, clubId, tournamentId, participantId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Tournament not found');
|
||||
throw new Error('Turnier nicht gefunden');
|
||||
}
|
||||
const participant = TournamentMember.findAll({
|
||||
where: { tournamentId: tournamentId, groupId: participantId, clubMemberId: participantId },
|
||||
const exists = await TournamentMember.findOne({
|
||||
where: { tournamentId, clubMemberId: participantId }
|
||||
});
|
||||
if (participant) {
|
||||
throw new Error('Participant already exists');
|
||||
if (exists) {
|
||||
throw new Error('Teilnehmer bereits hinzugefügt');
|
||||
}
|
||||
await TournamentMember.create({
|
||||
tournamentId: tournamentId,
|
||||
groupId: participantId,
|
||||
tournamentId,
|
||||
clubMemberId: participantId,
|
||||
groupId: null
|
||||
});
|
||||
}
|
||||
|
||||
async getParticipants(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
// 4. Teilnehmerliste
|
||||
async getParticipants(userToken, clubId, tournamentId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Tournament not found');
|
||||
throw new Error('Turnier nicht gefunden');
|
||||
}
|
||||
return await TournamentMember.findAll({
|
||||
where: {
|
||||
tournamentId: tournamentId,
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Member,
|
||||
as: 'member',
|
||||
attributes: ['id', 'lastName', 'firstName'],
|
||||
order: [['firstName', 'ASC'], ['lastName', 'ASC']],
|
||||
}
|
||||
]
|
||||
where: { tournamentId },
|
||||
include: [{
|
||||
model: Member,
|
||||
as: 'member',
|
||||
attributes: ['id', 'firstName', 'lastName'],
|
||||
}],
|
||||
order: [[{ model: Member, as: 'member' }, 'firstName', 'ASC']]
|
||||
});
|
||||
}
|
||||
|
||||
async setModus(token, clubId, tournamentId, type, numberOfGroups) {
|
||||
await checkAccess(token, clubId);
|
||||
// 5. Modus setzen (Gruppen / KO‑Runde)
|
||||
async setModus(userToken, clubId, tournamentId, type, numberOfGroups) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Tournament not found');
|
||||
throw new Error('Turnier nicht gefunden');
|
||||
}
|
||||
await tournament.update({ type, numberOfGroups });
|
||||
}
|
||||
|
||||
async createGroups(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
|
||||
// 6. Leere Gruppen anlegen
|
||||
async createGroups(userToken, clubId, tournamentId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Tournament not found');
|
||||
throw new Error('Turnier nicht gefunden');
|
||||
}
|
||||
const existingGroups = await TournamentGroup.findAll({ where: { tournamentId } });
|
||||
const desiredGroupCount = tournament.numberOfGroups;
|
||||
if (existingGroups.length < desiredGroupCount) {
|
||||
const missingGroups = desiredGroupCount - existingGroups.length;
|
||||
for (let i = 0; i < missingGroups; i++) {
|
||||
await TournamentGroup.create({ tournamentId });
|
||||
}
|
||||
} else if (existingGroups.length > desiredGroupCount) {
|
||||
existingGroups.sort((a, b) => a.id - b.id);
|
||||
const groupsToRemove = existingGroups.slice(desiredGroupCount);
|
||||
for (const group of groupsToRemove) {
|
||||
await group.destroy();
|
||||
}
|
||||
const existing = await TournamentGroup.findAll({ where: { tournamentId } });
|
||||
const desired = tournament.numberOfGroups;
|
||||
// zu viele Gruppen löschen
|
||||
if (existing.length > desired) {
|
||||
const toRemove = existing.slice(desired);
|
||||
await Promise.all(toRemove.map(g => g.destroy()));
|
||||
}
|
||||
// fehlende Gruppen anlegen
|
||||
for (let i = existing.length; i < desired; i++) {
|
||||
await TournamentGroup.create({ tournamentId });
|
||||
}
|
||||
}
|
||||
|
||||
async fillGroups(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
// 7. Gruppen zufällig füllen & Spiele anlegen
|
||||
async fillGroups(userToken, clubId, tournamentId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Tournament not found');
|
||||
throw new Error('Turnier nicht gefunden');
|
||||
}
|
||||
const groups = await TournamentGroup.findAll({ where: { tournamentId } });
|
||||
if (!groups || groups.length === 0) {
|
||||
throw new Error('No groups available. Please create groups first.');
|
||||
if (!groups.length) {
|
||||
throw new Error('Keine Gruppen vorhanden. Erst erstellen.');
|
||||
}
|
||||
const members = await TournamentMember.findAll({ where: { tournamentId } });
|
||||
if (!members || members.length === 0) {
|
||||
throw new Error('No tournament members found.');
|
||||
if (!members.length) {
|
||||
throw new Error('Keine Teilnehmer vorhanden.');
|
||||
}
|
||||
// alte Matches löschen
|
||||
await TournamentMatch.destroy({ where: { tournamentId } });
|
||||
const shuffledMembers = [...members];
|
||||
for (let i = shuffledMembers.length - 1; i > 0; i--) {
|
||||
|
||||
// mische Teilnehmer
|
||||
const shuffled = members.slice();
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffledMembers[i], shuffledMembers[j]] = [shuffledMembers[j], shuffledMembers[i]];
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
const numberOfGroups = groups.length;
|
||||
for (let i = 0; i < shuffledMembers.length; i++) {
|
||||
const groupAssignment = groups[i % numberOfGroups].id;
|
||||
await shuffledMembers[i].update({ groupId: groupAssignment });
|
||||
// verteile in round‑robin‑Gruppen
|
||||
for (let idx = 0; idx < shuffled.length; idx++) {
|
||||
const grpId = groups[idx % groups.length].id;
|
||||
await shuffled[idx].update({ groupId: grpId });
|
||||
}
|
||||
for (const group of groups) {
|
||||
const groupMembers = await TournamentMember.findAll({ where: { groupId: group.id } });
|
||||
for (let i = 0; i < groupMembers.length; i++) {
|
||||
for (let j = i + 1; j < groupMembers.length; j++) {
|
||||
// lege alle Paarungen in jeder Gruppe an
|
||||
for (const g of groups) {
|
||||
const gm = await TournamentMember.findAll({ where: { groupId: g.id } });
|
||||
for (let i = 0; i < gm.length; i++) {
|
||||
for (let j = i + 1; j < gm.length; j++) {
|
||||
await TournamentMatch.create({
|
||||
tournamentId: tournamentId,
|
||||
groupId: group.id,
|
||||
tournamentId,
|
||||
groupId: g.id,
|
||||
round: 'group',
|
||||
player1Id: groupMembers[i].id,
|
||||
player2Id: groupMembers[j].id,
|
||||
player1Id: gm[i].id,
|
||||
player2Id: gm[j].id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return await TournamentMember.findAll({ where: { tournamentId } });
|
||||
}
|
||||
|
||||
async getGroups(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
|
||||
// 8. Nur Gruppen (ohne Teilnehmer)
|
||||
async getGroups(userToken, clubId, tournamentId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Tournament not found');
|
||||
throw new Error('Turnier nicht gefunden');
|
||||
}
|
||||
const groups = await TournamentGroup.findAll({ where: { tournamentId } });
|
||||
return groups;
|
||||
return await TournamentGroup.findAll({ where: { tournamentId } });
|
||||
}
|
||||
|
||||
async getGroupsWithParticipants(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
// 9. Gruppen mit ihren Teilnehmern
|
||||
async getGroupsWithParticipants(userToken, clubId, tournamentId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Tournament not found');
|
||||
throw new Error('Turnier nicht gefunden');
|
||||
}
|
||||
const groups = await TournamentGroup.findAll({
|
||||
where: { tournamentId },
|
||||
include: [
|
||||
{
|
||||
model: TournamentMember,
|
||||
as: 'tournamentGroupMembers',
|
||||
required: false,
|
||||
include: [
|
||||
{
|
||||
model: Member,
|
||||
as: 'member',
|
||||
attributes: ['id', 'firstName', 'lastName']
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
include: [{
|
||||
model: TournamentMember,
|
||||
as: 'tournamentGroupMembers',
|
||||
include: [{ model: Member, as: 'member', attributes: ['id', 'firstName', 'lastName'] }]
|
||||
}],
|
||||
order: [['id', 'ASC']]
|
||||
});
|
||||
return groups.map(group => ({
|
||||
groupId: group.id,
|
||||
participants: group.tournamentGroupMembers.map(p => ({
|
||||
id: p.id,
|
||||
name: `${p.member.firstName} ${p.member.lastName}`
|
||||
return groups.map(g => ({
|
||||
groupId: g.id,
|
||||
participants: g.tournamentGroupMembers.map(m => ({
|
||||
id: m.id,
|
||||
name: `${m.member.firstName} ${m.member.lastName}`
|
||||
}))
|
||||
}));
|
||||
}
|
||||
|
||||
async getTournament(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
const tournament = await Tournament.findOne({
|
||||
where: { id: tournamentId, clubId },
|
||||
});
|
||||
if (!tournament) {
|
||||
throw new Error('Tournament not found');
|
||||
}
|
||||
return tournament;
|
||||
// 10. Einzelnes Turnier
|
||||
async getTournament(userToken, clubId, tournamentId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const t = await Tournament.findOne({ where: { id: tournamentId, clubId } });
|
||||
if (!t) throw new Error('Turnier nicht gefunden');
|
||||
return t;
|
||||
}
|
||||
|
||||
async getTournamentMatches(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
const tournament = await Tournament.findOne({
|
||||
where: { id: tournamentId, clubId },
|
||||
});
|
||||
if (!tournament) {
|
||||
throw new Error('Tournament not found');
|
||||
}
|
||||
const matches = await TournamentMatch.findAll({
|
||||
// 11. Spiele eines Turniers
|
||||
async getTournamentMatches(userToken, clubId, tournamentId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const t = await Tournament.findOne({ where: { id: tournamentId, clubId } });
|
||||
if (!t) throw new Error('Turnier nicht gefunden');
|
||||
return await TournamentMatch.findAll({
|
||||
where: { tournamentId },
|
||||
include: [
|
||||
{
|
||||
model: TournamentMember,
|
||||
as: 'player1',
|
||||
include: {
|
||||
model: Member,
|
||||
as: 'member',
|
||||
}
|
||||
},
|
||||
{
|
||||
model: TournamentMember,
|
||||
as: 'player2',
|
||||
include: {
|
||||
model: Member,
|
||||
as: 'member',
|
||||
}
|
||||
},
|
||||
{
|
||||
model: TournamentResult,
|
||||
as: 'tournamentResults',
|
||||
order: [['set', 'ASC']]
|
||||
}
|
||||
{ model: TournamentMember, as: 'player1', include: [{ model: Member, as: 'member' }] },
|
||||
{ model: TournamentMember, as: 'player2', include: [{ model: Member, as: 'member' }] },
|
||||
{ model: TournamentResult, as: 'tournamentResults' }
|
||||
],
|
||||
order: [['id', 'ASC']]
|
||||
order: [
|
||||
['id', 'ASC'],
|
||||
[{ model: TournamentResult, as: 'tournamentResults' }, 'set', 'ASC']
|
||||
]
|
||||
});
|
||||
return matches;
|
||||
}
|
||||
|
||||
async addMatchResult(token, clubId, tournamentId, matchId, set, result) {
|
||||
await checkAccess(token, clubId);
|
||||
|
||||
const matches = await TournamentMatch.findAll({
|
||||
where: { id: matchId, tournamentId },
|
||||
});
|
||||
|
||||
if (matches.length > 0) {
|
||||
const match = matches[0];
|
||||
const tournamentResult = await TournamentResult.findOne({where: {
|
||||
matchId: match.id,
|
||||
set: set,
|
||||
}
|
||||
});
|
||||
if (tournamentResult && tournamentResult.set == set) {
|
||||
tournamentResult.result = result;
|
||||
await tournamentResult.save();
|
||||
} else {
|
||||
const points = result.split(':');
|
||||
await TournamentResult.create({
|
||||
matchId,
|
||||
set,
|
||||
pointsPlayer1: points[0],
|
||||
pointsPlayer2: points[1],
|
||||
});
|
||||
}
|
||||
return;
|
||||
// 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');
|
||||
const existing = await TournamentResult.findOne({ where: { matchId, set } });
|
||||
if (existing) {
|
||||
existing.pointsPlayer1 = +result.split(':')[0];
|
||||
existing.pointsPlayer2 = +result.split(':')[1];
|
||||
await existing.save();
|
||||
} else {
|
||||
const [p1, p2] = result.split(':').map(Number);
|
||||
await TournamentResult.create({ matchId, set, pointsPlayer1: p1, pointsPlayer2: p2 });
|
||||
}
|
||||
throw new Error('Match not found');
|
||||
}
|
||||
|
||||
async finishMatch(token, clubId, tournamentId, matchId) {
|
||||
await checkAccess(token, clubId);
|
||||
// 13. Match abschließen (Endergebnis setzen)
|
||||
async finishMatch(userToken, clubId, tournamentId, matchId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const matches = await TournamentMatch.findAll({
|
||||
where: { id: matchId, tournamentId },
|
||||
include: [
|
||||
{
|
||||
model: TournamentResult,
|
||||
as: 'tournamentResults'
|
||||
}
|
||||
],
|
||||
include: [{ model: TournamentResult, as: 'tournamentResults' }],
|
||||
order: [[{ model: TournamentResult, as: 'tournamentResults' }, 'set', 'ASC']]
|
||||
});
|
||||
let win = 0;
|
||||
let lose = 0;
|
||||
for (const results of matches[0].tournamentResults) {
|
||||
if (results.pointsPlayer1 > results.pointsPlayer2) {
|
||||
win++;
|
||||
} else {
|
||||
lose++;
|
||||
const match = matches[0];
|
||||
if (!match) throw new Error('Match nicht gefunden');
|
||||
let win = 0, lose = 0;
|
||||
match.tournamentResults.forEach(r => {
|
||||
if (r.pointsPlayer1 > r.pointsPlayer2) win++;
|
||||
else lose++;
|
||||
});
|
||||
match.isFinished = true;
|
||||
match.result = `${win}:${lose}`;
|
||||
await match.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ermittelt aus jeder Gruppe den Gruppensieger und legt
|
||||
* für die K.O.-Runde die ersten Matches an.
|
||||
*/
|
||||
// services/tournamentService.js
|
||||
async startKnockout(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
const t = await Tournament.findByPk(tournamentId);
|
||||
if (!t || t.clubId != clubId) throw new Error('Tournament not found');
|
||||
|
||||
const totalQualifiers = t.numberOfGroups * t.advancingPerGroup;
|
||||
if (totalQualifiers < 2) throw new Error('Zu wenige Qualifikanten für K.O.-Runde');
|
||||
|
||||
// lösche frühere KO-Matches
|
||||
await TournamentMatch.destroy({ where: { tournamentId, round: { [Op.ne]: 'group' } } });
|
||||
|
||||
// lade alle Gruppenteilnehmer
|
||||
const groups = await TournamentGroup.findAll({
|
||||
where: { tournamentId },
|
||||
include: [{ model: TournamentMember, as: 'tournamentGroupMembers' }]
|
||||
});
|
||||
// lade alle Gruppenspiele und Ergebnisse
|
||||
const groupMatches = await TournamentMatch.findAll({
|
||||
where: { tournamentId, round: 'group' },
|
||||
include: [{ model: TournamentResult, as: 'tournamentResults' }]
|
||||
});
|
||||
|
||||
const qualifiers = [];
|
||||
for (const g of groups) {
|
||||
// init stats
|
||||
const stats = {};
|
||||
g.tournamentGroupMembers.forEach(m => {
|
||||
stats[m.id] = { member: m, points: 0, setsWon: 0, setsLost: 0 };
|
||||
});
|
||||
// auswerten
|
||||
for (const m of groupMatches.filter(m => m.groupId === g.id && m.isFinished)) {
|
||||
const [p1, p2] = m.result.split(':').map(n => parseInt(n, 10));
|
||||
if (p1 > p2) stats[m.player1Id].points += 2;
|
||||
else if (p2 > p1) stats[m.player2Id].points += 2;
|
||||
stats[m.player1Id].setsWon += p1;
|
||||
stats[m.player1Id].setsLost += p2;
|
||||
stats[m.player2Id].setsWon += p2;
|
||||
stats[m.player2Id].setsLost += p1;
|
||||
}
|
||||
// sortieren
|
||||
const ranked = Object.values(stats).sort((a, b) => {
|
||||
const diffA = a.setsWon - a.setsLost;
|
||||
const diffB = b.setsWon - b.setsLost;
|
||||
if (b.points !== a.points) return b.points - a.points;
|
||||
if (diffB !== diffA) return diffB - diffA;
|
||||
if (b.setsWon !== a.setsWon) return b.setsWon - a.setsWon;
|
||||
return a.member.id - b.member.id;
|
||||
});
|
||||
// take top N
|
||||
qualifiers.push(...ranked.slice(0, t.advancingPerGroup).map(r => r.member));
|
||||
}
|
||||
const result = win.toString() + ':' + lose.toString();
|
||||
if (matches.length == 1) {
|
||||
const match = matches[0];
|
||||
match.isFinished = true;
|
||||
match.result = result;
|
||||
await match.save();
|
||||
return;
|
||||
|
||||
// bracket aufbauen
|
||||
let roundSize = qualifiers.length;
|
||||
const getRoundName = size => {
|
||||
switch (size) {
|
||||
case 2: return 'Finale';
|
||||
case 4: return 'Halbfinale';
|
||||
case 8: return 'Viertelfinale';
|
||||
case 16: return 'Achtelfinale';
|
||||
default: return `Runde der ${size}`;
|
||||
}
|
||||
};
|
||||
|
||||
while (roundSize >= 2) {
|
||||
const rn = getRoundName(roundSize);
|
||||
for (let i = 0; i < roundSize / 2; i++) {
|
||||
const p1 = qualifiers[i].id;
|
||||
const p2 = qualifiers[roundSize - 1 - i].id;
|
||||
await TournamentMatch.create({ tournamentId, round: rn, player1Id: p1, player2Id: p2 });
|
||||
}
|
||||
// Platzhalter für nächste Runde
|
||||
qualifiers.splice(roundSize / 2);
|
||||
roundSize = roundSize / 2;
|
||||
}
|
||||
throw new Error('Match not found');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,107 +1,127 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="tournaments-view">
|
||||
<h2>Turnier</h2>
|
||||
<div>
|
||||
<div>
|
||||
<h3>Datum</h3>
|
||||
<div>
|
||||
<select v-model="selectedDate">
|
||||
<option value="new">Neues Turnier</option>
|
||||
<option v-for="date in dates" :key="date.id" :value="date.id">
|
||||
{{ new Date(date.date).toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
}) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<template v-if="selectedDate === 'new'">
|
||||
<div>
|
||||
<input type="date" v-model="newDate" />
|
||||
<button @click="createTournament">Erstellen</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>
|
||||
<h3>Turnier</h3>
|
||||
<div>
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox" v-model="isGroupTournament">
|
||||
Spielen in Gruppen
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Teilnehmer</h4>
|
||||
<ul>
|
||||
<li v-for="participant in participants" :key="participant.id">
|
||||
{{ participant.member.firstName }} {{ participant.member.lastName }}
|
||||
</li>
|
||||
</ul>
|
||||
<select v-model="selectedMember">
|
||||
<option v-for="member in clubMembers" :key="member.id" :value="member.id">
|
||||
{{ member.firstName }} {{ member.lastName }}
|
||||
</option>
|
||||
</select>
|
||||
<button type="button" @click="addParticipant">Hinzufügen</button>
|
||||
</div>
|
||||
<div v-if="isGroupTournament && participants.length > 1">
|
||||
<label>
|
||||
Anzahl Gruppen:
|
||||
<input type="number" v-model="numberOfGroups">
|
||||
</label>
|
||||
<button @click="createGroups">Gruppen erstellen</button>
|
||||
<button @click="randomizeGroups">Zufällig verteilen</button>
|
||||
</div>
|
||||
<div v-if="groups && groups.length > 0">
|
||||
<h4>Gruppen</h4>
|
||||
<ul class="groupoverview">
|
||||
<li v-for="group in groups" :key="group.groupId">
|
||||
<h4>Gruppe {{ group.groupId }}</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Spielername</th>
|
||||
<th>Bilanz</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="participant in group.participants" :key="participant.id">
|
||||
<td>{{ participant.name }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Begegnung</th>
|
||||
<th>Sätze</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="match in matches" :key="match.id">
|
||||
<td>{{ match.groupId ? "Gr " + match.groupId : match.round }}</td>
|
||||
<td>{{ getPlayerName(match.player1) }} - {{ getPlayerName(match.player2) }}</td>
|
||||
<td v-for="result in match.tournamentResults">{{ result.pointsPlayer1 }}:{{ result.pointsPlayer2 }}</td>
|
||||
<td><input size="5" type="text" v-model="match.result" @keyup.enter="saveMatchResult(match, match.tournamentResults.length + 1, match.result)" /></td>
|
||||
<td v-if="match.isFinished">{{ match.result ?? '0:0' }}</td>
|
||||
<td v-else><button @click="finishMatch(match)">Abschließen</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Datumsauswahl / Neues Turnier -->
|
||||
<div class="tournament-config">
|
||||
<h3>Datum</h3>
|
||||
<select v-model="selectedDate">
|
||||
<option value="new">Neues Turnier</option>
|
||||
<option v-for="date in dates" :key="date.id" :value="date.id">
|
||||
{{ new Date(date.date).toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
}) }}
|
||||
</option>
|
||||
</select>
|
||||
<div v-if="selectedDate === 'new'" class="new-tournament">
|
||||
<input type="date" v-model="newDate" />
|
||||
<button @click="createTournament">Erstellen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Konfiguration & Gruppenphase -->
|
||||
<div v-if="selectedDate !== 'new'" class="tournament-setup">
|
||||
<label>
|
||||
<input type="checkbox" v-model="isGroupTournament" />
|
||||
Spielen in Gruppen
|
||||
</label>
|
||||
|
||||
<section class="participants">
|
||||
<h4>Teilnehmer</h4>
|
||||
<ul>
|
||||
<li v-for="participant in participants" :key="participant.id">
|
||||
{{ participant.member.firstName }}
|
||||
{{ participant.member.lastName }}
|
||||
</li>
|
||||
</ul>
|
||||
<select v-model="selectedMember">
|
||||
<option v-for="member in clubMembers" :key="member.id" :value="member.id">
|
||||
{{ member.firstName }}
|
||||
{{ member.lastName }}
|
||||
</option>
|
||||
</select>
|
||||
<button @click="addParticipant">Hinzufügen</button>
|
||||
</section>
|
||||
|
||||
<section v-if="isGroupTournament && participants.length > 1" class="group-controls">
|
||||
<label>
|
||||
Anzahl Gruppen:
|
||||
<input type="number" v-model.number="numberOfGroups" min="1" />
|
||||
</label>
|
||||
<button @click="createGroups">Gruppen erstellen</button>
|
||||
<button @click="randomizeGroups">Zufällig verteilen</button>
|
||||
</section>
|
||||
|
||||
<section v-if="groups.length" class="groups-overview">
|
||||
<h3>Gruppenübersicht</h3>
|
||||
<div v-for="group in groups" :key="group.groupId" class="group-table">
|
||||
<h4>Gruppe {{ group.groupId }}</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platz</th>
|
||||
<th>Spieler</th>
|
||||
<th>Punkte</th>
|
||||
<th>Satz</th>
|
||||
<th>Diff</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="pl in groupRankings[group.groupId]" :key="pl.id">
|
||||
<td>{{ pl.position }}.</td>
|
||||
<td>{{ pl.name }}</td>
|
||||
<td>{{ pl.points }}</td>
|
||||
<td>{{ pl.setsWon }}:{{ pl.setsLost }}</td>
|
||||
<td>
|
||||
{{ pl.setDiff >= 0 ? '+' + pl.setDiff : pl.setDiff }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- K.o.-Runde starten -->
|
||||
<div v-if="participants.length > 1 && !showKnockout" class="ko-start">
|
||||
<button @click="startKnockout">
|
||||
K.o.-Runde starten
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- K.o.-Runde anzeigen -->
|
||||
<section v-if="showKnockout" class="ko-round">
|
||||
<h4>K.-o.-Runde</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Runde</th>
|
||||
<th>Begegnung</th>
|
||||
<th>Ergebnis</th>
|
||||
<th>Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="m in knockoutMatches" :key="m.id">
|
||||
<td>{{ m.round }}</td>
|
||||
<td>
|
||||
{{ getPlayerName(m.player1) }} –
|
||||
{{ getPlayerName(m.player2) }}
|
||||
</td>
|
||||
<td>{{ m.result || '-' }}</td>
|
||||
<td v-if="!m.isFinished">
|
||||
<input v-model="m.resultInput" placeholder="z.B. 11:4, 4:11, 4, -4"
|
||||
@keyup.enter="saveMatchResult(m, m.resultInput)" />
|
||||
<button @click="finishMatch(m)">
|
||||
Fertig
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -111,60 +131,77 @@ import apiClient from '../apiClient';
|
||||
|
||||
export default {
|
||||
name: 'TournamentsView',
|
||||
computed: {
|
||||
...mapGetters(['isAuthenticated', 'currentClub', 'clubs']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedDate: 'new',
|
||||
newDate: '',
|
||||
dates: [],
|
||||
|
||||
participants: [],
|
||||
selectedMember: null,
|
||||
clubMembers: [],
|
||||
|
||||
numberOfGroups: 1,
|
||||
isGroupTournament: false,
|
||||
groups: [],
|
||||
|
||||
matches: [],
|
||||
showKnockout: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isAuthenticated', 'currentClub']),
|
||||
knockoutMatches() {
|
||||
return this.matches.filter(m => m.round !== 'group');
|
||||
},
|
||||
groupRankings() {
|
||||
const byGroup = {};
|
||||
this.groups.forEach(g => {
|
||||
byGroup[g.groupId] = g.participants.map(p => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
points: 0,
|
||||
setsWon: 0,
|
||||
setsLost: 0,
|
||||
setDiff: 0,
|
||||
}));
|
||||
});
|
||||
this.matches.forEach(m => {
|
||||
if (!m.isFinished || m.round !== 'group') return;
|
||||
const [s1, s2] = m.result.split(':').map(n => +n);
|
||||
const arr = byGroup[m.groupId];
|
||||
if (!arr) return;
|
||||
const e1 = arr.find(x => x.id === m.player1.id);
|
||||
const e2 = arr.find(x => x.id === m.player2.id);
|
||||
if (!e1 || !e2) return;
|
||||
if (s1 > s2) e1.points += 2;
|
||||
else if (s2 > s1) e2.points += 2;
|
||||
e1.setsWon += s1; e1.setsLost += s2;
|
||||
e2.setsWon += s2; e2.setsLost += s1;
|
||||
});
|
||||
const rankings = {};
|
||||
Object.entries(byGroup).forEach(([gid, arr]) => {
|
||||
arr.forEach(p => p.setDiff = p.setsWon - p.setsLost);
|
||||
arr.sort((a, b) => {
|
||||
if (b.points !== a.points) return b.points - a.points;
|
||||
if (b.setDiff !== a.setDiff) return b.setDiff - a.setDiff;
|
||||
if (b.setsWon !== a.setsWon) return b.setsWon - a.setsWon;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
rankings[gid] = arr.map((p, i) => ({
|
||||
...p, position: i + 1
|
||||
}));
|
||||
});
|
||||
return rankings;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectedDate: async function (newVal) {
|
||||
if (newVal !== 'new') {
|
||||
try {
|
||||
const groupResponse = await apiClient.get(`/tournament/${this.currentClub}/${newVal}`);
|
||||
this.isGroupTournament = groupResponse.data.type === 'groups';
|
||||
const participantsResponse = await apiClient.post('/tournament/participants', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: newVal,
|
||||
});
|
||||
this.participants = participantsResponse.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
await this.fetchGroups();
|
||||
selectedDate: {
|
||||
immediate: true,
|
||||
handler: async function (val) {
|
||||
if (val === 'new') return;
|
||||
await this.loadTournamentData();
|
||||
}
|
||||
},
|
||||
isGroupTournament: async function (newVal) {
|
||||
if (newVal) {
|
||||
this.numberOfGroups = 2;
|
||||
} else {
|
||||
this.numberOfGroups = 1;
|
||||
}
|
||||
await apiClient.post('/tournament/modus', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
type: newVal ? 'groups' : 'bestOf',
|
||||
numberOfGroups: this.numberOfGroups,
|
||||
});
|
||||
},
|
||||
numberOfGroups: async function (newVal) {
|
||||
await apiClient.post('/tournament/modus', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
type: this.isGroupTournament ? 'groups' : 'bestOf',
|
||||
numberOfGroups: newVal,
|
||||
});
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
@@ -172,132 +209,163 @@ export default {
|
||||
this.$router.push('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const responseDates = await apiClient.get(`/tournament/${this.currentClub}`);
|
||||
this.dates = responseDates.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching tournaments:', error);
|
||||
}
|
||||
|
||||
try {
|
||||
const responseMembers = await apiClient.get(`/clubmembers/get/${this.currentClub}/false`);
|
||||
this.clubMembers = responseMembers.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching club members:', error);
|
||||
}
|
||||
// Turniere und Mitglieder laden
|
||||
const d = await apiClient.get(`/tournament/${this.currentClub}`);
|
||||
this.dates = d.data;
|
||||
const m = await apiClient.get(
|
||||
`/clubmembers/get/${this.currentClub}/false`
|
||||
);
|
||||
this.clubMembers = m.data;
|
||||
},
|
||||
methods: {
|
||||
async loadTournamentData() {
|
||||
// 1) Turnier‐Metadaten holen (Typ + Anzahl Gruppen)
|
||||
const tRes = await apiClient.get(
|
||||
`/tournament/${this.currentClub}/${this.selectedDate}`
|
||||
);
|
||||
const tournament = tRes.data;
|
||||
this.isGroupTournament = tournament.type === 'groups';
|
||||
this.numberOfGroups = tournament.numberOfGroups;
|
||||
|
||||
// 2) Teilnehmer
|
||||
const pRes = await apiClient.post('/tournament/participants', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
});
|
||||
this.participants = pRes.data;
|
||||
|
||||
// 3) Gruppen (mit Teilnehmern)
|
||||
const gRes = await apiClient.get('/tournament/groups', {
|
||||
params: {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
}
|
||||
});
|
||||
this.groups = gRes.data;
|
||||
|
||||
// 4) Alle Matches
|
||||
const mRes = await apiClient.get(
|
||||
`/tournament/matches/${this.currentClub}/${this.selectedDate}`
|
||||
);
|
||||
this.matches = mRes.data;
|
||||
|
||||
// 5) Steuere K.o.-Anzeige
|
||||
this.showKnockout = this.matches.some(m => m.round !== 'group');
|
||||
},
|
||||
|
||||
getPlayerName(p) {
|
||||
return p.member.firstName + ' ' + p.member.lastName;
|
||||
},
|
||||
|
||||
async createTournament() {
|
||||
try {
|
||||
const response = await apiClient.post('/tournament', {
|
||||
clubId: this.currentClub,
|
||||
name: this.newDate,
|
||||
date: this.newDate,
|
||||
});
|
||||
this.dates = response.data;
|
||||
this.newDate = '';
|
||||
} catch (error) {
|
||||
console.error('Error creating tournament:', error);
|
||||
}
|
||||
const r = await apiClient.post('/tournament', {
|
||||
clubId: this.currentClub,
|
||||
tournamentName: this.newDate,
|
||||
date: this.newDate
|
||||
});
|
||||
this.dates = r.data;
|
||||
this.selectedDate = this.dates[this.dates.length - 1].id;
|
||||
this.newDate = '';
|
||||
},
|
||||
|
||||
async addParticipant() {
|
||||
try {
|
||||
const response = await apiClient.post('/tournament/participant', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
participant: this.selectedMember,
|
||||
});
|
||||
this.participants = response.data;
|
||||
} catch (error) {
|
||||
console.error('Error adding participant:', error);
|
||||
}
|
||||
const r = await apiClient.post('/tournament/participant', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
participant: this.selectedMember
|
||||
});
|
||||
this.participants = r.data;
|
||||
},
|
||||
|
||||
async createGroups() {
|
||||
await apiClient.put('/tournament/groups', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
tournamentId: this.selectedDate
|
||||
});
|
||||
await this.fetchGroups();
|
||||
await this.loadTournamentData();
|
||||
},
|
||||
|
||||
async randomizeGroups() {
|
||||
try {
|
||||
const response = await apiClient.post('/tournament/groups', {
|
||||
const r = await apiClient.post('/tournament/groups', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
tournamentId: this.selectedDate
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error randomizing groups:', error);
|
||||
this.participants = r.data;
|
||||
} catch (err) {
|
||||
alert('Fehler beim Zufällig‑Verteilen:\n' +
|
||||
(err.response?.data?.error || err.message));
|
||||
}
|
||||
await this.fetchGroups();
|
||||
await this.loadTournamentData();
|
||||
},
|
||||
async fetchGroups() {
|
||||
try {
|
||||
const response = await apiClient.get('/tournament/groups', {
|
||||
params: {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
}
|
||||
});
|
||||
this.groups = response.data;
|
||||
const matchesResponse = await apiClient.get(`/tournament/matches/${this.currentClub}/${this.selectedDate}`);
|
||||
this.matches = matchesResponse.data;
|
||||
console.log(this.matches);
|
||||
} catch (error) {
|
||||
console.error('Error fetching groups:', error);
|
||||
}
|
||||
},
|
||||
getPlayerName(player) {
|
||||
return player.member.firstName + ' ' + player.member.lastName;
|
||||
},
|
||||
async saveMatchResult(match, set, result) {
|
||||
try {
|
||||
await apiClient.post('/tournament/match/result', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
matchId: match.id,
|
||||
set,
|
||||
result: result,
|
||||
});
|
||||
this.fetchGroups();
|
||||
} catch (error) {
|
||||
console.error('Error saving match result:', error);
|
||||
|
||||
async saveMatchResult(match, result) {
|
||||
// wenn kein ':' dabei, ergänzen
|
||||
if (result.indexOf(':') === -1) {
|
||||
result = result.indexOf('-') > -1
|
||||
? '11:' + result
|
||||
: (result * -1) + ':11';
|
||||
}
|
||||
await apiClient.post('/tournament/match/result', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
matchId: match.id,
|
||||
set: (match.tournamentResults?.length || 0) + 1,
|
||||
result
|
||||
});
|
||||
await this.loadTournamentData();
|
||||
},
|
||||
|
||||
async finishMatch(match) {
|
||||
try {
|
||||
await apiClient.post('/tournament/match/finish', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
matchId: match.id,
|
||||
});
|
||||
this.fetchGroups();
|
||||
} catch (error) {
|
||||
console.error('Error finishing match:', error);
|
||||
}
|
||||
await apiClient.post('/tournament/match/finish', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
matchId: match.id
|
||||
});
|
||||
await this.loadTournamentData();
|
||||
},
|
||||
},
|
||||
|
||||
async startKnockout() {
|
||||
await apiClient.post('/tournament/knockout', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
});
|
||||
await this.loadTournamentData();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tournaments {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
.tournaments-view {
|
||||
padding: 1rem;
|
||||
}
|
||||
.groupoverview {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
justify-content: left;
|
||||
padding: 0;
|
||||
|
||||
.participants,
|
||||
.group-controls,
|
||||
.groups-overview,
|
||||
.ko-round,
|
||||
.ko-start {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.groupoverview li {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.group-table {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 0.5em;
|
||||
border: 1px solid #ccc;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user