diff --git a/backend/controllers/tournamentController.js b/backend/controllers/tournamentController.js index 46b1b2a..e3c6c8e 100644 --- a/backend/controllers/tournamentController.js +++ b/backend/controllers/tournamentController.js @@ -434,6 +434,20 @@ export const updateParticipantSeeded = async (req, res) => { } }; +export const setParticipantGaveUp = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentId, participantId } = req.params; + const { gaveUp } = req.body; + try { + await tournamentService.setParticipantGaveUp(token, clubId, tournamentId, participantId, gaveUp); + emitTournamentChanged(clubId, tournamentId); + res.status(200).json({ message: 'Aufgabe-Status aktualisiert' }); + } catch (err) { + console.error('[setParticipantGaveUp] Error:', err); + res.status(500).json({ error: err.message }); + } +}; + export const deleteMatchResult = async (req, res) => { const { authcode: token } = req.headers; const { clubId, tournamentId, matchId, set } = req.body; @@ -555,6 +569,20 @@ export const updateExternalParticipantSeeded = async (req, res) => { } }; +export const setExternalParticipantGaveUp = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentId, participantId } = req.params; + const { gaveUp } = req.body; + try { + await tournamentService.setExternalParticipantGaveUp(token, clubId, tournamentId, participantId, gaveUp); + emitTournamentChanged(clubId, tournamentId); + res.status(200).json({ message: 'Aufgabe-Status aktualisiert' }); + } catch (error) { + console.error('[setExternalParticipantGaveUp] Error:', error); + res.status(500).json({ error: error.message }); + } +}; + // Tournament Classes export const getTournamentClasses = async (req, res) => { const { authcode: token } = req.headers; diff --git a/backend/migrations/20260130_add_gave_up_to_tournament_participants.sql b/backend/migrations/20260130_add_gave_up_to_tournament_participants.sql new file mode 100644 index 0000000..f51afee --- /dev/null +++ b/backend/migrations/20260130_add_gave_up_to_tournament_participants.sql @@ -0,0 +1,8 @@ +-- Add gave_up (Aufgabe) to tournament participants +-- Wenn ein Spieler aufgibt: alle seine Spiele zählen für den Gegner (11:0), beide aufgegeben = 0:0, kein Sieger + +ALTER TABLE `tournament_member` + ADD COLUMN `gave_up` TINYINT(1) NOT NULL DEFAULT 0 AFTER `out_of_competition`; + +ALTER TABLE `external_tournament_participant` + ADD COLUMN `gave_up` TINYINT(1) NOT NULL DEFAULT 0 AFTER `out_of_competition`; diff --git a/backend/models/ExternalTournamentParticipant.js b/backend/models/ExternalTournamentParticipant.js index fb7592a..f9d66c2 100644 --- a/backend/models/ExternalTournamentParticipant.js +++ b/backend/models/ExternalTournamentParticipant.js @@ -88,6 +88,12 @@ const ExternalTournamentParticipant = sequelize.define('ExternalTournamentPartic type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false + }, + gaveUp: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'gave_up' } }, { underscored: true, diff --git a/backend/models/TournamentMember.js b/backend/models/TournamentMember.js index a979445..7473d19 100644 --- a/backend/models/TournamentMember.js +++ b/backend/models/TournamentMember.js @@ -30,6 +30,12 @@ const TournamentMember = sequelize.define('TournamentMember', { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false + }, + gaveUp: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'gave_up' } }, { underscored: true, diff --git a/backend/routes/tournamentRoutes.js b/backend/routes/tournamentRoutes.js index 2543476..bb97319 100644 --- a/backend/routes/tournamentRoutes.js +++ b/backend/routes/tournamentRoutes.js @@ -20,6 +20,7 @@ import { resetMatches, removeParticipant, updateParticipantSeeded, + setParticipantGaveUp, deleteMatchResult, reopenMatch, deleteKnockoutMatches, @@ -28,6 +29,7 @@ import { getExternalParticipants, removeExternalParticipant, updateExternalParticipantSeeded, + setExternalParticipantGaveUp, getTournamentClasses, addTournamentClass, updateTournamentClass, @@ -54,6 +56,7 @@ router.post('/participant', authenticate, addParticipant); router.post('/participants', authenticate, getParticipants); router.delete('/participant', authenticate, removeParticipant); router.put('/participant/:clubId/:tournamentId/:participantId/seeded', authenticate, updateParticipantSeeded); +router.put('/participant/:clubId/:tournamentId/:participantId/gave-up', authenticate, setParticipantGaveUp); router.post('/modus', authenticate, setModus); router.post('/groups/reset', authenticate, resetGroups); router.post('/matches/reset', authenticate, resetMatches); @@ -82,6 +85,7 @@ router.post('/external-participant', authenticate, addExternalParticipant); router.post('/external-participants', authenticate, getExternalParticipants); router.delete('/external-participant', authenticate, removeExternalParticipant); router.put('/external-participant/:clubId/:tournamentId/:participantId/seeded', authenticate, updateExternalParticipantSeeded); +router.put('/external-participant/:clubId/:tournamentId/:participantId/gave-up', authenticate, setExternalParticipantGaveUp); // Tournament Classes router.get('/classes/:clubId/:tournamentId', authenticate, getTournamentClasses); diff --git a/backend/services/tournamentService.js b/backend/services/tournamentService.js index 62c33cd..5b7b3c3 100644 --- a/backend/services/tournamentService.js +++ b/backend/services/tournamentService.js @@ -1582,6 +1582,7 @@ class TournamentService { if (!tournament || tournament.clubId != clubId) { throw new Error('Turnier nicht gefunden'); } + const winningSets = tournament.winningSets || 3; let groups = await TournamentGroup.findAll({ where: { tournamentId }, include: [{ @@ -1672,6 +1673,7 @@ class TournamentService { ? `${pairing.external2.firstName} ${pairing.external2.lastName}` : 'Unbekannt'; + const pairingGaveUp = !!(pairing.member1?.gaveUp || pairing.member2?.gaveUp || pairing.external1?.gaveUp || pairing.external2?.gaveUp); stats[`pairing_${pairing.id}`] = { id: `pairing_${pairing.id}`, pairingId: pairing.id, @@ -1679,6 +1681,7 @@ class TournamentService { seeded: pairing.seeded || false, isExternal: false, isPairing: true, + gaveUp: pairingGaveUp, player1Id: pairing.member1Id || pairing.external1Id, player2Id: pairing.member2Id || pairing.external2Id, points: 0, @@ -1700,6 +1703,7 @@ class TournamentService { name: `${tm.member.firstName} ${tm.member.lastName}`, seeded: tm.seeded || false, isExternal: false, + gaveUp: !!tm.gaveUp, points: 0, setsWon: 0, setsLost: 0, @@ -1710,7 +1714,7 @@ class TournamentService { matchesLost: 0 }; } - + // Externe Teilnehmer for (const ext of g.externalGroupMembers || []) { stats[ext.id] = { @@ -1718,6 +1722,7 @@ class TournamentService { name: `${ext.firstName} ${ext.lastName}`, seeded: ext.seeded || false, isExternal: true, + gaveUp: !!ext.gaveUp, points: 0, setsWon: 0, setsLost: 0, @@ -1744,6 +1749,46 @@ class TournamentService { ); if (!pairing1Key || !pairing2Key) continue; + + const p1GaveUp = stats[pairing1Key].gaveUp; + const p2GaveUp = stats[pairing2Key].gaveUp; + if (p1GaveUp || p2GaveUp) { + if (p1GaveUp && p2GaveUp) { + stats[pairing1Key].setsWon += 0; + stats[pairing1Key].setsLost += 0; + stats[pairing2Key].setsWon += 0; + stats[pairing2Key].setsLost += 0; + } else if (p1GaveUp && !p2GaveUp) { + stats[pairing2Key].points += 1; + stats[pairing2Key].matchesWon += 1; + stats[pairing1Key].points -= 1; + stats[pairing1Key].matchesLost += 1; + stats[pairing1Key].setsWon += 0; + stats[pairing1Key].setsLost += winningSets; + stats[pairing2Key].setsWon += winningSets; + stats[pairing2Key].setsLost += 0; + const pts = 11 * winningSets; + stats[pairing1Key].pointsWon += 0; + stats[pairing1Key].pointsLost += pts; + stats[pairing2Key].pointsWon += pts; + stats[pairing2Key].pointsLost += 0; + } else { + stats[pairing1Key].points += 1; + stats[pairing1Key].matchesWon += 1; + stats[pairing2Key].points -= 1; + stats[pairing2Key].matchesLost += 1; + stats[pairing1Key].setsWon += winningSets; + stats[pairing1Key].setsLost += 0; + stats[pairing2Key].setsWon += 0; + stats[pairing2Key].setsLost += winningSets; + const pts = 11 * winningSets; + stats[pairing1Key].pointsWon += pts; + stats[pairing1Key].pointsLost += 0; + stats[pairing2Key].pointsWon += 0; + stats[pairing2Key].pointsLost += pts; + } + continue; + } // Ergebnis kann null/undefiniert oder in anderem Format sein -> defensiv prüfen if (!m.result || typeof m.result !== 'string' || !m.result.includes(':')) continue; const [s1, s2] = m.result.split(':').map(n => parseInt(n, 10)); @@ -1781,6 +1826,46 @@ class TournamentService { } else { // Bei Einzel: Normale Logik if (!stats[m.player1Id] || !stats[m.player2Id]) continue; + + const p1GaveUp = stats[m.player1Id].gaveUp; + const p2GaveUp = stats[m.player2Id].gaveUp; + if (p1GaveUp || p2GaveUp) { + if (p1GaveUp && p2GaveUp) { + stats[m.player1Id].setsWon += 0; + stats[m.player1Id].setsLost += 0; + stats[m.player2Id].setsWon += 0; + stats[m.player2Id].setsLost += 0; + } else if (p1GaveUp && !p2GaveUp) { + stats[m.player2Id].points += 1; + stats[m.player2Id].matchesWon += 1; + stats[m.player1Id].points -= 1; + stats[m.player1Id].matchesLost += 1; + stats[m.player1Id].setsWon += 0; + stats[m.player1Id].setsLost += winningSets; + stats[m.player2Id].setsWon += winningSets; + stats[m.player2Id].setsLost += 0; + const pts = 11 * winningSets; + stats[m.player1Id].pointsWon += 0; + stats[m.player1Id].pointsLost += pts; + stats[m.player2Id].pointsWon += pts; + stats[m.player2Id].pointsLost += 0; + } else { + stats[m.player1Id].points += 1; + stats[m.player1Id].matchesWon += 1; + stats[m.player2Id].points -= 1; + stats[m.player2Id].matchesLost += 1; + stats[m.player1Id].setsWon += winningSets; + stats[m.player1Id].setsLost += 0; + stats[m.player2Id].setsWon += 0; + stats[m.player2Id].setsLost += winningSets; + const pts = 11 * winningSets; + stats[m.player1Id].pointsWon += pts; + stats[m.player1Id].pointsLost += 0; + stats[m.player2Id].pointsWon += 0; + stats[m.player2Id].pointsLost += pts; + } + continue; + } // Ergebnis kann null/undefiniert oder in anderem Format sein -> defensiv prüfen if (!m.result || typeof m.result !== 'string' || !m.result.includes(':')) continue; const [s1, s2] = m.result.split(':').map(n => parseInt(n, 10)); @@ -2077,7 +2162,8 @@ class TournamentService { lastName: ep.lastName, club: ep.club, gender: ep.gender, - birthDate: ep.birthDate + birthDate: ep.birthDate, + gaveUp: !!ep.gaveUp }; } else { plain.player1 = null; @@ -2095,18 +2181,49 @@ class TournamentService { lastName: ep.lastName, club: ep.club, gender: ep.gender, - birthDate: ep.birthDate + birthDate: ep.birthDate, + gaveUp: !!ep.gaveUp }; } else { plain.player2 = null; } + // Virtuelle Ergebnisse bei Aufgabe: ein Spieler aufgegeben → Gegner 11:0 je Satz; beide aufgegeben → 0:0, kein Sieger + const winningSets = t.winningSets || 3; + const p1GaveUp = plain.player1?.gaveUp; + const p2GaveUp = plain.player2?.gaveUp; + if (p1GaveUp || p2GaveUp) { + plain.gaveUpMatch = true; + if (p1GaveUp && p2GaveUp) { + plain.result = '0:0'; + plain.isFinished = true; + plain.tournamentResults = [{ set: 1, pointsPlayer1: 0, pointsPlayer2: 0 }]; + } else if (p1GaveUp && !p2GaveUp) { + plain.result = `0:${winningSets}`; + plain.isFinished = true; + plain.tournamentResults = Array.from({ length: winningSets }, (_, i) => ({ set: i + 1, pointsPlayer1: 0, pointsPlayer2: 11 })); + } else { + plain.result = `${winningSets}:0`; + plain.isFinished = true; + plain.tournamentResults = Array.from({ length: winningSets }, (_, i) => ({ set: i + 1, pointsPlayer1: 11, pointsPlayer2: 0 })); + } + } + return plain; }); return finalMatches; } + async _matchHasGaveUpPlayer(tournamentId, player1Id, player2Id) { + const ids = [player1Id, player2Id].filter(Boolean); + if (ids.length === 0) return false; + const members = await TournamentMember.findAll({ where: { tournamentId, id: { [Op.in]: ids } } }); + const externals = await ExternalTournamentParticipant.findAll({ where: { tournamentId, id: { [Op.in]: ids } } }); + if (members.some(m => m.gaveUp) || externals.some(e => e.gaveUp)) return true; + return false; + } + // 12. Satz-Ergebnis hinzufügen/überschreiben async addMatchResult(userToken, clubId, tournamentId, matchId, set, result) { await checkAccess(userToken, clubId); @@ -2119,6 +2236,9 @@ class TournamentService { // Lade Match, um isFinished zu prüfen const match = await TournamentMatch.findByPk(matchId); if (!match || match.tournamentId != tournamentId) throw new Error('Match nicht gefunden'); + if (await this._matchHasGaveUpPlayer(tournamentId, match.player1Id, match.player2Id)) { + throw new Error('Ergebnisse von Spielen mit aufgegebenen Spielern können nicht bearbeitet werden.'); + } const existing = await TournamentResult.findOne({ where: { matchId, set } }); if (existing) { @@ -2163,6 +2283,9 @@ class TournamentService { include: [{ model: TournamentResult, as: "tournamentResults" }] }); if (!match) throw new Error("Match nicht gefunden"); + if (await this._matchHasGaveUpPlayer(tournamentId, match.player1Id, match.player2Id)) { + throw new Error('Spiele mit aufgegebenen Spielern können nicht abgeschlossen werden.'); + } let win = 0, lose = 0; for (const r of match.tournamentResults) { @@ -2334,9 +2457,10 @@ class TournamentService { ] }); const groupMatches = await TournamentMatch.findAll({ - where: { tournamentId, round: "group", isFinished: true }, + where: { tournamentId, round: "group" }, include: [{ model: TournamentResult, as: "tournamentResults" }] }); + const winningSets = tournament.winningSets || 3; // Lade alle Klassen, um zu prüfen, ob es sich um Doppel-Klassen handelt const tournamentClasses = await TournamentClass.findAll({ where: { tournamentId } }); @@ -2352,44 +2476,57 @@ class TournamentService { const stats = {}; // Interne Teilnehmer for (const tm of g.tournamentGroupMembers || []) { - stats[tm.id] = { member: tm, points: 0, setsWon: 0, setsLost: 0, pointsWon: 0, pointsLost: 0, isExternal: false, matchesWon: 0, matchesLost: 0 }; + stats[tm.id] = { member: tm, gaveUp: !!tm.gaveUp, points: 0, setsWon: 0, setsLost: 0, pointsWon: 0, pointsLost: 0, isExternal: false, matchesWon: 0, matchesLost: 0 }; } // Externe Teilnehmer for (const ext of g.externalGroupMembers || []) { - stats[ext.id] = { member: ext, points: 0, setsWon: 0, setsLost: 0, pointsWon: 0, pointsLost: 0, isExternal: true, matchesWon: 0, matchesLost: 0 }; + stats[ext.id] = { member: ext, gaveUp: !!ext.gaveUp, points: 0, setsWon: 0, setsLost: 0, pointsWon: 0, pointsLost: 0, isExternal: true, matchesWon: 0, matchesLost: 0 }; } for (const m of groupMatches.filter(m => m.groupId === g.id)) { if (!stats[m.player1Id] || !stats[m.player2Id]) continue; - const [p1, p2] = m.result.split(":").map(n => parseInt(n, 10)); - if (p1 > p2) { - stats[m.player1Id].points += 1; // Sieger bekommt +1 - stats[m.player1Id].matchesWon += 1; - stats[m.player2Id].points -= 1; // Verlierer bekommt -1 - stats[m.player2Id].matchesLost += 1; + const p1GaveUp = stats[m.player1Id].gaveUp; + const p2GaveUp = stats[m.player2Id].gaveUp; + let p1, p2, p1Points = 0, p2Points = 0; + if (p1GaveUp || p2GaveUp) { + if (p1GaveUp && p2GaveUp) { + p1 = 0; p2 = 0; + } else if (p1GaveUp && !p2GaveUp) { + p1 = 0; p2 = winningSets; + p2Points = 11 * winningSets; + } else { + p1 = winningSets; p2 = 0; + p1Points = 11 * winningSets; + } + } else if (m.isFinished && m.result && typeof m.result === 'string' && m.result.includes(':')) { + [p1, p2] = m.result.split(":").map(n => parseInt(n, 10)); + if (m.tournamentResults && m.tournamentResults.length > 0) { + for (const r of m.tournamentResults) { + p1Points += Math.abs(r.pointsPlayer1 || 0); + p2Points += Math.abs(r.pointsPlayer2 || 0); + } + } } else { - stats[m.player2Id].points += 1; // Sieger bekommt +1 + continue; + } + if (p1 > p2) { + stats[m.player1Id].points += 1; + stats[m.player1Id].matchesWon += 1; + stats[m.player2Id].points -= 1; + stats[m.player2Id].matchesLost += 1; + } else if (p2 > p1) { + stats[m.player2Id].points += 1; stats[m.player2Id].matchesWon += 1; - stats[m.player1Id].points -= 1; // Verlierer bekommt -1 + stats[m.player1Id].points -= 1; stats[m.player1Id].matchesLost += 1; } stats[m.player1Id].setsWon += p1; stats[m.player1Id].setsLost += p2; stats[m.player2Id].setsWon += p2; stats[m.player2Id].setsLost += p1; - - // Berechne gespielte Punkte aus tournamentResults - if (m.tournamentResults && m.tournamentResults.length > 0) { - let p1Points = 0, p2Points = 0; - for (const r of m.tournamentResults) { - // Verwende absolute Werte, falls negative Werte gespeichert wurden - p1Points += Math.abs(r.pointsPlayer1 || 0); - p2Points += Math.abs(r.pointsPlayer2 || 0); - } - stats[m.player1Id].pointsWon += p1Points; - stats[m.player1Id].pointsLost += p2Points; - stats[m.player2Id].pointsWon += p2Points; - stats[m.player2Id].pointsLost += p1Points; - } + stats[m.player1Id].pointsWon += p1Points; + stats[m.player1Id].pointsLost += p2Points; + stats[m.player2Id].pointsWon += p2Points; + stats[m.player2Id].pointsLost += p1Points; } // Berechne Punktverhältnis und absolute Differenz für jeden Spieler @@ -2413,17 +2550,30 @@ class TournamentService { // 5. Bei Spielpunktgleichheit: Wer mehr Spielpunkte erzielt hat if (b.pointsWon !== a.pointsWon) return b.pointsWon - a.pointsWon; // 6. Direkter Vergleich (Sieger weiter oben) - const directMatch = groupMatches.find(m => + const directMatch = groupMatches.find(m => m.groupId === g.id && ((m.player1Id === a.member.id && m.player2Id === b.member.id) || (m.player1Id === b.member.id && m.player2Id === a.member.id)) ); if (directMatch) { - const [s1, s2] = directMatch.result.split(":").map(n => parseInt(n, 10)); - const aWon = (directMatch.player1Id === a.member.id && s1 > s2) || + const p1Gu = stats[directMatch.player1Id]?.gaveUp; + const p2Gu = stats[directMatch.player2Id]?.gaveUp; + let s1, s2; + if (p1Gu && p2Gu) { + s1 = 0; s2 = 0; + } else if (p1Gu && !p2Gu) { + s1 = 0; s2 = winningSets; + } else if (!p1Gu && p2Gu) { + s1 = winningSets; s2 = 0; + } else if (directMatch.result && typeof directMatch.result === 'string' && directMatch.result.includes(':')) { + [s1, s2] = directMatch.result.split(":").map(n => parseInt(n, 10)); + } else { + return a.member.id - b.member.id; + } + const aWon = (directMatch.player1Id === a.member.id && s1 > s2) || (directMatch.player2Id === a.member.id && s2 > s1); - if (aWon) return -1; // a hat gewonnen -> a kommt weiter oben - return 1; // b hat gewonnen -> b kommt weiter oben + if (aWon) return -1; + return 1; } // Fallback: Nach ID return a.member.id - b.member.id; @@ -2942,13 +3092,35 @@ class TournamentService { await participant.save(); } + async setParticipantGaveUp(userToken, clubId, tournamentId, participantId, gaveUp) { + await checkAccess(userToken, clubId); + const participant = await TournamentMember.findOne({ + where: { id: participantId, tournamentId } + }); + if (!participant) throw new Error('Teilnehmer nicht gefunden'); + participant.gaveUp = !!gaveUp; + await participant.save(); + } + + async setExternalParticipantGaveUp(userToken, clubId, tournamentId, participantId, gaveUp) { + await checkAccess(userToken, clubId); + const participant = await ExternalTournamentParticipant.findOne({ + where: { id: participantId, tournamentId } + }); + if (!participant) throw new Error('Externer Teilnehmer nicht gefunden'); + participant.gaveUp = !!gaveUp; + await participant.save(); + } + // services/tournamentService.js async deleteMatchResult(userToken, clubId, tournamentId, matchId, setToDelete) { await checkAccess(userToken, clubId); - // Match existiert? const match = await TournamentMatch.findOne({ where: { id: matchId, tournamentId } }); if (!match) throw new Error('Match nicht gefunden'); + if (await this._matchHasGaveUpPlayer(tournamentId, match.player1Id, match.player2Id)) { + throw new Error('Ergebnisse von Spielen mit aufgegebenen Spielern können nicht bearbeitet werden.'); + } // Satz löschen await TournamentResult.destroy({ where: { matchId, set: setToDelete } }); @@ -2977,6 +3149,9 @@ class TournamentService { if (!match) { throw new Error("Match nicht gefunden"); } + if (await this._matchHasGaveUpPlayer(tournamentId, match.player1Id, match.player2Id)) { + throw new Error('Spiele mit aufgegebenen Spielern können nicht wieder geöffnet werden.'); + } // Nur den Abschluss‑Status zurücksetzen, nicht die Einzelsätze match.isFinished = false; diff --git a/frontend/src/components/tournament/TournamentGroupsTab.vue b/frontend/src/components/tournament/TournamentGroupsTab.vue index 395efc3..63eae3e 100644 --- a/frontend/src/components/tournament/TournamentGroupsTab.vue +++ b/frontend/src/components/tournament/TournamentGroupsTab.vue @@ -128,7 +128,7 @@ G{{ String.fromCharCode(96 + group.groupNumber) }}{{ idx + 1 }} {{ pl.position }}. - {{ pl.name }} + {{ pl.name }}{{ $t('tournaments.gaveUp') }} {{ (pl.matchesWon || 0) * 2 }}:{{ (pl.matchesLost || 0) * 2 }} {{ pl.setsWon }}:{{ pl.setsLost }} @@ -513,5 +513,13 @@ export default { .merge-pools-actions { margin-top: 0.5rem; } +.gave-up-badge { + margin-left: 0.35rem; + padding: 0.1rem 0.35rem; + font-size: 0.75rem; + background: #f8d7da; + color: #721c24; + border-radius: 4px; +} diff --git a/frontend/src/components/tournament/TournamentParticipantsTab.vue b/frontend/src/components/tournament/TournamentParticipantsTab.vue index a757dbb..f6909a6 100644 --- a/frontend/src/components/tournament/TournamentParticipantsTab.vue +++ b/frontend/src/components/tournament/TournamentParticipantsTab.vue @@ -93,6 +93,7 @@ {{ $t('tournaments.seeded') }} + {{ $t('tournaments.gaveUp') }} {{ $t('tournaments.name') }} {{ $t('members.gender') }} {{ $t('tournaments.club') }} @@ -110,6 +111,11 @@ + + +