diff --git a/server.js b/server.js index 898d36a..e161ff7 100644 --- a/server.js +++ b/server.js @@ -345,6 +345,7 @@ app.post('/accounts/:id', requireAuth, (req, res) => { }); // PDF Export route +// PDF Export route - OPTIMIZED app.get('/accounts/:id/pdf', requireAuth, (req, res) => { const accountId = req.params.id; @@ -353,7 +354,6 @@ app.get('/accounts/:id/pdf', requireAuth, (req, res) => { return res.status(404).send('Konto nicht gefunden'); } - // Get all transactions for this account db.all(` SELECT t.*, u.username as created_by_name, i.name as item_name FROM transactions t @@ -367,80 +367,110 @@ app.get('/accounts/:id/pdf', requireAuth, (req, res) => { return res.status(500).send('Fehler beim Generieren des PDFs'); } - // Create PDF document - const doc = new PDFDocument({ margin: 30 }); + const doc = new PDFDocument({ + margin: 30, + bufferPages: true, + autoFirstPage: true, + compress: true + }); - // Set response headers for PDF download res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', `attachment; filename="Kontoauszug_${account.name.replace(/\s+/g, '_')}_${new Date().toISOString().split('T')[0]}.pdf"`); - // Pipe the PDF to the response doc.pipe(res); - // Add content to PDF - doc.fontSize(20).text(`Kontoauszug für ${account.name}`, { align: 'center' }); - doc.moveDown(); + // Title - smaller + doc.fontSize(16).text(`Kontoauszug für ${account.name}`, { align: 'center' }); + doc.moveDown(0.5); - doc.fontSize(14).text(`Kontostand: ${account.balance.toFixed(2)} €`); - doc.fontSize(12).text(`Erstellt am: ${new Date(account.created_at).toLocaleDateString('de-DE')}`); - doc.moveDown(2); + // Account info - smaller fonts + doc.fontSize(10); + doc.text(`Kontostand: ${account.balance.toFixed(2)} €`); + doc.text(`Konto erstellt: ${new Date(account.created_at).toLocaleDateString('de-DE')}`); + doc.text(`Generiert am: ${new Date().toLocaleString('de-DE')}`); + doc.text(`Generiert von: ${req.user.username}`); + doc.moveDown(1.5); - doc.fontSize(16).text('Transaktionshistorie:', { underline: true }); - doc.moveDown(); + // Transaction history header + doc.fontSize(12).text('Transaktionshistorie:', { underline: true }); + doc.moveDown(1); - // Table headers - const table = { - headers: ['Datum', 'Typ', 'Betrag', 'Beschreibung', 'Artikel', 'Durchgeführt von'], - rows: [] - }; + // Table setup + const tableTop = doc.y; + const columnSpacing = 8; + const pageWidth = doc.page.width - 60; - transactions.forEach(tx => { - table.rows.push([ + const columns = [ + { name: 'Datum', width: 100 }, + { name: 'Typ', width: 60 }, + { name: 'Betrag', width: 50 }, + { name: 'Beschreibung', width: 120 }, + { name: 'Artikel', width: 80 }, + { name: 'Von', width: 70 } + ]; + + const totalWidth = columns.reduce((sum, col) => sum + col.width, 0) + (columnSpacing * (columns.length - 1)); + const scaleFactor = totalWidth > pageWidth ? pageWidth / totalWidth : 1; + const scaledColumns = columns.map(col => ({ ...col, width: col.width * scaleFactor })); + + // Draw headers + doc.font('Helvetica-Bold').fontSize(10); + let x = 30; + scaledColumns.forEach(col => { + doc.text(col.name, x, tableTop, { width: col.width, align: 'left' }); + x += col.width + columnSpacing; + }); + + const tableEndX = x - columnSpacing; + doc.moveTo(30, tableTop + 14).lineTo(tableEndX, tableTop + 14).stroke('#000000'); + + // Draw rows + doc.font('Helvetica').fontSize(9); + let y = tableTop + 22; + const rowHeight = 16; + + transactions.forEach((tx, rowIndex) => { + if (y > doc.page.height - 80) { + doc.addPage(); + y = 30; + doc.font('Helvetica-Bold').fontSize(10); + x = 30; + scaledColumns.forEach(col => { + doc.text(col.name, x, y, { width: col.width, align: 'left' }); + x += col.width + columnSpacing; + }); + doc.moveTo(30, y + 14).lineTo(tableEndX, y + 14).stroke('#000000'); + doc.font('Helvetica').fontSize(9); + y += 22; + } + + x = 30; + const rowData = [ new Date(tx.created_at).toLocaleString('de-DE'), tx.type === 'deposit' ? 'Einzahlung' : 'Abbuchung', `${tx.amount.toFixed(2)} €`, - tx.description || '-', - tx.item_name || '-', - tx.created_by_name || 'System' - ]); - }); - - // Draw table - const tableTop = doc.y; - const columnSpacing = 10; - const columnWidths = [80, 60, 50, 120, 80, 80]; - - // Draw headers - doc.font('Helvetica-Bold'); - let x = 30; - table.headers.forEach((header, i) => { - doc.text(header, x, tableTop, { width: columnWidths[i], align: 'left' }); - x += columnWidths[i] + columnSpacing; - }); - doc.font('Helvetica'); - - // Draw rows - let y = tableTop + 20; - transactions.forEach((tx, rowIndex) => { - x = 30; - const row = table.rows[rowIndex]; - row.forEach((cell, i) => { - doc.text(cell, x, y, { width: columnWidths[i], align: 'left' }); - x += columnWidths[i] + columnSpacing; - }); - y += 20; + (tx.description || '-').substring(0, 40), + (tx.item_name || '-').substring(0, 25), + (tx.created_by_name || 'System').substring(0, 20) + ]; + + rowData.forEach((cell, i) => { + doc.text(cell, x, y, { width: scaledColumns[i].width, align: 'left' }); + x += scaledColumns[i].width + columnSpacing; + }); + + y += rowHeight; - // Add horizontal line between rows if (rowIndex < transactions.length - 1) { - doc.moveTo(30, y - 5).lineTo(550, y - 5).stroke('#cccccc'); + doc.moveTo(30, y - 6).lineTo(tableEndX, y - 6).stroke('#e0e0e0'); } }); doc.moveDown(2); - doc.fontSize(10).text(`Generiert am: ${new Date().toLocaleString('de-DE')}`, { align: 'right' }); - doc.fontSize(10).text(`Generiert von: ${req.user.username}`, { align: 'right' }); + doc.fontSize(10); + doc.text(`Gesamt: ${transactions.length} Transaktionen`, { align: 'right' }); + doc.text(`Endstand: ${account.balance.toFixed(2)} €`, { align: 'right' }); - // Finalize PDF doc.end(); }); });