Implement external participant management and tournament class features
This commit enhances the tournament management system by introducing functionality for handling external participants and tournament classes. New methods are added to the `tournamentController` and `tournamentService` for adding, retrieving, updating, and removing external participants, as well as managing tournament classes. The backend models are updated to support these features, including new relationships and attributes. The frontend is also updated to allow users to manage external participants and classes, improving the overall user experience and interactivity in tournament management.
This commit is contained in:
@@ -95,9 +95,13 @@
|
||||
<span class="nav-icon">🏆</span>
|
||||
Interne Turniere
|
||||
</router-link>
|
||||
<router-link v-if="hasPermission('tournaments', 'read')" to="/official-tournaments" class="nav-link" title="Offizielle Turniere">
|
||||
<router-link v-if="hasPermission('tournaments', 'read')" to="/external-tournaments" class="nav-link" title="Turniere mit Externen">
|
||||
<span class="nav-icon">🌐</span>
|
||||
Offene Turniere
|
||||
</router-link>
|
||||
<router-link v-if="hasPermission('tournaments', 'read')" to="/official-tournaments" class="nav-link" title="Turnierteilnahmen">
|
||||
<span class="nav-icon">📄</span>
|
||||
Offizielle Turniere
|
||||
Turnierteilnahmen
|
||||
</router-link>
|
||||
<router-link v-if="hasPermission('predefined_activities', 'read')" to="/predefined-activities" class="nav-link" title="Vordefinierte Aktivitäten">
|
||||
<span class="nav-icon">⚙️</span>
|
||||
|
||||
@@ -58,6 +58,7 @@ class PDFGenerator {
|
||||
this.pdf.addPage();
|
||||
this.xPos = this.margin;
|
||||
this.yPos = this.position;
|
||||
this.cursorY = this.margin;
|
||||
this.isLeftColumn = true;
|
||||
}
|
||||
|
||||
@@ -606,6 +607,552 @@ class PDFGenerator {
|
||||
this.cursorY = y + 10;
|
||||
}
|
||||
|
||||
addTournamentPDF(tournamentName, tournamentDate, groupsByClass, groupRankings, matchesByClassAndGroup, getPlayerName, knockoutRanking, participants, hasKnockoutMatches, knockoutMatches) {
|
||||
// Header
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(16);
|
||||
this.pdf.text(tournamentName || 'Turnier', this.margin, this.cursorY);
|
||||
this.cursorY += 8;
|
||||
|
||||
if (tournamentDate) {
|
||||
this.pdf.setFont('helvetica', 'normal');
|
||||
this.pdf.setFontSize(12);
|
||||
const formattedDate = new Date(tournamentDate).toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
this.pdf.text(`Datum: ${formattedDate}`, this.margin, this.cursorY);
|
||||
this.cursorY += 10;
|
||||
}
|
||||
|
||||
// 1. Gesamt-Ranking nach Klassen (nur Platz und Spieler)
|
||||
// knockoutRanking und participants werden als zusätzliche Parameter übergeben
|
||||
// Verwende K.O.-Ranking nur wenn es vorhanden ist UND K.O.-Runden existieren
|
||||
const useKnockoutRanking = knockoutRanking && knockoutRanking.length > 0 && hasKnockoutMatches;
|
||||
this.addTournamentClassRankings(groupsByClass, groupRankings, getPlayerName, useKnockoutRanking ? knockoutRanking : null, participants);
|
||||
|
||||
// 2. Gruppen-Matrizen mit Ergebnissen (neue Seite)
|
||||
this.addTournamentGroupMatrices(groupsByClass, groupRankings, getPlayerName);
|
||||
|
||||
// 4. Alle Spiele nach Klasse und Gruppe sortiert (inkl. K.O.-Runden)
|
||||
this.addTournamentMatches(matchesByClassAndGroup, getPlayerName, knockoutMatches);
|
||||
}
|
||||
|
||||
addTournamentClassRankings(groupsByClass, groupRankings, getPlayerName, knockoutRanking, participants) {
|
||||
// Wenn K.O.-Runden vorhanden sind, verwende diese für das Ranking
|
||||
if (knockoutRanking && knockoutRanking.length > 0) {
|
||||
// Erstelle Mapping von Member-ID zu classId
|
||||
// Für interne Teilnehmer: member.id -> classId
|
||||
const memberClassMap = {};
|
||||
if (participants) {
|
||||
participants.forEach(p => {
|
||||
// Interne Teilnehmer haben ein member-Objekt
|
||||
if (p.member && p.member.id) {
|
||||
memberClassMap[p.member.id] = p.classId;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Gruppiere K.O.-Ranking nach Klassen
|
||||
// entry.classId sollte bereits vorhanden sein (aus extendedRankingList)
|
||||
const rankingByClass = {};
|
||||
knockoutRanking.forEach(entry => {
|
||||
// Verwende classId direkt aus entry, falls vorhanden
|
||||
let classId = entry.classId;
|
||||
|
||||
// Fallback: Suche über memberId, falls classId nicht vorhanden
|
||||
if (classId == null && entry.member) {
|
||||
const memberId = entry.member.id;
|
||||
if (memberId && participants) {
|
||||
// Suche im Mapping
|
||||
classId = memberClassMap[memberId] || null;
|
||||
|
||||
// Falls nicht gefunden, suche direkt in participants
|
||||
if (!classId) {
|
||||
const participant = participants.find(p =>
|
||||
p.member && p.member.id === memberId
|
||||
);
|
||||
if (participant) {
|
||||
classId = participant.classId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const classKey = classId != null ? String(classId) : 'null';
|
||||
|
||||
if (!rankingByClass[classKey]) {
|
||||
rankingByClass[classKey] = [];
|
||||
}
|
||||
|
||||
const playerName = entry.member
|
||||
? `${entry.member.firstName || ''} ${entry.member.lastName || ''}`.trim()
|
||||
: getPlayerName(entry.player || entry);
|
||||
|
||||
rankingByClass[classKey].push({
|
||||
position: entry.position,
|
||||
name: playerName
|
||||
});
|
||||
});
|
||||
|
||||
// Sortiere innerhalb jeder Klasse nach Position
|
||||
Object.keys(rankingByClass).forEach(classKey => {
|
||||
rankingByClass[classKey].sort((a, b) => a.position - b.position);
|
||||
});
|
||||
|
||||
// Zeige Rankings nach Klassen (sortiert nach classId)
|
||||
Object.entries(rankingByClass).sort((a, b) => {
|
||||
const aNum = a[0] === 'null' ? 999999 : parseInt(a[0]);
|
||||
const bNum = b[0] === 'null' ? 999999 : parseInt(b[0]);
|
||||
return aNum - bNum;
|
||||
}).forEach(([classId, players]) => {
|
||||
const className = classId !== 'null' && classId !== 'undefined' && this.getClassNameForId ? this.getClassNameForId(classId) : null;
|
||||
|
||||
// Prüfe ob neue Seite nötig
|
||||
if (this.cursorY > 250) {
|
||||
this.addNewPage();
|
||||
this.cursorY = this.margin;
|
||||
}
|
||||
|
||||
// Klassen-Überschrift
|
||||
if (className) {
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(14);
|
||||
this.pdf.text(className, this.margin, this.cursorY);
|
||||
this.cursorY += 10;
|
||||
}
|
||||
|
||||
// Zeige Spieler
|
||||
players.forEach(player => {
|
||||
if (this.cursorY > 280) {
|
||||
this.addNewPage();
|
||||
this.cursorY = this.margin;
|
||||
// Klassen-Überschrift erneut anzeigen bei Seitenwechsel
|
||||
if (className) {
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(14);
|
||||
this.pdf.text(className, this.margin, this.cursorY);
|
||||
this.cursorY += 10;
|
||||
}
|
||||
}
|
||||
|
||||
this.pdf.setFont('helvetica', 'normal');
|
||||
this.pdf.setFontSize(11);
|
||||
const playerText = `${player.position}. ${player.name}`;
|
||||
this.pdf.text(playerText, this.margin, this.cursorY);
|
||||
this.cursorY += 7;
|
||||
});
|
||||
|
||||
// Abstand nach Klasse
|
||||
this.cursorY += 5;
|
||||
});
|
||||
} else {
|
||||
// Fallback: Verwende Gruppen-Rankings (nur wenn keine K.O.-Runden)
|
||||
// Aber: Wenn es K.O.-Runden gibt, aber rankingList leer ist, bedeutet das,
|
||||
// dass die K.O.-Runden noch nicht abgeschlossen sind
|
||||
// In diesem Fall sollten wir trotzdem die Gruppen-Rankings verwenden
|
||||
Object.entries(groupsByClass).forEach(([classId, groups]) => {
|
||||
const className = classId !== 'null' && classId !== 'undefined' && this.getClassNameForId ? this.getClassNameForId(classId) : null;
|
||||
|
||||
// Prüfe ob neue Seite nötig
|
||||
if (this.cursorY > 250) {
|
||||
this.addNewPage();
|
||||
this.cursorY = this.margin;
|
||||
}
|
||||
|
||||
// Klassen-Überschrift
|
||||
if (className) {
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(14);
|
||||
this.pdf.text(className, this.margin, this.cursorY);
|
||||
this.cursorY += 10;
|
||||
}
|
||||
|
||||
// Sammle alle Spieler aus allen Gruppen dieser Klasse
|
||||
// WICHTIG: Zeige alle Spieler, nicht nur die ersten Plätze
|
||||
const allPlayers = [];
|
||||
groups.forEach(group => {
|
||||
const rankings = groupRankings[group.groupId] || [];
|
||||
rankings.forEach(player => {
|
||||
allPlayers.push({
|
||||
position: player.position,
|
||||
name: player.name,
|
||||
seeded: player.seeded
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Sortiere nach Position (1, 1, 2, 2, 3, 3, etc.)
|
||||
allPlayers.sort((a, b) => {
|
||||
if (a.position !== b.position) {
|
||||
return a.position - b.position;
|
||||
}
|
||||
// Bei gleicher Position alphabetisch sortieren
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
// Erstelle einfache Liste: nur Platz und Spieler
|
||||
allPlayers.forEach(player => {
|
||||
if (this.cursorY > 280) {
|
||||
this.addNewPage();
|
||||
this.cursorY = this.margin;
|
||||
// Klassen-Überschrift erneut anzeigen bei Seitenwechsel
|
||||
if (className) {
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(14);
|
||||
this.pdf.text(className, this.margin, this.cursorY);
|
||||
this.cursorY += 10;
|
||||
}
|
||||
}
|
||||
|
||||
this.pdf.setFont('helvetica', 'normal');
|
||||
this.pdf.setFontSize(11);
|
||||
const playerText = `${player.position}. ${player.seeded ? '★ ' : ''}${player.name}`;
|
||||
this.pdf.text(playerText, this.margin, this.cursorY);
|
||||
this.cursorY += 7;
|
||||
});
|
||||
|
||||
// Abstand nach Klasse
|
||||
this.cursorY += 5;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addTournamentTables(groupsByClass, groupRankings, getPlayerName) {
|
||||
// Für jede Klasse
|
||||
Object.entries(groupsByClass).forEach(([classId, groups]) => {
|
||||
const className = classId !== 'null' && classId !== 'undefined' && this.getClassNameForId ? this.getClassNameForId(classId) : null;
|
||||
|
||||
// Für jede Gruppe
|
||||
groups.forEach(group => {
|
||||
if (this.cursorY > 250) {
|
||||
this.addNewPage();
|
||||
this.cursorY = this.margin;
|
||||
}
|
||||
|
||||
// Überschrift
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(12);
|
||||
let title = `Gruppe ${group.groupNumber}`;
|
||||
if (className) {
|
||||
title = `${className} - ${title}`;
|
||||
}
|
||||
this.pdf.text(title, this.margin, this.cursorY);
|
||||
this.cursorY += 8;
|
||||
|
||||
// Tabelle mit Rankings
|
||||
const rankings = groupRankings[group.groupId] || [];
|
||||
if (rankings.length > 0) {
|
||||
const head = [['Platz', 'Spieler', 'Punkte', 'Sätze', 'Diff']];
|
||||
const body = rankings.map(p => [
|
||||
`${p.position}.`,
|
||||
(p.seeded ? '★ ' : '') + p.name,
|
||||
p.points.toString(),
|
||||
`${p.setsWon}:${p.setsLost}`,
|
||||
(p.setDiff >= 0 ? '+' : '') + p.setDiff.toString()
|
||||
]);
|
||||
|
||||
autoTable(this.pdf, {
|
||||
startY: this.cursorY,
|
||||
margin: { left: this.margin, right: this.margin },
|
||||
head,
|
||||
body,
|
||||
theme: 'grid',
|
||||
styles: { fontSize: 10 },
|
||||
headStyles: { fillColor: [220, 220, 220], textColor: 0, halign: 'left', fontStyle: 'bold' },
|
||||
didDrawPage: (data) => {
|
||||
this.cursorY = data.cursor.y + 10;
|
||||
}
|
||||
});
|
||||
this.cursorY += 5;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addTournamentKnockoutRanking(knockoutRanking, getPlayerName) {
|
||||
// Prüfe ob neue Seite nötig
|
||||
if (this.cursorY > 200) {
|
||||
this.addNewPage();
|
||||
this.cursorY = this.margin;
|
||||
}
|
||||
|
||||
// Überschrift
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(14);
|
||||
this.pdf.text('Gesamt-Ranking (K.O.-Runden)', this.margin, this.cursorY);
|
||||
this.cursorY += 10;
|
||||
|
||||
// Ranking-Tabelle
|
||||
const head = [['Platz', 'Spieler']];
|
||||
const body = knockoutRanking.map(entry => {
|
||||
const playerName = entry.member
|
||||
? `${entry.member.firstName || ''} ${entry.member.lastName || ''}`.trim()
|
||||
: getPlayerName(entry.player || entry);
|
||||
return [
|
||||
`${entry.position}.`,
|
||||
playerName
|
||||
];
|
||||
});
|
||||
|
||||
autoTable(this.pdf, {
|
||||
startY: this.cursorY,
|
||||
margin: { left: this.margin, right: this.margin },
|
||||
head,
|
||||
body,
|
||||
theme: 'grid',
|
||||
styles: { fontSize: 11 },
|
||||
headStyles: { fillColor: [220, 220, 220], textColor: 0, halign: 'left', fontStyle: 'bold' },
|
||||
didDrawPage: (data) => {
|
||||
this.cursorY = data.cursor.y + 10;
|
||||
}
|
||||
});
|
||||
this.cursorY += 5;
|
||||
}
|
||||
|
||||
addTournamentGroupMatrices(groupsByClass, groupRankings, getPlayerName) {
|
||||
// Neue Seite für Matrizen
|
||||
this.addNewPage();
|
||||
this.cursorY = this.margin;
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(14);
|
||||
this.pdf.text('Gruppen-Matrizen', this.margin, this.cursorY);
|
||||
this.cursorY += 10;
|
||||
|
||||
// Für jede Klasse
|
||||
Object.entries(groupsByClass).forEach(([classId, groups]) => {
|
||||
const className = classId !== 'null' && classId !== 'undefined' && this.getClassNameForId ? this.getClassNameForId(classId) : null;
|
||||
|
||||
// Für jede Gruppe
|
||||
groups.forEach(group => {
|
||||
if (this.cursorY > 200) {
|
||||
this.addNewPage();
|
||||
this.cursorY = this.margin;
|
||||
}
|
||||
|
||||
const rankings = groupRankings[group.groupId] || [];
|
||||
if (rankings.length === 0) return;
|
||||
|
||||
// Überschrift
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(11);
|
||||
let title = `Gruppe ${group.groupNumber}`;
|
||||
if (className) {
|
||||
title = `${className} - ${title}`;
|
||||
}
|
||||
this.pdf.text(title, this.margin, this.cursorY);
|
||||
this.cursorY += 7;
|
||||
|
||||
// Matrix erstellen
|
||||
const head = [['', ...rankings.map((p, idx) => `G${String.fromCharCode(96 + group.groupNumber)}${idx + 1}`)]];
|
||||
const body = rankings.map((player, idx) => {
|
||||
const row = [`G${String.fromCharCode(96 + group.groupNumber)}${idx + 1} ${(player.seeded ? '★ ' : '') + player.name}`];
|
||||
rankings.forEach((opponent, oppIdx) => {
|
||||
if (idx === oppIdx) {
|
||||
row.push('-');
|
||||
} else {
|
||||
// Finde Match-Ergebnis
|
||||
const match = this.findMatchResult ? this.findMatchResult(player.id, opponent.id, group.groupId) : null;
|
||||
row.push(match || '-');
|
||||
}
|
||||
});
|
||||
return row;
|
||||
});
|
||||
|
||||
autoTable(this.pdf, {
|
||||
startY: this.cursorY,
|
||||
margin: { left: this.margin, right: this.margin },
|
||||
head,
|
||||
body,
|
||||
theme: 'grid',
|
||||
styles: { fontSize: 8 },
|
||||
headStyles: { fillColor: [220, 220, 220], textColor: 0, halign: 'left', fontStyle: 'bold' },
|
||||
columnStyles: {
|
||||
0: { cellWidth: 50 }
|
||||
},
|
||||
didDrawPage: (data) => {
|
||||
this.cursorY = data.cursor.y + 10;
|
||||
}
|
||||
});
|
||||
this.cursorY += 5;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addTournamentMatches(matchesByClassAndGroup, getPlayerName, knockoutMatches = []) {
|
||||
// Neue Seite für Spiele
|
||||
this.addNewPage();
|
||||
this.cursorY = this.margin;
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(14);
|
||||
this.pdf.text('Alle Spiele', this.margin, this.cursorY);
|
||||
this.cursorY += 10;
|
||||
|
||||
// Für jede Klasse
|
||||
Object.entries(matchesByClassAndGroup).forEach(([classId, groups]) => {
|
||||
const className = classId !== 'null' && classId !== 'undefined' && this.getClassNameForId ? this.getClassNameForId(classId) : null;
|
||||
|
||||
// Für jede Gruppe
|
||||
Object.entries(groups).forEach(([groupId, matches]) => {
|
||||
if (matches.length === 0) return;
|
||||
|
||||
if (this.cursorY > 200) {
|
||||
this.addNewPage();
|
||||
this.cursorY = this.margin;
|
||||
}
|
||||
|
||||
// Überschrift
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(11);
|
||||
let title = `Gruppe ${matches[0].groupNumber || groupId}`;
|
||||
if (className) {
|
||||
title = `${className} - ${title}`;
|
||||
}
|
||||
this.pdf.text(title, this.margin, this.cursorY);
|
||||
this.cursorY += 7;
|
||||
|
||||
// Spiele-Tabelle
|
||||
const head = [['Runde', 'Spieler 1', 'Spieler 2', 'Ergebnis', 'Sätze']];
|
||||
const body = matches.map(m => [
|
||||
m.groupRound?.toString() || '-',
|
||||
getPlayerName(m.player1),
|
||||
getPlayerName(m.player2),
|
||||
m.isFinished ? (m.result || '-') : '-',
|
||||
this.formatSets(m)
|
||||
]);
|
||||
|
||||
autoTable(this.pdf, {
|
||||
startY: this.cursorY,
|
||||
margin: { left: this.margin, right: this.margin },
|
||||
head,
|
||||
body,
|
||||
theme: 'grid',
|
||||
styles: { fontSize: 9 },
|
||||
headStyles: { fillColor: [220, 220, 220], textColor: 0, halign: 'left', fontStyle: 'bold' },
|
||||
didDrawPage: (data) => {
|
||||
this.cursorY = data.cursor.y + 10;
|
||||
}
|
||||
});
|
||||
this.cursorY += 5;
|
||||
});
|
||||
});
|
||||
|
||||
// K.O.-Runden hinzufügen (wenn vorhanden)
|
||||
if (knockoutMatches && knockoutMatches.length > 0) {
|
||||
// Gruppiere K.O.-Matches nach Klassen
|
||||
const knockoutMatchesByClass = {};
|
||||
knockoutMatches.forEach(match => {
|
||||
const classKey = match.classId != null ? String(match.classId) : 'null';
|
||||
if (!knockoutMatchesByClass[classKey]) {
|
||||
knockoutMatchesByClass[classKey] = [];
|
||||
}
|
||||
knockoutMatchesByClass[classKey].push(match);
|
||||
});
|
||||
|
||||
// Sortiere Klassen
|
||||
const sortedClasses = Object.keys(knockoutMatchesByClass).sort((a, b) => {
|
||||
const aNum = a === 'null' ? 999999 : parseInt(a);
|
||||
const bNum = b === 'null' ? 999999 : parseInt(b);
|
||||
return aNum - bNum;
|
||||
});
|
||||
|
||||
// Für jede Klasse
|
||||
sortedClasses.forEach(classKey => {
|
||||
const classMatches = knockoutMatchesByClass[classKey];
|
||||
const className = classKey !== 'null' && classKey !== 'undefined' && this.getClassNameForId ? this.getClassNameForId(classKey) : null;
|
||||
|
||||
// Sortiere Matches nach Runde (frühere Runden zuerst: Achtelfinale, Viertelfinale, Halbfinale, Finale)
|
||||
const getRoundType = (roundName) => {
|
||||
if (!roundName) return 999;
|
||||
if (roundName.includes('Achtelfinale')) return 0;
|
||||
if (roundName.includes('Viertelfinale')) return 1;
|
||||
if (roundName.includes('Halbfinale')) return 2;
|
||||
if (roundName.includes('Finale')) return 3;
|
||||
// Für Runden wie "6-Runde", "8-Runde" etc. - extrahiere die Zahl
|
||||
const numberMatch = roundName.match(/(\d+)-Runde/);
|
||||
if (numberMatch) {
|
||||
const num = parseInt(numberMatch[1]);
|
||||
// Größere Zahlen = frühere Runden, also umgekehrt sortieren
|
||||
return -num; // Negativ, damit größere Zahlen zuerst kommen
|
||||
}
|
||||
return 999; // Unbekannte Runden zuletzt
|
||||
};
|
||||
|
||||
classMatches.sort((a, b) => {
|
||||
const aRoundType = getRoundType(a.round);
|
||||
const bRoundType = getRoundType(b.round);
|
||||
if (aRoundType !== bRoundType) {
|
||||
return aRoundType - bRoundType;
|
||||
}
|
||||
// Wenn gleicher Typ, alphabetisch sortieren
|
||||
return (a.round || '').localeCompare(b.round || '');
|
||||
});
|
||||
|
||||
if (this.cursorY > 200) {
|
||||
this.addNewPage();
|
||||
this.cursorY = this.margin;
|
||||
}
|
||||
|
||||
// Überschrift
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(11);
|
||||
let title = 'K.-o.-Runde';
|
||||
if (className) {
|
||||
title = `${className} - ${title}`;
|
||||
}
|
||||
this.pdf.text(title, this.margin, this.cursorY);
|
||||
this.cursorY += 7;
|
||||
|
||||
// Spiele-Tabelle
|
||||
const head = [['Runde', 'Spieler 1', 'Spieler 2', 'Ergebnis', 'Sätze']];
|
||||
const body = classMatches.map(m => [
|
||||
m.round || '-',
|
||||
getPlayerName(m.player1),
|
||||
getPlayerName(m.player2),
|
||||
m.isFinished ? (m.result || '-') : '-',
|
||||
this.formatSets(m)
|
||||
]);
|
||||
|
||||
autoTable(this.pdf, {
|
||||
startY: this.cursorY,
|
||||
margin: { left: this.margin, right: this.margin },
|
||||
head,
|
||||
body,
|
||||
theme: 'grid',
|
||||
styles: { fontSize: 9 },
|
||||
headStyles: { fillColor: [220, 220, 220], textColor: 0, halign: 'left', fontStyle: 'bold' },
|
||||
didDrawPage: (data) => {
|
||||
this.cursorY = data.cursor.y + 10;
|
||||
}
|
||||
});
|
||||
this.cursorY += 5;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getClassNameForId(classId, groupsByClass) {
|
||||
// Versuche, den Klassennamen zu finden
|
||||
// Dies sollte von außen übergeben werden, aber als Fallback:
|
||||
return `Klasse ${classId}`;
|
||||
}
|
||||
|
||||
findMatchResult(player1Id, player2Id, groupId) {
|
||||
// Diese Methode sollte von außen übergeben werden
|
||||
// Für jetzt: Rückgabe von null
|
||||
return null;
|
||||
}
|
||||
|
||||
formatSets(match) {
|
||||
if (!match.tournamentResults || match.tournamentResults.length === 0) {
|
||||
return '-';
|
||||
}
|
||||
return match.tournamentResults
|
||||
.sort((a, b) => a.set - b.set)
|
||||
.map(r => `${Math.abs(r.pointsPlayer1)}:${Math.abs(r.pointsPlayer2)}`)
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default PDFGenerator;
|
||||
|
||||
@@ -10,6 +10,7 @@ import DiaryView from './views/DiaryView.vue';
|
||||
import PendingApprovalsView from './views/PendingApprovalsView.vue';
|
||||
import ScheduleView from './views/ScheduleView.vue';
|
||||
import TournamentsView from './views/TournamentsView.vue';
|
||||
import ExternalTournamentsView from './views/ExternalTournamentsView.vue';
|
||||
import TrainingStatsView from './views/TrainingStatsView.vue';
|
||||
import ClubSettings from './views/ClubSettings.vue';
|
||||
import PredefinedActivities from './views/PredefinedActivities.vue';
|
||||
@@ -33,7 +34,8 @@ const routes = [
|
||||
{ path: '/diary', component: DiaryView },
|
||||
{ path: '/pending-approvals', component: PendingApprovalsView},
|
||||
{ path: '/schedule', component: ScheduleView},
|
||||
{ path: '/tournaments', component: TournamentsView },
|
||||
{ path: '/tournaments', component: TournamentsView, props: { allowsExternal: false } },
|
||||
{ path: '/external-tournaments', component: ExternalTournamentsView },
|
||||
{ path: '/training-stats', component: TrainingStatsView },
|
||||
{ path: '/club-settings', component: ClubSettings },
|
||||
{ path: '/predefined-activities', component: PredefinedActivities },
|
||||
|
||||
14
frontend/src/views/ExternalTournamentsView.vue
Normal file
14
frontend/src/views/ExternalTournamentsView.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<TournamentsView :allowsExternal="true" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TournamentsView from './TournamentsView.vue';
|
||||
|
||||
export default {
|
||||
name: 'ExternalTournamentsView',
|
||||
components: {
|
||||
TournamentsView
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="official-tournaments">
|
||||
<h2>Offizielle Turniere</h2>
|
||||
<h2>Turnierteilnahmen</h2>
|
||||
<div v-if="list && list.length" class="list">
|
||||
<div class="tabs">
|
||||
<button :class="['tab', topActiveTab==='events' ? 'active' : '']" @click="switchTopTab('events')" title="Gespeicherte Veranstaltungen anzeigen">Veranstaltungen</button>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user