Initial commit: IT Nexus Web-App
This commit is contained in:
345
backend/src/utils/pdfGenerator.js
Normal file
345
backend/src/utils/pdfGenerator.js
Normal file
@@ -0,0 +1,345 @@
|
||||
const PDFDocument = require('pdfkit');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
class PDFGenerator {
|
||||
constructor() {
|
||||
this.doc = new PDFDocument({
|
||||
size: 'A4',
|
||||
margins: {
|
||||
top: 50,
|
||||
bottom: 50,
|
||||
left: 50,
|
||||
right: 50
|
||||
}
|
||||
});
|
||||
this.currentY = 50;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Cereda Systems header with logo
|
||||
*/
|
||||
addHeader(title, subtitle = '') {
|
||||
// Company name
|
||||
this.doc
|
||||
.fontSize(20)
|
||||
.fillColor('#3fa3a3')
|
||||
.font('Helvetica-Bold')
|
||||
.text('Cereda Systems GmbH', { align: 'center' });
|
||||
|
||||
this.currentY = this.doc.y + 10;
|
||||
|
||||
// Title
|
||||
this.doc
|
||||
.fontSize(16)
|
||||
.fillColor('#1e293b')
|
||||
.font('Helvetica-Bold')
|
||||
.text(title, { align: 'center' });
|
||||
|
||||
this.currentY = this.doc.y + 5;
|
||||
|
||||
// Subtitle
|
||||
if (subtitle) {
|
||||
this.doc
|
||||
.fontSize(10)
|
||||
.fillColor('#64748b')
|
||||
.font('Helvetica')
|
||||
.text(subtitle, { align: 'center' });
|
||||
|
||||
this.currentY = this.doc.y + 5;
|
||||
}
|
||||
|
||||
// Horizontal line
|
||||
this.doc
|
||||
.moveTo(50, this.currentY + 10)
|
||||
.lineTo(545, this.currentY + 10)
|
||||
.strokeColor('#3fa3a3')
|
||||
.lineWidth(2)
|
||||
.stroke();
|
||||
|
||||
this.currentY += 30;
|
||||
this.doc.y = this.currentY;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add section title
|
||||
*/
|
||||
addSectionTitle(title) {
|
||||
this.currentY = this.doc.y + 10;
|
||||
|
||||
this.doc
|
||||
.fontSize(14)
|
||||
.fillColor('#1e293b')
|
||||
.font('Helvetica-Bold')
|
||||
.text(title, 50, this.currentY);
|
||||
|
||||
this.currentY = this.doc.y + 10;
|
||||
this.doc.y = this.currentY;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add text paragraph
|
||||
*/
|
||||
addText(text, options = {}) {
|
||||
const defaultOptions = {
|
||||
fontSize: 10,
|
||||
color: '#334155',
|
||||
font: 'Helvetica',
|
||||
align: 'left'
|
||||
};
|
||||
|
||||
const opts = { ...defaultOptions, ...options };
|
||||
|
||||
this.doc
|
||||
.fontSize(opts.fontSize)
|
||||
.fillColor(opts.color)
|
||||
.font(opts.font)
|
||||
.text(text, {
|
||||
align: opts.align,
|
||||
continued: opts.continued || false
|
||||
});
|
||||
|
||||
this.currentY = this.doc.y + 5;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add key-value pair
|
||||
*/
|
||||
addKeyValue(key, value, options = {}) {
|
||||
const x = options.x || 50;
|
||||
const y = this.currentY;
|
||||
|
||||
this.doc
|
||||
.fontSize(10)
|
||||
.fillColor('#64748b')
|
||||
.font('Helvetica-Bold')
|
||||
.text(`${key}:`, x, y, { continued: true, width: 150 })
|
||||
.fillColor('#1e293b')
|
||||
.font('Helvetica')
|
||||
.text(` ${value || '-'}`);
|
||||
|
||||
this.currentY = this.doc.y + 5;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table
|
||||
*/
|
||||
addTable(headers, rows, options = {}) {
|
||||
const startX = options.x || 50;
|
||||
const startY = this.currentY;
|
||||
const columnWidth = options.columnWidth || 120;
|
||||
const rowHeight = options.rowHeight || 25;
|
||||
|
||||
// Draw header
|
||||
this.doc
|
||||
.fontSize(9)
|
||||
.fillColor('#ffffff')
|
||||
.font('Helvetica-Bold');
|
||||
|
||||
headers.forEach((header, index) => {
|
||||
const x = startX + (index * columnWidth);
|
||||
|
||||
// Header background
|
||||
this.doc
|
||||
.rect(x, startY, columnWidth, rowHeight)
|
||||
.fillAndStroke('#3fa3a3', '#3fa3a3');
|
||||
|
||||
// Header text
|
||||
this.doc
|
||||
.fillColor('#ffffff')
|
||||
.text(header, x + 5, startY + 8, {
|
||||
width: columnWidth - 10,
|
||||
ellipsis: true
|
||||
});
|
||||
});
|
||||
|
||||
// Draw rows
|
||||
rows.forEach((row, rowIndex) => {
|
||||
const y = startY + rowHeight + (rowIndex * rowHeight);
|
||||
|
||||
this.doc
|
||||
.fontSize(9)
|
||||
.fillColor('#1e293b')
|
||||
.font('Helvetica');
|
||||
|
||||
row.forEach((cell, colIndex) => {
|
||||
const x = startX + (colIndex * columnWidth);
|
||||
|
||||
// Row background (alternating)
|
||||
this.doc
|
||||
.rect(x, y, columnWidth, rowHeight)
|
||||
.fillAndStroke(rowIndex % 2 === 0 ? '#f8fafc' : '#ffffff', '#e2e8f0');
|
||||
|
||||
// Cell text
|
||||
this.doc
|
||||
.fillColor('#1e293b')
|
||||
.text(cell || '-', x + 5, y + 8, {
|
||||
width: columnWidth - 10,
|
||||
ellipsis: true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.currentY = startY + rowHeight + (rows.length * rowHeight) + 20;
|
||||
this.doc.y = this.currentY;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add checklist
|
||||
*/
|
||||
addChecklist(items, checkedItems = {}) {
|
||||
this.currentY = this.doc.y + 10;
|
||||
|
||||
items.forEach((item) => {
|
||||
const isChecked = checkedItems[item.key] || false;
|
||||
const checkbox = isChecked ? '[X]' : '[ ]';
|
||||
|
||||
this.doc
|
||||
.fontSize(10)
|
||||
.fillColor(isChecked ? '#10b981' : '#64748b')
|
||||
.font('Helvetica-Bold')
|
||||
.text(checkbox, 50, this.currentY, { continued: true })
|
||||
.font('Helvetica')
|
||||
.fillColor('#1e293b')
|
||||
.text(' ' + item.label);
|
||||
|
||||
this.currentY = this.doc.y + 8;
|
||||
});
|
||||
|
||||
this.currentY += 10;
|
||||
this.doc.y = this.currentY;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add signature fields
|
||||
*/
|
||||
addSignatureFields(fields) {
|
||||
this.currentY = this.doc.y + 20;
|
||||
|
||||
const fieldWidth = 200;
|
||||
const spacing = (545 - 50 - (fields.length * fieldWidth)) / (fields.length - 1);
|
||||
|
||||
fields.forEach((field, index) => {
|
||||
const x = 50 + (index * (fieldWidth + spacing));
|
||||
|
||||
// Signature line
|
||||
this.doc
|
||||
.moveTo(x, this.currentY + 40)
|
||||
.lineTo(x + fieldWidth, this.currentY + 40)
|
||||
.strokeColor('#94a3b8')
|
||||
.lineWidth(1)
|
||||
.stroke();
|
||||
|
||||
// Field label
|
||||
this.doc
|
||||
.fontSize(9)
|
||||
.fillColor('#64748b')
|
||||
.font('Helvetica')
|
||||
.text(field.label, x, this.currentY + 45, {
|
||||
width: fieldWidth,
|
||||
align: 'center'
|
||||
});
|
||||
|
||||
// Date field
|
||||
this.doc
|
||||
.fontSize(8)
|
||||
.fillColor('#94a3b8')
|
||||
.text('Datum: _______________', x, this.currentY + 60, {
|
||||
width: fieldWidth,
|
||||
align: 'center'
|
||||
});
|
||||
});
|
||||
|
||||
this.currentY += 100;
|
||||
this.doc.y = this.currentY;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add footer
|
||||
*/
|
||||
addFooter(text) {
|
||||
const bottomY = 800;
|
||||
|
||||
// Horizontal line
|
||||
this.doc
|
||||
.moveTo(50, bottomY - 20)
|
||||
.lineTo(545, bottomY - 20)
|
||||
.strokeColor('#e2e8f0')
|
||||
.lineWidth(1)
|
||||
.stroke();
|
||||
|
||||
// Footer text
|
||||
this.doc
|
||||
.fontSize(8)
|
||||
.fillColor('#94a3b8')
|
||||
.font('Helvetica')
|
||||
.text(text, 50, bottomY - 10, {
|
||||
align: 'center'
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add page break
|
||||
*/
|
||||
addPageBreak() {
|
||||
this.doc.addPage();
|
||||
this.currentY = 50;
|
||||
this.doc.y = this.currentY;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save PDF to file
|
||||
*/
|
||||
async saveToFile(filePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Ensure directory exists
|
||||
const dir = path.dirname(filePath);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
const stream = fs.createWriteStream(filePath);
|
||||
|
||||
this.doc.pipe(stream);
|
||||
this.doc.end();
|
||||
|
||||
stream.on('finish', () => resolve(filePath));
|
||||
stream.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PDF as buffer
|
||||
*/
|
||||
async getBuffer() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const buffers = [];
|
||||
|
||||
this.doc.on('data', buffers.push.bind(buffers));
|
||||
this.doc.on('end', () => resolve(Buffer.concat(buffers)));
|
||||
this.doc.on('error', reject);
|
||||
|
||||
this.doc.end();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PDFGenerator;
|
||||
Reference in New Issue
Block a user