From 51b620f496d1e0aebe15f52a5c4c61f920b3ca3e Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Fri, 17 Jul 2026 14:28:12 +0200 Subject: [PATCH] feat: enhance table generation in PDF with optional cell pinning for teams --- frontend/src/components/PDFGenerator.js | 38 ++++++++++++++++++++++--- frontend/src/views/ScheduleView.vue | 24 +++++++++++----- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/PDFGenerator.js b/frontend/src/components/PDFGenerator.js index 77e7fcab..20d05f50 100755 --- a/frontend/src/components/PDFGenerator.js +++ b/frontend/src/components/PDFGenerator.js @@ -384,7 +384,7 @@ class PDFGenerator { } } - addTable(tableId, highlightName = '', excludeColumns = []) { + addTable(tableId, highlightName = '', excludeColumns = [], getCellPin = null) { this.pdf.setFontSize(11); // Wenn Spalten ausgeschlossen werden sollen, parse die Tabelle manuell @@ -398,10 +398,15 @@ class PDFGenerator { .map(h => h.text); const rows = Array.from(table.querySelectorAll('tbody tr')).map(tr => { - return Array.from(tr.querySelectorAll('td')) - .map((td, index) => ({ text: td.textContent.trim(), index })) + const sourceCells = Array.from(tr.querySelectorAll('td')) + .map((td, index) => ({ text: td.textContent.trim(), index })); + + return sourceCells .filter(cell => !excludeColumns.includes(cell.index)) - .map(cell => cell.text); + .map(cell => { + const pin = getCellPin ? getCellPin(sourceCells, cell.index) : null; + return pin ? { content: cell.text, pin } : cell.text; + }); }); autoTable(this.pdf, { @@ -425,6 +430,31 @@ class PDFGenerator { }, didDrawPage: (data) => { this.cursorY = data.cursor.y + 10; + }, + didDrawCell: (data) => { + const pin = data.cell.raw?.pin; + if (!pin || data.section !== 'body') return; + + const fontSize = 7; + const teamText = Array.isArray(data.cell.text) + ? data.cell.text.join(' ') + : String(data.cell.text || ''); + const pinText = `(${pin})`; + const padding = data.cell.padding('left'); + const teamWidth = this.pdf.getTextWidth(teamText); + const pinWidth = this.pdf.getTextWidth(pinText) * (fontSize / this.pdf.getFontSize()); + const pinX = data.cell.x + padding + teamWidth + 1; + const pinY = data.cell.y + data.cell.height - 3; + + this.pdf.setFontSize(fontSize); + this.pdf.setTextColor(90); + if (pinX + pinWidth <= data.cell.x + data.cell.width - data.cell.padding('right')) { + this.pdf.text(pinText, pinX, pinY); + } else { + this.pdf.text(pinText, data.cell.x + padding, pinY); + } + this.pdf.setFontSize(11); + this.pdf.setTextColor(0); } }); } else { diff --git a/frontend/src/views/ScheduleView.vue b/frontend/src/views/ScheduleView.vue index 7cf2fce5..4215df6e 100755 --- a/frontend/src/views/ScheduleView.vue +++ b/frontend/src/views/ScheduleView.vue @@ -2504,19 +2504,29 @@ export default { const pdfGen = new PDFGenerator(20, 10, this.$t); pdfGen.addTitle(`${this.$t('schedule.gamesFor')} ${highlightName} ${this.$t('common.in')} ${this.selectedLeague}`); - // Bestimme die auszuschließenden Spalten - // Spaltenstruktur: Datum, Uhrzeit, Heimmannschaft, Gastmannschaft, Ergebnis, [Altersklasse], Code, Heim-PIN, Gast-PIN + // Die Ortsliste steht auf Seite 2. In der Spieltabelle bleiben Datum, + // Uhrzeit, Heim- und Gastmannschaft sowie ggf. die Altersklasse. + // Spaltenstruktur: Ort, Datum, Uhrzeit, Heim, Gast, Ergebnis, + // [Altersklasse], Code, Heim-PIN, Gast-PIN const hasAgeClass = this.selectedLeague === this.$t('schedule.overallSchedule') || this.selectedLeague === this.$t('schedule.adultSchedule'); let excludeColumns; if (hasAgeClass) { - // Mit Altersklasse: Ergebnis=4, Code=6, Heim-PIN=7, Gast-PIN=8 - excludeColumns = [4, 6, 7, 8]; + // Mit Altersklasse: Ort=0, Ergebnis=5, Code=7, Heim-PIN=8, Gast-PIN=9 + excludeColumns = [0, 5, 7, 8, 9]; } else { - // Ohne Altersklasse: Ergebnis=4, Code=5, Heim-PIN=6, Gast-PIN=7 - excludeColumns = [4, 5, 6, 7]; + // Ohne Altersklasse: Ort=0, Ergebnis=5, Code=6, Heim-PIN=7, Gast-PIN=8 + excludeColumns = [0, 5, 6, 7, 8]; } - pdfGen.addTable('schedule-table', highlightName, excludeColumns); + const homePinIndex = hasAgeClass ? 8 : 7; + const guestPinIndex = hasAgeClass ? 9 : 8; + const getOwnTeamPin = (cells, index) => { + if (this.friendlyOnly || (index !== 3 && index !== 4)) return null; + const teamName = cells[index]?.text || ''; + const pin = cells[index === 3 ? homePinIndex : guestPinIndex]?.text || ''; + return teamName.includes(highlightName) && pin !== '-' ? pin : null; + }; + pdfGen.addTable('schedule-table', highlightName, excludeColumns, getOwnTeamPin); pdfGen.addNewPage(); const uniqueLocations = this.getUniqueLocations(); uniqueLocations.forEach((addressLines, clubName) => {