feat(tournament): add mini championship functionality and enhance tournament class handling
- Introduced addMiniChampionship method in tournamentService to create tournaments with predefined classes for mini championships. - Updated getTournaments method to filter tournaments based on type, including support for mini championships. - Enhanced TournamentClass model to include maxBirthYear for age class restrictions. - Modified tournamentController and tournamentRoutes to support new mini championship endpoint. - Updated frontend components to manage mini championship creation and display, including localization for new terms.
This commit is contained in:
@@ -40,12 +40,13 @@ export const resetPool = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 1. Alle Turniere eines Vereins
|
||||
// 1. Alle Turniere eines Vereins (query: type = 'internal' | 'external' | 'mini')
|
||||
export const getTournaments = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
const type = req.query.type || null;
|
||||
try {
|
||||
const tournaments = await tournamentService.getTournaments(token, clubId);
|
||||
const tournaments = await tournamentService.getTournaments(token, clubId, type);
|
||||
res.status(200).json(tournaments);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -64,7 +65,6 @@ export const addTournament = async (req, res) => {
|
||||
const { clubId, tournamentName, date, winningSets, allowsExternal } = req.body;
|
||||
try {
|
||||
const tournament = await tournamentService.addTournament(token, clubId, tournamentName, date, winningSets, allowsExternal);
|
||||
// Emit Socket-Event
|
||||
if (clubId && tournament && tournament.id) {
|
||||
emitTournamentChanged(clubId, tournament.id);
|
||||
}
|
||||
@@ -75,6 +75,22 @@ export const addTournament = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Minimeisterschaft anlegen (Turnier + 6 Klassen)
|
||||
export const addMiniChampionship = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentName, date, year, winningSets } = req.body;
|
||||
try {
|
||||
const tournament = await tournamentService.addMiniChampionship(token, clubId, tournamentName, date, year, winningSets);
|
||||
if (clubId && tournament && tournament.id) {
|
||||
emitTournamentChanged(clubId, tournament.id);
|
||||
}
|
||||
res.status(201).json(tournament);
|
||||
} catch (error) {
|
||||
console.error('[addMiniChampionship] Error:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 3. Teilnehmer hinzufügen - klassengebunden
|
||||
export const addParticipant = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
@@ -599,9 +615,9 @@ export const getTournamentClasses = async (req, res) => {
|
||||
export const addTournamentClass = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.params;
|
||||
const { name, isDoubles, gender, minBirthYear } = req.body;
|
||||
const { name, isDoubles, gender, minBirthYear, maxBirthYear } = req.body;
|
||||
try {
|
||||
const tournamentClass = await tournamentService.addTournamentClass(token, clubId, tournamentId, name, isDoubles, gender, minBirthYear);
|
||||
const tournamentClass = await tournamentService.addTournamentClass(token, clubId, tournamentId, name, isDoubles, gender, minBirthYear, maxBirthYear);
|
||||
emitTournamentChanged(clubId, tournamentId);
|
||||
res.status(200).json(tournamentClass);
|
||||
} catch (error) {
|
||||
@@ -613,11 +629,9 @@ export const addTournamentClass = async (req, res) => {
|
||||
export const updateTournamentClass = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId, classId } = req.params;
|
||||
const { name, sortOrder, isDoubles, gender, minBirthYear } = req.body;
|
||||
const { name, sortOrder, isDoubles, gender, minBirthYear, maxBirthYear } = req.body;
|
||||
try {
|
||||
console.log('[updateTournamentClass] Request body:', { name, sortOrder, isDoubles, gender, minBirthYear });
|
||||
const tournamentClass = await tournamentService.updateTournamentClass(token, clubId, tournamentId, classId, name, sortOrder, isDoubles, gender, minBirthYear);
|
||||
console.log('[updateTournamentClass] Updated class:', JSON.stringify(tournamentClass.toJSON(), null, 2));
|
||||
const tournamentClass = await tournamentService.updateTournamentClass(token, clubId, tournamentId, classId, name, sortOrder, isDoubles, gender, minBirthYear, maxBirthYear);
|
||||
emitTournamentChanged(clubId, tournamentId);
|
||||
res.status(200).json(tournamentClass);
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Minimeisterschaften: Turnier-Jahr und Alters-Obergrenze pro Klasse
|
||||
-- tournament.mini_championship_year: Jahr der Minimeisterschaft (z.B. 2025); nur gesetzt bei Minimeisterschaften
|
||||
-- tournament_class.max_birth_year: Geboren im Jahr X oder früher (<=); für Altersklassen 12/10
|
||||
|
||||
ALTER TABLE `tournament`
|
||||
ADD COLUMN `mini_championship_year` INT NULL AFTER `allows_external`;
|
||||
|
||||
ALTER TABLE `tournament_class`
|
||||
ADD COLUMN `max_birth_year` INT NULL AFTER `min_birth_year`;
|
||||
@@ -39,6 +39,12 @@ const Tournament = sequelize.define('Tournament', {
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
miniChampionshipYear: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'mini_championship_year',
|
||||
comment: 'Jahr der Minimeisterschaft; nur gesetzt bei Minimeisterschaften'
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'tournament',
|
||||
|
||||
@@ -44,6 +44,13 @@ const TournamentClass = sequelize.define('TournamentClass', {
|
||||
defaultValue: null,
|
||||
field: 'min_birth_year',
|
||||
comment: 'Geboren im Jahr X oder später (>=)'
|
||||
},
|
||||
maxBirthYear: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
field: 'max_birth_year',
|
||||
comment: 'Geboren im Jahr X oder früher (<=); für Altersklassen 12/10'
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
|
||||
@@ -2,6 +2,7 @@ import express from 'express';
|
||||
import {
|
||||
getTournaments,
|
||||
addTournament,
|
||||
addMiniChampionship,
|
||||
updateTournament,
|
||||
addParticipant,
|
||||
getParticipants,
|
||||
@@ -105,6 +106,9 @@ router.get('/stages', authenticate, getStages);
|
||||
router.put('/stages', authenticate, upsertStages);
|
||||
router.post('/stages/advance', authenticate, advanceStage);
|
||||
|
||||
// Minimeisterschaft anlegen (vor :clubId, damit 'mini' nicht als clubId matcht)
|
||||
router.post('/mini', authenticate, addMiniChampionship);
|
||||
|
||||
// Muss NACH allen festen Pfaden stehen, sonst matcht z.B. '/stages' als clubId='stages'
|
||||
router.get('/:clubId', authenticate, getTournaments);
|
||||
router.post('/', authenticate, addTournament);
|
||||
|
||||
@@ -678,13 +678,19 @@ class TournamentService {
|
||||
};
|
||||
}
|
||||
|
||||
// 1. Turniere listen
|
||||
async getTournaments(userToken, clubId) {
|
||||
// 1. Turniere listen (type: 'internal' | 'external' | 'mini' optional – bei 'mini' nur Minimeisterschaften)
|
||||
async getTournaments(userToken, clubId, type = null) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const where = { clubId };
|
||||
if (type === 'mini') {
|
||||
where.miniChampionshipYear = { [Op.ne]: null };
|
||||
} else if (type === 'internal' || type === 'external') {
|
||||
where.miniChampionshipYear = { [Op.is]: null };
|
||||
}
|
||||
const tournaments = await Tournament.findAll({
|
||||
where: { clubId },
|
||||
where,
|
||||
order: [['date', 'DESC']],
|
||||
attributes: ['id', 'name', 'date', 'allowsExternal']
|
||||
attributes: ['id', 'name', 'date', 'allowsExternal', 'miniChampionshipYear']
|
||||
});
|
||||
return JSON.parse(JSON.stringify(tournaments));
|
||||
}
|
||||
@@ -708,6 +714,54 @@ class TournamentService {
|
||||
return JSON.parse(JSON.stringify(t));
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimeisterschaft anlegen: Turnier + 6 vorkonfigurierte Klassen (Jungen/Mädchen 12, 10, 8).
|
||||
* Jahr Y: 12 = in Y 11 oder 12 Jahre (Geburtsjahr Y-12 oder Y-11), 10 = 9/10 (Y-10, Y-9), 8 = 8 oder jünger (≥ Y-8).
|
||||
*/
|
||||
async addMiniChampionship(userToken, clubId, tournamentName, date, year, winningSets = 3) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const existing = await Tournament.findOne({ where: { clubId, date } });
|
||||
if (existing) {
|
||||
throw new Error('Ein Turnier mit diesem Datum existiert bereits');
|
||||
}
|
||||
const Y = Number(year);
|
||||
if (!Number.isFinite(Y) || Y < 2000 || Y > 2100) {
|
||||
throw new Error('Ungültiges Jahr für die Minimeisterschaft');
|
||||
}
|
||||
const t = await Tournament.create({
|
||||
name: tournamentName,
|
||||
date,
|
||||
clubId: +clubId,
|
||||
bestOfEndroundSize: 0,
|
||||
type: '',
|
||||
winningSets: winningSets || 3,
|
||||
allowsExternal: false,
|
||||
miniChampionshipYear: Y
|
||||
});
|
||||
const classes = [
|
||||
{ name: 'Jungen 12', gender: 'male', minBirthYear: Y - 12, maxBirthYear: Y - 11 },
|
||||
{ name: 'Jungen 10', gender: 'male', minBirthYear: Y - 10, maxBirthYear: Y - 9 },
|
||||
{ name: 'Jungen 8', gender: 'male', minBirthYear: Y - 8, maxBirthYear: null },
|
||||
{ name: 'Mädchen 12', gender: 'female', minBirthYear: Y - 12, maxBirthYear: Y - 11 },
|
||||
{ name: 'Mädchen 10', gender: 'female', minBirthYear: Y - 10, maxBirthYear: Y - 9 },
|
||||
{ name: 'Mädchen 8', gender: 'female', minBirthYear: Y - 8, maxBirthYear: null },
|
||||
];
|
||||
for (let i = 0; i < classes.length; i++) {
|
||||
await TournamentClass.create({
|
||||
tournamentId: t.id,
|
||||
name: classes[i].name,
|
||||
sortOrder: i + 1,
|
||||
isDoubles: false,
|
||||
gender: classes[i].gender,
|
||||
minBirthYear: classes[i].minBirthYear,
|
||||
maxBirthYear: classes[i].maxBirthYear
|
||||
});
|
||||
}
|
||||
return JSON.parse(JSON.stringify(await Tournament.findByPk(t.id, {
|
||||
attributes: ['id', 'name', 'date', 'allowsExternal', 'miniChampionshipYear']
|
||||
})));
|
||||
}
|
||||
|
||||
// 3. Teilnehmer hinzufügen (kein Duplikat) - klassengebunden
|
||||
async addParticipant(userToken, clubId, classId, participantId, tournamentId = null) {
|
||||
await checkAccess(userToken, clubId);
|
||||
@@ -756,9 +810,8 @@ class TournamentService {
|
||||
}
|
||||
}
|
||||
|
||||
// Validierung: Geburtsjahr muss zur Klasse passen (geboren im Jahr X oder später, also >=)
|
||||
if (tournamentClass.minBirthYear && member.birthDate) {
|
||||
// Parse das Geburtsdatum (Format: YYYY-MM-DD oder DD.MM.YYYY)
|
||||
// Validierung: Geburtsjahr muss zur Klasse passen (minBirthYear <= birthYear <= maxBirthYear)
|
||||
if (member.birthDate) {
|
||||
let birthYear = null;
|
||||
if (member.birthDate.includes('-')) {
|
||||
birthYear = parseInt(member.birthDate.split('-')[0]);
|
||||
@@ -768,10 +821,13 @@ class TournamentService {
|
||||
birthYear = parseInt(parts[2]);
|
||||
}
|
||||
}
|
||||
if (birthYear && !isNaN(birthYear)) {
|
||||
if (birthYear < tournamentClass.minBirthYear) {
|
||||
if (birthYear != null && !isNaN(birthYear)) {
|
||||
if (tournamentClass.minBirthYear != null && birthYear < tournamentClass.minBirthYear) {
|
||||
throw new Error(`Dieser Teilnehmer ist zu alt für diese Klasse. Erlaubt: geboren ${tournamentClass.minBirthYear} oder später`);
|
||||
}
|
||||
if (tournamentClass.maxBirthYear != null && birthYear > tournamentClass.maxBirthYear) {
|
||||
throw new Error(`Dieser Teilnehmer ist zu jung für diese Klasse. Erlaubt: geboren ${tournamentClass.maxBirthYear} oder früher`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3258,11 +3314,13 @@ class TournamentService {
|
||||
}
|
||||
}
|
||||
|
||||
if (birthYear && !isNaN(birthYear)) {
|
||||
// Geboren im Jahr X oder später bedeutet: birthYear >= minBirthYear
|
||||
if (birthYear < tournamentClass.minBirthYear) {
|
||||
if (birthYear != null && !isNaN(birthYear)) {
|
||||
if (tournamentClass.minBirthYear != null && birthYear < tournamentClass.minBirthYear) {
|
||||
throw new Error(`Dieser Teilnehmer ist zu alt für diese Klasse. Erlaubt: geboren ${tournamentClass.minBirthYear} oder später`);
|
||||
}
|
||||
if (tournamentClass.maxBirthYear != null && birthYear > tournamentClass.maxBirthYear) {
|
||||
throw new Error(`Dieser Teilnehmer ist zu jung für diese Klasse. Erlaubt: geboren ${tournamentClass.maxBirthYear} oder früher`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3337,13 +3395,12 @@ class TournamentService {
|
||||
});
|
||||
}
|
||||
|
||||
async addTournamentClass(userToken, clubId, tournamentId, name, isDoubles = false, gender = null, minBirthYear = null) {
|
||||
async addTournamentClass(userToken, clubId, tournamentId, name, isDoubles = false, gender = null, minBirthYear = null, maxBirthYear = null) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Turnier nicht gefunden');
|
||||
}
|
||||
// Finde die höchste sortOrder
|
||||
const maxSortOrder = await TournamentClass.max('sortOrder', {
|
||||
where: { tournamentId }
|
||||
}) || 0;
|
||||
@@ -3353,11 +3410,12 @@ class TournamentService {
|
||||
sortOrder: maxSortOrder + 1,
|
||||
isDoubles: isDoubles || false,
|
||||
gender: gender || null,
|
||||
minBirthYear: minBirthYear || null
|
||||
minBirthYear: minBirthYear ?? null,
|
||||
maxBirthYear: maxBirthYear ?? null
|
||||
});
|
||||
}
|
||||
|
||||
async updateTournamentClass(userToken, clubId, tournamentId, classId, name, sortOrder, isDoubles, gender, minBirthYear) {
|
||||
async updateTournamentClass(userToken, clubId, tournamentId, classId, name, sortOrder, isDoubles, gender, minBirthYear, maxBirthYear) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
@@ -3369,37 +3427,15 @@ class TournamentService {
|
||||
if (!tournamentClass) {
|
||||
throw new Error('Klasse nicht gefunden');
|
||||
}
|
||||
console.log('[updateTournamentClass] Before update:', {
|
||||
id: tournamentClass.id,
|
||||
name: tournamentClass.name,
|
||||
isDoubles: tournamentClass.isDoubles,
|
||||
gender: tournamentClass.gender,
|
||||
minBirthYear: tournamentClass.minBirthYear
|
||||
});
|
||||
console.log('[updateTournamentClass] New values:', { name, sortOrder, isDoubles, gender, minBirthYear });
|
||||
|
||||
// Verwende update() statt direkter Zuweisung für bessere Kontrolle
|
||||
const updateData = {};
|
||||
if (name !== undefined) updateData.name = name;
|
||||
if (sortOrder !== undefined) updateData.sortOrder = sortOrder;
|
||||
if (isDoubles !== undefined) updateData.isDoubles = isDoubles;
|
||||
if (gender !== undefined) updateData.gender = gender;
|
||||
if (minBirthYear !== undefined) updateData.minBirthYear = minBirthYear;
|
||||
|
||||
console.log('[updateTournamentClass] Update data:', updateData);
|
||||
|
||||
if (maxBirthYear !== undefined) updateData.maxBirthYear = maxBirthYear;
|
||||
await tournamentClass.update(updateData);
|
||||
|
||||
// Lade die aktualisierte Instanz neu, um sicherzustellen, dass wir die aktuellen DB-Werte haben
|
||||
await tournamentClass.reload();
|
||||
|
||||
console.log('[updateTournamentClass] After update and reload:', {
|
||||
id: tournamentClass.id,
|
||||
name: tournamentClass.name,
|
||||
isDoubles: tournamentClass.isDoubles,
|
||||
gender: tournamentClass.gender,
|
||||
minBirthYear: tournamentClass.minBirthYear
|
||||
});
|
||||
return tournamentClass;
|
||||
}
|
||||
|
||||
|
||||
@@ -461,45 +461,32 @@ export default {
|
||||
return this.clubMembers;
|
||||
}
|
||||
|
||||
// Filtere basierend auf Geschlechtsbeschränkung und Geburtsjahr
|
||||
// Filtere basierend auf Geschlecht und Geburtsjahr (minBirthYear/maxBirthYear)
|
||||
const classGender = selectedClass.gender;
|
||||
const minBirthYear = selectedClass.minBirthYear;
|
||||
const maxBirthYear = selectedClass.maxBirthYear;
|
||||
|
||||
return this.clubMembers.filter(member => {
|
||||
// Filtere nach Geschlecht
|
||||
const memberGender = member.gender || 'unknown';
|
||||
const memberGender = (member.gender || 'unknown').toLowerCase();
|
||||
let genderMatch = true;
|
||||
|
||||
if (classGender) {
|
||||
// Wenn die Klasse "mixed" ist, erlaube alle Geschlechter
|
||||
if (classGender === 'mixed') {
|
||||
genderMatch = true;
|
||||
} else if (classGender === 'male') {
|
||||
genderMatch = memberGender === 'male';
|
||||
} else if (classGender === 'female') {
|
||||
genderMatch = memberGender === 'female';
|
||||
}
|
||||
if (classGender && classGender !== 'mixed') {
|
||||
if (classGender === 'male') genderMatch = memberGender === 'male';
|
||||
else if (classGender === 'female') genderMatch = memberGender === 'female';
|
||||
}
|
||||
|
||||
// Filtere nach Geburtsjahr (geboren im Jahr X oder später, also >=)
|
||||
let birthYearMatch = true;
|
||||
if (minBirthYear && member.birthDate) {
|
||||
// Parse das Geburtsdatum (Format: YYYY-MM-DD oder DD.MM.YYYY)
|
||||
if (member.birthDate) {
|
||||
let birthYear = null;
|
||||
if (member.birthDate.includes('-')) {
|
||||
// Format: YYYY-MM-DD
|
||||
birthYear = parseInt(member.birthDate.split('-')[0]);
|
||||
} else if (member.birthDate.includes('.')) {
|
||||
// Format: DD.MM.YYYY
|
||||
const parts = member.birthDate.split('.');
|
||||
if (parts.length === 3) {
|
||||
birthYear = parseInt(parts[2]);
|
||||
}
|
||||
const bd = String(member.birthDate);
|
||||
if (bd.includes('-')) {
|
||||
birthYear = parseInt(bd.split('-')[0], 10);
|
||||
} else if (bd.includes('.')) {
|
||||
const parts = bd.split('.');
|
||||
if (parts.length >= 3) birthYear = parseInt(parts[2], 10);
|
||||
}
|
||||
|
||||
if (birthYear && !isNaN(birthYear)) {
|
||||
// Geboren im Jahr X oder später bedeutet: birthYear >= minBirthYear
|
||||
birthYearMatch = birthYear >= minBirthYear;
|
||||
if (birthYear != null && !isNaN(birthYear)) {
|
||||
if (minBirthYear != null && birthYear < minBirthYear) birthYearMatch = false;
|
||||
if (maxBirthYear != null && birthYear > maxBirthYear) birthYearMatch = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -561,6 +561,10 @@
|
||||
"tournaments": {
|
||||
"internalTournaments": "Interne Turniere",
|
||||
"openTournaments": "Offene Turniere",
|
||||
"miniChampionships": "Minimeisterschaften",
|
||||
"newMiniChampionship": "Neue Minimeisterschaft",
|
||||
"miniChampionshipYear": "Jahr (Altersstufen)",
|
||||
"miniChampionshipYearHint": "12 = in diesem Jahr 11 oder 12 Jahre, 10 = 9 oder 10, 8 = 8 oder jünger",
|
||||
"tournamentParticipations": "Turnierteilnahmen",
|
||||
"date": "Datum",
|
||||
"newTournament": "Neues Turnier",
|
||||
|
||||
@@ -22,19 +22,40 @@
|
||||
</option>
|
||||
</select>
|
||||
<div v-if="selectedDate === 'new'" class="new-tournament">
|
||||
<label>
|
||||
{{ $t('tournaments.name') }}:
|
||||
<input type="text" v-model="newTournamentName" :placeholder="$t('tournaments.tournamentName')" />
|
||||
</label>
|
||||
<label>
|
||||
{{ $t('tournaments.date') }}:
|
||||
<input type="date" v-model="newDate" />
|
||||
</label>
|
||||
<label>
|
||||
{{ $t('tournaments.winningSets') }}:
|
||||
<input type="number" v-model.number="newWinningSets" min="1" />
|
||||
</label>
|
||||
<button @click="createTournament">{{ $t('tournaments.create') }}</button>
|
||||
<template v-if="isMiniChampionship">
|
||||
<label>
|
||||
{{ $t('tournaments.name') }}:
|
||||
<input type="text" v-model="newTournamentName" :placeholder="$t('tournaments.tournamentName')" />
|
||||
</label>
|
||||
<label>
|
||||
{{ $t('tournaments.date') }}:
|
||||
<input type="date" v-model="newDate" />
|
||||
</label>
|
||||
<label :title="$t('tournaments.miniChampionshipYearHint')">
|
||||
{{ $t('tournaments.miniChampionshipYear') }}:
|
||||
<input type="number" v-model.number="newMiniYear" min="2000" max="2100" step="1" />
|
||||
</label>
|
||||
<label>
|
||||
{{ $t('tournaments.winningSets') }}:
|
||||
<input type="number" v-model.number="newWinningSets" min="1" />
|
||||
</label>
|
||||
<button @click="createMiniChampionship">{{ $t('tournaments.newMiniChampionship') }}</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<label>
|
||||
{{ $t('tournaments.name') }}:
|
||||
<input type="text" v-model="newTournamentName" :placeholder="$t('tournaments.tournamentName')" />
|
||||
</label>
|
||||
<label>
|
||||
{{ $t('tournaments.date') }}:
|
||||
<input type="date" v-model="newDate" />
|
||||
</label>
|
||||
<label>
|
||||
{{ $t('tournaments.winningSets') }}:
|
||||
<input type="number" v-model.number="newWinningSets" min="1" />
|
||||
</label>
|
||||
<button @click="createTournament">{{ $t('tournaments.create') }}</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedDate !== 'new'" class="tournament-setup">
|
||||
@@ -122,7 +143,7 @@
|
||||
:allows-external="allowsExternal"
|
||||
:is-group-tournament="isGroupTournament"
|
||||
:selected-member="selectedMember"
|
||||
:club-members="clubMembers"
|
||||
:club-members="clubMembersForParticipantAdd"
|
||||
:has-training-today="hasTrainingToday"
|
||||
:new-external-participant="newExternalParticipant"
|
||||
:participants="participants"
|
||||
@@ -283,6 +304,10 @@ export default {
|
||||
allowsExternal: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isMiniChampionship: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -308,6 +333,7 @@ export default {
|
||||
newDate: '',
|
||||
newTournamentName: '',
|
||||
newWinningSets: 3,
|
||||
newMiniYear: new Date().getFullYear(),
|
||||
currentTournamentName: '',
|
||||
currentTournamentDate: '',
|
||||
currentWinningSets: 3,
|
||||
@@ -506,6 +532,16 @@ export default {
|
||||
groupRankingsKey(groupId, classId) {
|
||||
return `${groupId}-${classId ?? 'null'}`;
|
||||
},
|
||||
clubMembersForParticipantAdd() {
|
||||
if (!this.isMiniChampionship || !this.selectedViewClass || this.selectedViewClass === '__none__' || !this.tournamentClasses?.length) {
|
||||
return this.clubMembers;
|
||||
}
|
||||
const classItem = this.tournamentClasses.find(c => String(c.id) === String(this.selectedViewClass));
|
||||
if (!classItem || (classItem.minBirthYear == null && classItem.maxBirthYear == null && !classItem.gender)) {
|
||||
return this.clubMembers;
|
||||
}
|
||||
return this.clubMembers.filter(m => this.memberEligibleForMiniClass(m, classItem));
|
||||
},
|
||||
|
||||
// Mapping von groupId zu groupNumber für die Teilnehmer-Auswahl
|
||||
groupIdToNumberMap() {
|
||||
@@ -1452,12 +1488,16 @@ export default {
|
||||
|
||||
async loadTournaments() {
|
||||
try {
|
||||
const d = await apiClient.get(`/tournament/${this.currentClub}`);
|
||||
// Filtere Turniere basierend auf allowsExternal Prop
|
||||
if (this.allowsExternal) {
|
||||
this.dates = d.data.filter(t => t.allowsExternal === true || t.allowsExternal === 1);
|
||||
const url = this.isMiniChampionship
|
||||
? `/tournament/${this.currentClub}?type=mini`
|
||||
: `/tournament/${this.currentClub}`;
|
||||
const d = await apiClient.get(url);
|
||||
if (this.isMiniChampionship) {
|
||||
this.dates = d.data || [];
|
||||
} else if (this.allowsExternal) {
|
||||
this.dates = (d.data || []).filter(t => !t.miniChampionshipYear && (t.allowsExternal === true || t.allowsExternal === 1));
|
||||
} else {
|
||||
this.dates = d.data.filter(t => !t.allowsExternal || t.allowsExternal === false || t.allowsExternal === 0);
|
||||
this.dates = (d.data || []).filter(t => !t.miniChampionshipYear && (!t.allowsExternal || t.allowsExternal === false || t.allowsExternal === 0));
|
||||
}
|
||||
|
||||
// Lade Mitgliederliste, falls noch nicht geladen
|
||||
@@ -1556,17 +1596,9 @@ export default {
|
||||
winningSets: this.newWinningSets,
|
||||
allowsExternal: this.allowsExternal
|
||||
});
|
||||
|
||||
|
||||
// Speichere die ID des neuen Turniers
|
||||
const newTournamentId = r.data.id;
|
||||
|
||||
// Lade die Turniere neu
|
||||
await this.loadTournaments();
|
||||
|
||||
// Setze das neue Turnier als ausgewählt
|
||||
this.selectedDate = newTournamentId;
|
||||
|
||||
this.newDate = '';
|
||||
this.newTournamentName = '';
|
||||
this.newWinningSets = 3;
|
||||
@@ -1577,6 +1609,59 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
async createMiniChampionship() {
|
||||
if (!this.newDate) {
|
||||
await this.showInfo(this.$t('messages.error'), this.$t('tournaments.pleaseEnterDate'), '', 'error');
|
||||
return;
|
||||
}
|
||||
const year = Number(this.newMiniYear);
|
||||
if (!Number.isFinite(year) || year < 2000 || year > 2100) {
|
||||
await this.showInfo(this.$t('messages.error'), this.$t('tournaments.miniChampionshipYearHint'), '', 'error');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const r = await apiClient.post('/tournament/mini', {
|
||||
clubId: this.currentClub,
|
||||
tournamentName: this.newTournamentName || (`Minimeisterschaft ${year}`),
|
||||
date: this.newDate,
|
||||
year,
|
||||
winningSets: this.newWinningSets
|
||||
});
|
||||
const newTournamentId = r.data.id;
|
||||
await this.loadTournaments();
|
||||
this.selectedDate = newTournamentId;
|
||||
this.newDate = '';
|
||||
this.newTournamentName = '';
|
||||
this.newMiniYear = new Date().getFullYear();
|
||||
this.newWinningSets = 3;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Anlegen der Minimeisterschaft:', error);
|
||||
const message = safeErrorMessage(error, this.$t('tournaments.errorCreatingTournament'));
|
||||
await this.showInfo(this.$t('messages.error'), message, '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
memberEligibleForMiniClass(member, classItem) {
|
||||
if (classItem.gender && classItem.gender !== 'mixed') {
|
||||
const g = (member.gender || 'unknown').toLowerCase();
|
||||
if (classItem.gender === 'male' && g !== 'male') return false;
|
||||
if (classItem.gender === 'female' && g !== 'female') return false;
|
||||
}
|
||||
const bd = member.birthDate;
|
||||
if (!bd) return true;
|
||||
let birthYear = null;
|
||||
if (typeof bd === 'string' && bd.includes('-')) {
|
||||
birthYear = parseInt(bd.split('-')[0], 10);
|
||||
} else if (typeof bd === 'string' && bd.includes('.')) {
|
||||
const parts = bd.split('.');
|
||||
if (parts.length >= 3) birthYear = parseInt(parts[2], 10);
|
||||
}
|
||||
if (birthYear == null || !Number.isFinite(birthYear)) return true;
|
||||
if (classItem.minBirthYear != null && birthYear < classItem.minBirthYear) return false;
|
||||
if (classItem.maxBirthYear != null && birthYear > classItem.maxBirthYear) return false;
|
||||
return true;
|
||||
},
|
||||
|
||||
async addParticipant() {
|
||||
if (!this.selectedMember) {
|
||||
await this.showInfo(this.$t('messages.error'), this.$t('tournaments.pleaseSelectParticipant'), '', 'error');
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
>
|
||||
🌐 {{ $t('tournaments.openTournaments') }}
|
||||
</button>
|
||||
<button
|
||||
:class="['tab-button', { active: activeTab === 'mini' }]"
|
||||
@click="switchTab('mini')"
|
||||
>
|
||||
🏅 {{ $t('tournaments.miniChampionships') }}
|
||||
</button>
|
||||
<button
|
||||
:class="['tab-button', { active: activeTab === 'official' }]"
|
||||
@click="switchTab('official')"
|
||||
@@ -30,6 +36,9 @@
|
||||
<div v-else-if="activeTab === 'external'">
|
||||
<TournamentTab :allowsExternal="true" />
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'mini'">
|
||||
<TournamentTab :allowsExternal="false" :isMiniChampionship="true" />
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'official'">
|
||||
<OfficialTournaments />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user