Implementiert die Funktion zur Generierung eines Teilnehmer-PDFs in OfficialTournaments.vue. Fügt die Methode addParticipantsSummary in PDFGenerator.js hinzu, um eine Zusammenfassung der Teilnehmerdaten in einem PDF-Dokument darzustellen. Integriert die Logik zur Gruppierung und Formatierung der Teilnehmerinformationen basierend auf ihrem Anmeldestatus und der Teilnahme.

This commit is contained in:
Torsten Schulz (local)
2025-09-12 14:23:47 +02:00
parent cf04e5bfe8
commit dc8a5778d6
2 changed files with 186 additions and 0 deletions

View File

@@ -264,6 +264,77 @@ class PDFGenerator {
});
}
addParticipantsSummary(tournamentTitle, tournamentDateText, groups) {
// Header
const title = tournamentTitle || 'Offizielles Turnier';
this.pdf.setFont('helvetica', 'bold');
this.pdf.setFontSize(14);
this.pdf.text(title, this.margin, this.cursorY);
this.cursorY += 8;
if (tournamentDateText) {
this.pdf.setFont('helvetica', 'normal');
this.pdf.setFontSize(12);
this.pdf.text(String(tournamentDateText), this.margin, this.cursorY);
this.cursorY += 8;
}
// Tabelle mit Gruppierung
const head = [['Mitglied', 'Konkurrenz', 'Startzeit', 'Status', 'Platzierung']];
const body = [];
const rowStyles = [];
for (const group of groups) {
for (let i = 0; i < group.items.length; i++) {
const item = group.items[i];
const rowData = [
i === 0 ? group.memberName : '', // Name nur in erster Zeile
item.competitionName,
item.start || '',
item.statusText || '',
item.placement || ''
];
body.push(rowData);
rowStyles.push({
isFirstRow: i === 0,
memberStyle: group.memberStyle,
competitionName: item.competitionName,
statusStyle: item.statusStyle
});
}
}
this.pdf.setFontSize(11);
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' },
didParseCell: (data) => {
if (data.section !== 'body') return;
const rowStyle = rowStyles[data.row.index];
// Formatierung für Mitgliedsname (erste Spalte, erste Zeile der Gruppe)
if (data.column.index === 0 && rowStyle.isFirstRow) {
if (rowStyle.memberStyle === 'bold') data.cell.styles.fontStyle = 'bold';
else if (rowStyle.memberStyle === 'italic') data.cell.styles.fontStyle = 'italic';
else data.cell.styles.fontStyle = 'normal';
}
// Formatierung für Konkurrenzname (zweite Spalte)
else if (data.column.index === 1) {
if (rowStyle.statusStyle === 'bold') data.cell.styles.fontStyle = 'bold';
else if (rowStyle.statusStyle === 'italic') data.cell.styles.fontStyle = 'italic';
else data.cell.styles.fontStyle = 'normal';
}
},
didDrawPage: (data) => {
this.cursorY = data.cursor.y + 10;
}
});
}
addMemberCompetitions(tournamentTitle, memberName, recommendedRows = [], otherRows = [], venues = []) {
let y = this.margin;
this.pdf.setFont('helvetica', 'bold');

View File

@@ -154,6 +154,8 @@
<option value="registered">Angemeldet</option>
<option value="participated">Hat gespielt</option>
</select>
<div style="flex:1;"></div>
<button class="btn-primary" :disabled="!participantsRows.length" @click="generateParticipantsPdf">Teilnehmer-PDF</button>
</div>
<table>
<thead>
@@ -448,6 +450,119 @@ export default {
if (byFirst !== 0) return byFirst;
return this.collator.compare(lnA, lnB);
},
generateParticipantsPdf() {
if (!this.parsed) return;
const title = this.parsed?.parsedData?.title || 'Offizielles Turnier';
const dateText = this.parsed?.parsedData?.termin || '';
// Alle Teilnehmer unabhängig vom Filter
const comps = (this.parsed?.parsedData?.competitions) || [];
const compById = Object.fromEntries(comps.map(c => [String(c.id), c]));
const allRows = [];
const seen = new Set();
const merged = [];
if (Array.isArray(this.parsed?.participation)) {
for (const e of this.parsed.participation) {
const competitionId = String(e.competitionId);
const memberId = String(e.memberId);
const key = `${competitionId}-${memberId}`;
seen.add(key);
merged.push({ competitionId, memberId });
}
}
for (const [key, p] of Object.entries(this.participationMap || {})) {
if (seen.has(key)) continue;
const [competitionId, memberId] = key.split('-');
merged.push({ competitionId: String(competitionId), memberId: String(memberId) });
}
for (const e of merged) {
const competitionId = String(e.competitionId);
const memberId = String(e.memberId);
const c = compById[competitionId];
if (!c) continue;
const current = this.getParticipation(competitionId, memberId);
const mname = this.memberNameById(memberId);
const start = String(c.startTime || c.startzeit || '');
let statusStyle = 'normal';
let statusText = '';
if (current.participated) {
statusStyle = 'normal';
statusText = 'gespielt';
} else if (current.registered) {
statusStyle = 'italic';
statusText = 'angemeldet';
} else if (current.wants) {
statusStyle = 'bold';
statusText = 'möchte teilnehmen (Anmeldung fehlt)';
}
allRows.push({
memberName: mname,
competitionName: c.ageClassCompetition || c.altersklasseWettbewerb || '',
start,
placement: current.placement || '',
statusStyle,
statusText,
wants: current.wants,
registered: current.registered,
participated: current.participated,
});
}
// Nach Mitglied und Konkurrenz sortieren
allRows.sort((a, b) => {
const m = this.collator.compare(a.memberName, b.memberName);
if (m !== 0) return m;
return this.collator.compare(a.competitionName, b.competitionName);
});
// Gruppierung nach Mitglied mit korrekter Formatierungslogik
const groups = [];
const byMember = new Map();
for (const row of allRows) {
const key = row.memberName;
if (!byMember.has(key)) byMember.set(key, []);
byMember.get(key).push(row);
}
for (const [memberName, items] of byMember.entries()) {
items.sort((a, b) => this.collator.compare(a.competitionName, b.competitionName));
// Bestimme Formatierung für den Spielernamen basierend auf allen Konkurrenzen
let memberStyle = 'normal';
let memberStatusText = '';
const hasPlayed = items.some(item => item.participated);
const wantsToPlay = items.some(item => item.wants);
const allRegistered = items.every(item => !item.wants || item.registered);
const hasUnregistered = items.some(item => item.wants && !item.registered);
if (hasPlayed) {
memberStyle = 'normal';
memberStatusText = 'hat gespielt';
} else if (wantsToPlay && hasUnregistered) {
memberStyle = 'bold';
memberStatusText = 'Anmeldung fehlt';
} else if (wantsToPlay && allRegistered) {
memberStyle = 'italic';
memberStatusText = 'angemeldet';
} else {
// Fallback für Spieler ohne Teilnahmewünsche
memberStyle = 'normal';
memberStatusText = '';
}
groups.push({
memberName,
memberId: items[0]?.memberName || memberName,
memberStyle,
memberStatusText,
items,
});
}
groups.sort((a, b) => this.collator.compare(a.memberName, b.memberName));
const pdf = new PDFGenerator();
pdf.addParticipantsSummary(title, dateText, groups);
pdf.save('teilnehmer.pdf');
},
clubParticipationRows() {
if (this.clubParticipationRowsData && this.clubParticipationRowsData.length) {
return this.clubParticipationRowsData;