Fixed schedule PDF

This commit is contained in:
Torsten Schulz
2025-07-16 17:15:19 +02:00
parent f5deb343a8
commit ad2ab3cae8
5 changed files with 1135 additions and 373 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,8 @@
"axios": "^1.7.3",
"core-js": "^3.8.3",
"html2canvas": "^1.4.1",
"jspdf": "^2.5.1",
"jspdf": "^2.5.2",
"jspdf-autotable": "^5.0.2",
"sortablejs": "^1.15.3",
"vue": "^3.2.13",
"vue-multiselect": "^3.0.0",

View File

@@ -1,4 +1,5 @@
import jsPDF from 'jspdf';
import autoTable from 'jspdf-autotable';
import html2canvas from 'html2canvas';
class PDFGenerator {
@@ -17,6 +18,7 @@ class PDFGenerator {
this.COLUMN_GROUP = margin + 100;
this.COLUMN_DURATION = margin + 150;
this.LINE_HEIGHT = 7;
this.cursorY = margin;
}
async addSchedule(element) {
@@ -59,6 +61,23 @@ class PDFGenerator {
this.isLeftColumn = true;
}
addTitle(text) {
// remember old settings
const oldFont = this.pdf.getFont();
const oldStyle = this.pdf.getFont().fontStyle;
const oldSize = this.pdf.getFontSize();
// set new
this.pdf.setFont('helvetica', 'bold');
this.pdf.setFontSize(14);
this.pdf.text(text, this.margin, this.cursorY);
this.cursorY += 10;
// restore
this.pdf.setFont(oldFont.fontName, oldStyle);
this.pdf.setFontSize(oldSize);
}
addHeader(clubName, formattedDate, formattedStartTime, formattedEndTime) {
this.pdf.setFontSize(14);
this.pdf.setFont('helvetica', 'bold');
@@ -196,11 +215,9 @@ class PDFGenerator {
if (!this.addressY) {
this.addressY = 30;
}
this.pdf.setFontSize(14);
this.pdf.setFont(undefined, 'bold');
this.pdf.text(clubName, 20, this.addressY);
this.pdf.setFontSize(12);
this.pdf.setFont(undefined, 'normal');
addressLines.forEach(line => {
@@ -210,6 +227,44 @@ class PDFGenerator {
this.addressY += 10; // Abstand zur nächsten Adresse
}
async addScreenshot(element) {
const canvas = await html2canvas(element, { scale: 2 });
const imgData = canvas.toDataURL('image/png');
const imgWidth = this.pageWidth;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
this.pdf.addImage(imgData, 'PNG', this.margin, this.cursorY, imgWidth, imgHeight);
this.cursorY += imgHeight + 10;
if (this.cursorY > this.pdf.internal.pageSize.height - this.margin) {
this.pdf.addPage();
this.cursorY = this.margin;
}
}
addTable(tableId, highlightName = '') {
this.pdf.setFontSize(11);
console.log(highlightName);
autoTable(this.pdf, {
html: `#${tableId}`,
startY: this.cursorY,
margin: { left: this.margin, right: this.margin },
styles: { fontSize: this.pdf.getFontSize() },
headStyles: { fillColor: [220, 220, 220], textColor: 0, halign: 'left' },
theme: 'grid',
didParseCell: (data) => {
const cellText = Array.isArray(data.cell.text)
? data.cell.text.join(' ')
: String(data.cell.text);
if (highlightName && cellText.includes(highlightName)) {
data.cell.styles.fontStyle = 'bold';
}
},
didDrawPage: (data) => {
this.cursorY = data.cursor.y + 10;
}
});
}
}
export default PDFGenerator;

View File

@@ -16,7 +16,7 @@
<button @click="generatePDF">Download PDF</button>
<div v-if="matches.length > 0">
<h3>Spiele für {{ selectedLeague }}</h3>
<table>
<table id="schedule-table">
<thead>
<tr>
<th>Datum</th>
@@ -59,8 +59,6 @@
<script>
import { mapGetters } from 'vuex';
import apiClient from '../apiClient.js';
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
import PDFGenerator from '../components/PDFGenerator.js';
export default {
@@ -97,7 +95,7 @@ export default {
formData.append('clubId', this.currentClub);
try {
const response = await apiClient.post('/matches/import', formData, {
await apiClient.post('/matches/import', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
@@ -130,8 +128,12 @@ export default {
}
},
formatDate(date) {
const d = new Date(date);
const weekdays = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
const wd = weekdays[d.getDay()];
const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
return new Date(date).toLocaleDateString('de-DE', options);
const day = d.toLocaleDateString('de-DE', options);
return `${wd} ${day}`;
},
highlightClubName(teamName) {
const clubName = this.currentClubName;
@@ -141,18 +143,24 @@ export default {
return teamName;
},
getCurrentClubName() {
const club = this.clubs.find(club => club.id === this.currentClub);
const clubIdNum = Number(this.currentClub);
const club = this.clubs.find(c => c.id === clubIdNum);
return club ? club.name : '';
},
async generatePDF() {
const element = this.$el.querySelector('.flex-item > div');
const highlightName = this.getCurrentClubName();
if (element) {
const pdfGen = new PDFGenerator();
await pdfGen.addSchedule(element);
pdfGen.addTitle(`Spiele für ${highlightName} in ${this.selectedLeague}`);
pdfGen.addTable('schedule-table', highlightName);
pdfGen.addNewPage();
const uniqueLocations = this.getUniqueLocations();
uniqueLocations.forEach((addressLines, clubName) => {
pdfGen.addAddress(clubName, addressLines);
if (!clubName.includes(highlightName)) {
console.log(clubName, highlightName);
pdfGen.addAddress(clubName, addressLines);
}
});
pdfGen.save('Spielpläne.pdf');

84
package-lock.json generated
View File

@@ -66,14 +66,14 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.26.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.25.9",
"@babel/helper-validator-identifier": "^7.27.1",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
"picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
@@ -356,18 +356,18 @@
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -398,25 +398,25 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz",
"integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==",
"version": "7.27.6",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
"integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.26.9",
"@babel/types": "^7.26.9"
"@babel/template": "^7.27.2",
"@babel/types": "^7.27.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz",
"integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==",
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.26.9"
"@babel/types": "^7.28.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -1508,27 +1508,24 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz",
"integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==",
"version": "7.27.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
"version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/parser": "^7.26.9",
"@babel/types": "^7.26.9"
"@babel/code-frame": "^7.27.1",
"@babel/parser": "^7.27.2",
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -1553,13 +1550,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz",
"integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==",
"version": "7.28.1",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz",
"integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.25.9",
"@babel/helper-validator-identifier": "^7.25.9"
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -2096,9 +2093,9 @@
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@@ -4487,13 +4484,6 @@
"node": ">=4"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"dev": true,
"license": "MIT"
},
"node_modules/regenerator-transform": {
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",