feat: enhance PDF generation and improve Lucide icon initialization
This commit is contained in:
@@ -934,7 +934,7 @@ function renderEntries(entries) {
|
||||
});
|
||||
|
||||
// Reinitialize Lucide icons
|
||||
if (typeof lucide !== 'undefined') {
|
||||
if (typeof lucide !== 'undefined' && lucide.createIcons) {
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
@@ -1103,7 +1103,10 @@ function renderMonthlyView(entries) {
|
||||
const holidayName = getHolidayName(dateObj);
|
||||
const displayText = holidayName || 'Kein Eintrag';
|
||||
|
||||
let emptyRowClass = weekend ? 'hover:bg-gray-700 bg-gray-700' : 'hover:bg-gray-700 bg-red-950/40';
|
||||
// Don't mark future days as red, only past workdays without entries
|
||||
const isFutureDay = dateObj > today;
|
||||
let emptyRowClass = weekend ? 'hover:bg-gray-700 bg-gray-700' :
|
||||
isFutureDay ? 'hover:bg-gray-700' : 'hover:bg-gray-700 bg-red-950/40';
|
||||
|
||||
|
||||
row.className = emptyRowClass;
|
||||
@@ -1151,7 +1154,7 @@ function renderMonthlyView(entries) {
|
||||
}
|
||||
|
||||
// Reinitialize Lucide icons
|
||||
if (typeof lucide !== 'undefined') {
|
||||
if (typeof lucide !== 'undefined' && lucide.createIcons) {
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
@@ -1467,7 +1470,7 @@ async function updateVacationStatistics() {
|
||||
// Update UI with dynamic year
|
||||
const vacationLabel = document.getElementById('vacationYearLabel');
|
||||
vacationLabel.innerHTML = `<i data-lucide="plane" class="w-4 h-4"></i> Urlaub ${currentYear}`;
|
||||
if (typeof lucide !== 'undefined') {
|
||||
if (typeof lucide !== 'undefined' && lucide.createIcons) {
|
||||
lucide.createIcons();
|
||||
}
|
||||
document.getElementById('statVacationTaken').textContent = vacationTaken;
|
||||
@@ -1649,7 +1652,7 @@ function updateLocationButtons(location) {
|
||||
}
|
||||
|
||||
// Reinitialize Lucide icons after DOM update
|
||||
if (typeof lucide !== 'undefined') {
|
||||
if (typeof lucide !== 'undefined' && lucide.createIcons) {
|
||||
lucide.createIcons();
|
||||
}
|
||||
}
|
||||
@@ -2029,6 +2032,182 @@ async function bulkDeleteEntries() {
|
||||
toggleBulkEditMode(); // Close bulk edit mode
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate PDF with common template
|
||||
* @param {Object} options - PDF generation options
|
||||
*/
|
||||
async function generatePDF(options) {
|
||||
const {
|
||||
title = 'Zeiterfassung',
|
||||
subtitle,
|
||||
tableData,
|
||||
statistics,
|
||||
additionalInfo = {},
|
||||
fileName
|
||||
} = options;
|
||||
|
||||
const { targetHours, totalNetHours, balance } = statistics;
|
||||
const { vacationDays = 0, flextimeDays = 0 } = additionalInfo;
|
||||
|
||||
// Get employee data from settings
|
||||
const employeeName = await getSetting('employeeName') || '';
|
||||
const employeeId = await getSetting('employeeId') || '';
|
||||
|
||||
// Get jsPDF from window
|
||||
const { jsPDF } = window.jspdf;
|
||||
|
||||
// Create PDF
|
||||
const doc = new jsPDF('p', 'mm', 'a4');
|
||||
|
||||
// Header with statistics
|
||||
doc.setFillColor(15, 23, 42);
|
||||
doc.rect(0, 0, 210, 35, 'F');
|
||||
|
||||
// Title and subtitle
|
||||
doc.setTextColor(255, 255, 255);
|
||||
doc.setFontSize(16);
|
||||
doc.setFont(undefined, 'bold');
|
||||
doc.text(title, 15, 12, { align: 'left' });
|
||||
|
||||
doc.setFontSize(11);
|
||||
doc.setFont(undefined, 'normal');
|
||||
doc.text(subtitle, 195, 12, { align: 'right' });
|
||||
|
||||
// Employee info in second line
|
||||
if (employeeName || employeeId) {
|
||||
doc.setFontSize(8);
|
||||
doc.setTextColor(200, 200, 200);
|
||||
let employeeInfo = '';
|
||||
if (employeeName) employeeInfo += employeeName;
|
||||
if (employeeId) {
|
||||
if (employeeInfo) employeeInfo += ' | ';
|
||||
employeeInfo += `Personal-Nr. ${employeeId}`;
|
||||
}
|
||||
doc.text(employeeInfo, 15, 19, { align: 'left' });
|
||||
}
|
||||
|
||||
// Statistics - three columns in one line
|
||||
const statsY = employeeName || employeeId ? 28 : 22;
|
||||
|
||||
// Soll
|
||||
doc.setTextColor(180, 180, 180);
|
||||
doc.setFontSize(7);
|
||||
doc.text('SOLL-STUNDEN', 40, statsY - 3, { align: 'center' });
|
||||
doc.setTextColor(255, 255, 255);
|
||||
doc.setFontSize(11);
|
||||
doc.setFont(undefined, 'bold');
|
||||
doc.text(`${targetHours.toFixed(1)}h`, 40, statsY + 3, { align: 'center' });
|
||||
|
||||
// Ist
|
||||
doc.setTextColor(180, 180, 180);
|
||||
doc.setFontSize(7);
|
||||
doc.setFont(undefined, 'normal');
|
||||
doc.text('IST-STUNDEN', 105, statsY - 3, { align: 'center' });
|
||||
doc.setTextColor(255, 255, 255);
|
||||
doc.setFontSize(11);
|
||||
doc.setFont(undefined, 'bold');
|
||||
doc.text(`${totalNetHours.toFixed(1)}h`, 105, statsY + 3, { align: 'center' });
|
||||
|
||||
// Saldo
|
||||
doc.setTextColor(180, 180, 180);
|
||||
doc.setFontSize(7);
|
||||
doc.setFont(undefined, 'normal');
|
||||
doc.text('SALDO', 170, statsY - 3, { align: 'center' });
|
||||
if (balance >= 0) {
|
||||
doc.setTextColor(34, 197, 94);
|
||||
} else {
|
||||
doc.setTextColor(239, 68, 68);
|
||||
}
|
||||
doc.setFontSize(11);
|
||||
doc.setFont(undefined, 'bold');
|
||||
doc.text(`${balance >= 0 ? '+' : ''}${balance.toFixed(1)}h`, 170, statsY + 3, { align: 'center' });
|
||||
|
||||
// Additional info if needed (small, far right)
|
||||
if (vacationDays > 0 || flextimeDays > 0) {
|
||||
doc.setTextColor(150, 150, 150);
|
||||
doc.setFontSize(7);
|
||||
doc.setFont(undefined, 'normal');
|
||||
let infoText = '';
|
||||
if (vacationDays > 0) infoText += `Urlaub: ${vacationDays}`;
|
||||
if (flextimeDays > 0) {
|
||||
if (infoText) infoText += ' ';
|
||||
infoText += `Gleitzeit: ${flextimeDays}`;
|
||||
}
|
||||
doc.text(infoText, 195, statsY + 3, { align: 'right' });
|
||||
}
|
||||
|
||||
// Table starts after header
|
||||
let yPos = 37;
|
||||
|
||||
// Generate table
|
||||
doc.autoTable({
|
||||
startY: yPos,
|
||||
head: [['Datum', 'Tag', 'Beginn', 'Ende', 'Pause', 'Typ', 'Netto', 'Abw.']],
|
||||
body: tableData,
|
||||
theme: 'grid',
|
||||
tableWidth: 'auto',
|
||||
headStyles: {
|
||||
fillColor: [30, 41, 59],
|
||||
textColor: [255, 255, 255],
|
||||
fontSize: 9,
|
||||
fontStyle: 'bold',
|
||||
halign: 'center',
|
||||
cellPadding: 2.5,
|
||||
minCellHeight: 7
|
||||
},
|
||||
bodyStyles: {
|
||||
fillColor: [248, 250, 252],
|
||||
textColor: [15, 23, 42],
|
||||
fontSize: 8,
|
||||
cellPadding: 2,
|
||||
minCellHeight: 6
|
||||
},
|
||||
alternateRowStyles: {
|
||||
fillColor: [241, 245, 249]
|
||||
},
|
||||
columnStyles: {
|
||||
0: { halign: 'center', cellWidth: 24 }, // Datum
|
||||
1: { halign: 'center', cellWidth: 14 }, // Tag
|
||||
2: { halign: 'center', cellWidth: 18 }, // Beginn
|
||||
3: { halign: 'center', cellWidth: 18 }, // Ende
|
||||
4: { halign: 'center', cellWidth: 18 }, // Pause
|
||||
5: { halign: 'center', cellWidth: 26 }, // Ort
|
||||
6: { halign: 'center', cellWidth: 18 }, // Netto
|
||||
7: { halign: 'center', cellWidth: 18 } // Abweichung
|
||||
},
|
||||
didParseCell: function(data) {
|
||||
if (data.column.index === 7 && data.section === 'body') {
|
||||
const value = data.cell.raw;
|
||||
if (value.startsWith('+')) {
|
||||
data.cell.styles.textColor = [34, 197, 94];
|
||||
data.cell.styles.fontStyle = 'bold';
|
||||
} else if (value.startsWith('-') && value !== '-') {
|
||||
data.cell.styles.textColor = [239, 68, 68];
|
||||
data.cell.styles.fontStyle = 'bold';
|
||||
}
|
||||
}
|
||||
},
|
||||
margin: { left: 20, right: 20 } // Smaller margins for wider table
|
||||
});
|
||||
|
||||
// Footer
|
||||
const finalY = doc.lastAutoTable.finalY || yPos + 50;
|
||||
if (finalY < 270) {
|
||||
doc.setTextColor(156, 163, 175);
|
||||
doc.setFontSize(8);
|
||||
doc.text(`Erstellt am: ${new Date().toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}`, 105, 285, { align: 'center' });
|
||||
}
|
||||
|
||||
// Save PDF
|
||||
doc.save(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk export selected entries as PDF
|
||||
*/
|
||||
@@ -2143,82 +2322,7 @@ async function bulkExportPDF() {
|
||||
const targetHours = workdaysPassed * 8;
|
||||
const balance = totalNetHours - targetHours;
|
||||
|
||||
// Create PDF
|
||||
const doc = new jsPDF('p', 'mm', 'a4');
|
||||
|
||||
// Header with gradient effect
|
||||
doc.setFillColor(15, 23, 42);
|
||||
doc.rect(0, 0, 210, 35, 'F');
|
||||
|
||||
doc.setTextColor(255, 255, 255);
|
||||
doc.setFontSize(22);
|
||||
doc.setFont(undefined, 'bold');
|
||||
doc.text('Zeiterfassung', 105, 18, { align: 'center' });
|
||||
|
||||
doc.setFontSize(13);
|
||||
doc.setFont(undefined, 'normal');
|
||||
doc.text(dateRange, 105, 27, { align: 'center' });
|
||||
|
||||
// Statistics box - centered and styled
|
||||
let yPos = 43;
|
||||
doc.setFillColor(30, 41, 59);
|
||||
doc.roundedRect(20, yPos, 170, 38, 3, 3, 'F');
|
||||
|
||||
doc.setTextColor(156, 163, 175);
|
||||
doc.setFontSize(9);
|
||||
|
||||
// Employee info (if available)
|
||||
if (employeeName || employeeId) {
|
||||
let employeeInfo = '';
|
||||
if (employeeName) employeeInfo += `Mitarbeiter: ${employeeName}`;
|
||||
if (employeeId) {
|
||||
if (employeeInfo) employeeInfo += ' | ';
|
||||
employeeInfo += `Personal-Nr.: ${employeeId}`;
|
||||
}
|
||||
doc.text(employeeInfo, 105, yPos + 8, { align: 'center' });
|
||||
yPos += 5;
|
||||
}
|
||||
|
||||
// Statistics - centered layout with three columns
|
||||
const col1X = 45;
|
||||
const col2X = 105;
|
||||
const col3X = 165;
|
||||
|
||||
doc.text('Soll-Stunden', col1X, yPos + 12, { align: 'center' });
|
||||
doc.text('Ist-Stunden', col2X, yPos + 12, { align: 'center' });
|
||||
doc.text('Saldo', col3X, yPos + 12, { align: 'center' });
|
||||
|
||||
doc.setTextColor(255, 255, 255);
|
||||
doc.setFontSize(16);
|
||||
doc.setFont(undefined, 'bold');
|
||||
doc.text(`${targetHours.toFixed(1)}h`, col1X, yPos + 22, { align: 'center' });
|
||||
doc.text(`${totalNetHours.toFixed(1)}h`, col2X, yPos + 22, { align: 'center' });
|
||||
|
||||
if (balance >= 0) {
|
||||
doc.setTextColor(34, 197, 94);
|
||||
} else {
|
||||
doc.setTextColor(239, 68, 68);
|
||||
}
|
||||
doc.text(`${balance >= 0 ? '+' : ''}${balance.toFixed(1)}h`, col3X, yPos + 22, { align: 'center' });
|
||||
|
||||
// Additional info
|
||||
if (vacationDays > 0 || flextimeDays > 0) {
|
||||
yPos += 30;
|
||||
doc.setTextColor(156, 163, 175);
|
||||
doc.setFontSize(8);
|
||||
let infoText = '';
|
||||
if (vacationDays > 0) infoText += `Urlaubstage: ${vacationDays}`;
|
||||
if (flextimeDays > 0) {
|
||||
if (infoText) infoText += ' | ';
|
||||
infoText += `Gleittage: ${flextimeDays}`;
|
||||
}
|
||||
doc.text(infoText, 105, yPos + 8, { align: 'center' });
|
||||
yPos += 13;
|
||||
} else {
|
||||
yPos += 43;
|
||||
}
|
||||
|
||||
// Table data - include all days in range (including weekends/holidays)
|
||||
// Build table data
|
||||
const allDaysData = [];
|
||||
const entriesMap = new Map(selectedEntriesData.map(e => [e.date, e]));
|
||||
|
||||
@@ -2258,7 +2362,7 @@ async function bulkExportPDF() {
|
||||
endTime = '-';
|
||||
pauseText = '-';
|
||||
} else {
|
||||
locationText = entry.location === 'home' ? 'Home' : 'Büro';
|
||||
locationText = entry.location === 'home' ? 'Home' : 'Office';
|
||||
}
|
||||
|
||||
allDaysData.push([
|
||||
@@ -2300,74 +2404,16 @@ async function bulkExportPDF() {
|
||||
currentDate.setDate(currentDate.getDate() + 1);
|
||||
}
|
||||
|
||||
const tableData = allDaysData;
|
||||
|
||||
doc.autoTable({
|
||||
startY: yPos,
|
||||
head: [['Datum', 'Tag', 'Beginn', 'Ende', 'Pause', 'Ort', 'Netto', 'Abw.']],
|
||||
body: tableData,
|
||||
theme: 'grid',
|
||||
headStyles: {
|
||||
fillColor: [30, 41, 59],
|
||||
textColor: [255, 255, 255],
|
||||
fontSize: 9,
|
||||
fontStyle: 'bold',
|
||||
halign: 'center',
|
||||
cellPadding: 3
|
||||
},
|
||||
bodyStyles: {
|
||||
fillColor: [248, 250, 252],
|
||||
textColor: [15, 23, 42],
|
||||
fontSize: 8,
|
||||
cellPadding: 2.5
|
||||
},
|
||||
alternateRowStyles: {
|
||||
fillColor: [241, 245, 249]
|
||||
},
|
||||
columnStyles: {
|
||||
0: { halign: 'center', cellWidth: 24 }, // Datum
|
||||
1: { halign: 'center', cellWidth: 14 }, // Wochentag
|
||||
2: { halign: 'center', cellWidth: 18 }, // Beginn
|
||||
3: { halign: 'center', cellWidth: 18 }, // Ende
|
||||
4: { halign: 'center', cellWidth: 18 }, // Pause
|
||||
5: { halign: 'center', cellWidth: 28 }, // Ort
|
||||
6: { halign: 'center', cellWidth: 18 }, // Netto
|
||||
7: { halign: 'center', cellWidth: 18 } // Abweichung
|
||||
},
|
||||
didParseCell: function(data) {
|
||||
if (data.column.index === 7 && data.section === 'body') {
|
||||
const value = data.cell.raw;
|
||||
if (value.startsWith('+')) {
|
||||
data.cell.styles.textColor = [34, 197, 94];
|
||||
data.cell.styles.fontStyle = 'bold';
|
||||
} else if (value.startsWith('-') && value !== '-') {
|
||||
data.cell.styles.textColor = [239, 68, 68];
|
||||
data.cell.styles.fontStyle = 'bold';
|
||||
}
|
||||
}
|
||||
},
|
||||
// Calculate margins to center the table
|
||||
// Total column width: 156mm, page width: 210mm, so we need (210-156)/2 = 27mm margins
|
||||
margin: { left: 27, right: 27 }
|
||||
});
|
||||
|
||||
// Footer
|
||||
const finalY = doc.lastAutoTable.finalY || yPos + 50;
|
||||
if (finalY < 270) {
|
||||
doc.setTextColor(156, 163, 175);
|
||||
doc.setFontSize(8);
|
||||
doc.text(`Erstellt am: ${new Date().toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}`, 105, 285, { align: 'center' });
|
||||
}
|
||||
|
||||
// Save PDF
|
||||
// Generate PDF using common template
|
||||
const fileName = `Zeiterfassung_${selectedEntriesData[0].date}_${selectedEntriesData[selectedEntriesData.length - 1].date}.pdf`;
|
||||
doc.save(fileName);
|
||||
await generatePDF({
|
||||
title: 'Zeiterfassung',
|
||||
subtitle: dateRange,
|
||||
tableData: allDaysData,
|
||||
statistics: { targetHours, totalNetHours, balance },
|
||||
additionalInfo: { vacationDays, flextimeDays },
|
||||
fileName
|
||||
});
|
||||
|
||||
showNotification(`PDF mit ${allDaysData.length} Tag(en) erstellt`, 'success');
|
||||
} catch (error) {
|
||||
@@ -3072,87 +3118,7 @@ async function handleExportPDF() {
|
||||
const previousBalance = await calculatePreviousBalance(year, month);
|
||||
const totalBalance = monthBalance + previousBalance;
|
||||
|
||||
// Create PDF
|
||||
const doc = new jsPDF('p', 'mm', 'a4');
|
||||
|
||||
// Get employee data from settings
|
||||
const employeeName = await getSetting('employeeName') || '';
|
||||
const employeeId = await getSetting('employeeId') || '';
|
||||
|
||||
// Header with gradient effect
|
||||
doc.setFillColor(15, 23, 42);
|
||||
doc.rect(0, 0, 210, 35, 'F');
|
||||
|
||||
doc.setTextColor(255, 255, 255);
|
||||
doc.setFontSize(22);
|
||||
doc.setFont(undefined, 'bold');
|
||||
doc.text('Zeiterfassung', 105, 18, { align: 'center' });
|
||||
|
||||
doc.setFontSize(13);
|
||||
doc.setFont(undefined, 'normal');
|
||||
doc.text(monthName, 105, 27, { align: 'center' });
|
||||
|
||||
// Statistics box - centered and styled
|
||||
let yPos = 43;
|
||||
doc.setFillColor(30, 41, 59);
|
||||
doc.roundedRect(20, yPos, 170, 38, 3, 3, 'F');
|
||||
|
||||
doc.setTextColor(156, 163, 175);
|
||||
doc.setFontSize(9);
|
||||
|
||||
// Employee info (if available)
|
||||
if (employeeName || employeeId) {
|
||||
let employeeInfo = '';
|
||||
if (employeeName) employeeInfo += `Mitarbeiter: ${employeeName}`;
|
||||
if (employeeId) {
|
||||
if (employeeInfo) employeeInfo += ' | ';
|
||||
employeeInfo += `Personal-Nr.: ${employeeId}`;
|
||||
}
|
||||
doc.text(employeeInfo, 105, yPos + 8, { align: 'center' });
|
||||
yPos += 5;
|
||||
}
|
||||
|
||||
// Statistics - centered layout with three columns
|
||||
const col1X = 45;
|
||||
const col2X = 105;
|
||||
const col3X = 165;
|
||||
|
||||
doc.text('Soll-Stunden', col1X, yPos + 12, { align: 'center' });
|
||||
doc.text('Ist-Stunden', col2X, yPos + 12, { align: 'center' });
|
||||
doc.text('Saldo', col3X, yPos + 12, { align: 'center' });
|
||||
|
||||
doc.setTextColor(255, 255, 255);
|
||||
doc.setFontSize(16);
|
||||
doc.setFont(undefined, 'bold');
|
||||
doc.text(`${targetHours.toFixed(1)}h`, col1X, yPos + 22, { align: 'center' });
|
||||
doc.text(`${totalNetHours.toFixed(1)}h`, col2X, yPos + 22, { align: 'center' });
|
||||
|
||||
if (monthBalance >= 0) {
|
||||
doc.setTextColor(34, 197, 94);
|
||||
} else {
|
||||
doc.setTextColor(239, 68, 68);
|
||||
}
|
||||
doc.text(`${monthBalance >= 0 ? '+' : ''}${monthBalance.toFixed(1)}h`, col3X, yPos + 22, { align: 'center' });
|
||||
|
||||
// Additional info if vacation or flextime days exist
|
||||
if (vacationDays > 0 || flextimeDays > 0) {
|
||||
yPos += 30;
|
||||
doc.setTextColor(156, 163, 175);
|
||||
doc.setFontSize(8);
|
||||
let infoText = '';
|
||||
if (vacationDays > 0) infoText += `Urlaubstage: ${vacationDays}`;
|
||||
if (flextimeDays > 0) {
|
||||
if (infoText) infoText += ' | ';
|
||||
infoText += `Gleittage: ${flextimeDays}`;
|
||||
}
|
||||
doc.text(infoText, 105, yPos + 8, { align: 'center' });
|
||||
yPos += 13;
|
||||
} else {
|
||||
yPos += 43;
|
||||
}
|
||||
|
||||
// Table with entries
|
||||
// Create a complete list of all days in the month including weekends/holidays
|
||||
// Build table data
|
||||
const allDaysData = [];
|
||||
const entriesMap = new Map(entries.map(e => [e.date, e]));
|
||||
|
||||
@@ -3192,7 +3158,7 @@ async function handleExportPDF() {
|
||||
endTime = '-';
|
||||
pauseText = '-';
|
||||
} else {
|
||||
locationText = entry.location === 'home' ? 'Home' : 'Büro';
|
||||
locationText = entry.location === 'home' ? 'Home' : 'Office';
|
||||
}
|
||||
|
||||
allDaysData.push([
|
||||
@@ -3228,78 +3194,21 @@ async function handleExportPDF() {
|
||||
'-'
|
||||
]);
|
||||
}
|
||||
// Skip regular workdays without entries (not shown in PDF)
|
||||
// Skip regular workdays without entries
|
||||
}
|
||||
|
||||
const tableData = allDaysData;
|
||||
|
||||
doc.autoTable({
|
||||
startY: yPos,
|
||||
head: [['Datum', 'Tag', 'Beginn', 'Ende', 'Pause', 'Ort', 'Netto', 'Abw.']],
|
||||
body: tableData,
|
||||
theme: 'grid',
|
||||
headStyles: {
|
||||
fillColor: [30, 41, 59],
|
||||
textColor: [255, 255, 255],
|
||||
fontSize: 9,
|
||||
fontStyle: 'bold',
|
||||
halign: 'center',
|
||||
cellPadding: 3
|
||||
},
|
||||
bodyStyles: {
|
||||
fillColor: [248, 250, 252],
|
||||
textColor: [15, 23, 42],
|
||||
fontSize: 8,
|
||||
cellPadding: 2.5
|
||||
},
|
||||
alternateRowStyles: {
|
||||
fillColor: [241, 245, 249]
|
||||
},
|
||||
columnStyles: {
|
||||
0: { halign: 'center', cellWidth: 24 }, // Datum
|
||||
1: { halign: 'center', cellWidth: 14 }, // Wochentag
|
||||
2: { halign: 'center', cellWidth: 18 }, // Beginn
|
||||
3: { halign: 'center', cellWidth: 18 }, // Ende
|
||||
4: { halign: 'center', cellWidth: 18 }, // Pause
|
||||
5: { halign: 'center', cellWidth: 28 }, // Ort
|
||||
6: { halign: 'center', cellWidth: 18 }, // Netto
|
||||
7: { halign: 'center', cellWidth: 18 } // Abweichung
|
||||
},
|
||||
didParseCell: function(data) {
|
||||
// Color code deviations in the last column
|
||||
if (data.column.index === 7 && data.section === 'body') {
|
||||
const value = data.cell.raw;
|
||||
if (value.startsWith('+')) {
|
||||
data.cell.styles.textColor = [34, 197, 94]; // green
|
||||
data.cell.styles.fontStyle = 'bold';
|
||||
} else if (value.startsWith('-') && value !== '-') {
|
||||
data.cell.styles.textColor = [239, 68, 68]; // red
|
||||
data.cell.styles.fontStyle = 'bold';
|
||||
}
|
||||
}
|
||||
},
|
||||
margin: { left: 15, right: 15 }
|
||||
// Generate PDF using common template
|
||||
const fileName = `Zeiterfassung_${monthName.replace(' ', '_')}.pdf`;
|
||||
await generatePDF({
|
||||
title: 'Zeiterfassung',
|
||||
subtitle: monthName,
|
||||
tableData: allDaysData,
|
||||
statistics: { targetHours, totalNetHours, balance: monthBalance },
|
||||
additionalInfo: { vacationDays, flextimeDays },
|
||||
fileName
|
||||
});
|
||||
|
||||
// Footer with generation date
|
||||
const finalY = doc.lastAutoTable.finalY || yPos + 50;
|
||||
if (finalY < 270) {
|
||||
doc.setTextColor(156, 163, 175);
|
||||
doc.setFontSize(8);
|
||||
doc.text(`Erstellt am: ${new Date().toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}`, 105, 285, { align: 'center' });
|
||||
}
|
||||
|
||||
// Save PDF
|
||||
const fileName = `Zeiterfassung_${monthName.replace(' ', '_')}.pdf`;
|
||||
doc.save(fileName);
|
||||
|
||||
showNotification('PDF erfolgreich erstellt', 'success');
|
||||
showNotification(`PDF für ${monthName} erstellt`, 'success');
|
||||
} catch (error) {
|
||||
console.error('Error exporting PDF:', error);
|
||||
showNotification('Fehler beim PDF-Export', 'error');
|
||||
|
||||
Reference in New Issue
Block a user