';
downloadPdfBtn.style.display = 'block'; // Show PDF button
// Store data for PDF generation
resultsAreaDiv.dataset.inputs = JSON.stringify({
startingBalance: startingBalance,
startingDate: startingDate,
calculateAsOfDate: calculateAsOfDate,
accrualEvents: events.filter(e => e.type === 'accrual').map(e => ({ date: e.date.toISOString().split('T')[0], hours: e.hours })), // Store as simple objects with date strings
usageEvents: events.filter(e => e.type === 'usage').map(e => ({ date: e.date.toISOString().split('T')[0], hours: e.hours }))
});
resultsAreaDiv.dataset.history = JSON.stringify(history.map(entry => ({ // Store history with date strings
date: entry.date.toISOString().split('T')[0],
event: entry.event,
change: entry.change,
balance: entry.balance
})));
}
// Event listener for calculate button
calculateBtn.addEventListener('click', calculateBalance);
// Trigger calculation when switching TO the results tab
document.getElementById('results').addEventListener('transitionend', function() {
if (this.classList.contains('active')) {
calculateBalance();
}
});
// --- PDF Generation ---
downloadPdfBtn.addEventListener('click', function() {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
const inputs = JSON.parse(resultsAreaDiv.dataset.inputs || '{}');
const history = JSON.parse(resultsAreaDiv.dataset.history || '[]');
if (!inputs || history.length === 0) {
alert("No results to download. Please calculate first.");
return;
}
// Re-parse dates for PDF display
history.forEach(entry => { entry.date = new Date(entry.date); });
// --- PDF Styling (Matching CSS color scheme) ---
const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim();
const textColor = getComputedStyle(document.documentElement).getPropertyValue('--text-color').trim();
const outputBackgroundColor = getComputedStyle(document.documentElement).getPropertyValue('--output-background').trim();
const white = '#ffffff';
const balanceColor = getComputedStyle(document.documentElement).getPropertyValue('--balance-color').trim();
const accrualColor = getComputedStyle(document.documentElement).getPropertyValue('--accrual-color').trim();
const usageColor = getComputedStyle(document.documentElement).getPropertyValue('--usage-color').trim();
doc.setFontSize(18);
doc.setTextColor(primaryColor);
doc.text("PTO Balance History", 14, 22);
doc.setFontSize(10);
doc.setTextColor('#555');
doc.text("Based on user-provided inputs. Data is not saved.", 14, 30);
let yPos = 40;
// Summary of Inputs in PDF
doc.setFontSize(12);
doc.setTextColor(primaryColor);
doc.text("Summary of Inputs:", 14, yPos);
yPos += 7;
doc.setFontSize(10);
doc.setTextColor(textColor);
doc.text(`Starting Balance: ${formatHours(inputs.startingBalance)} as of ${new Date(inputs.startingDate).toLocaleDateString()}`, 14, yPos);
yPos += 7;
if (inputs.calculateAsOfDate) {
doc.text(`Balance Calculated As Of Date: ${new Date(inputs.calculateAsOfDate).toLocaleDateString()}`, 14, yPos);
yPos += 7;
}
yPos += 5; // Little space before events list
// Event Lists in PDF (Optional, could just show history table)
// Let's show the history table as the main output as it includes the balance changes.
// Balance History Table in PDF
yPos += 10; // Space before table
doc.setFontSize(12);
doc.setTextColor(primaryColor);
doc.text("Balance History Timeline:", 14, yPos);
const historyTableBody = history.map((entry, index, arr) => {
const isFinalRow = index === arr.length - 1 || (inputs.calculateAsOfDate && entry.date.toLocaleDateString() === new Date(inputs.calculateAsOfDate).toLocaleDateString() && index === arr.length - 1) ; // Check if it's the very last calculated point
return [
entry.date.toLocaleDateString(),
entry.event,
entry.change >= 0 ? '+' + formatHours(entry.change) : formatHours(entry.change), // Format change with sign
formatHours(entry.balance),
isFinalRow ? 'final-balance-row' : '' // Add class indicator for styling
];
});
doc.autoTable({
startY: yPos + 5,
head: [['Date', 'Event', 'Change', 'Balance']],
body: historyTableBody.length > 1 ? historyTableBody.map(row => row.slice(0, -1)) : [['No events after starting date', '', '', formatHours(inputs.startingBalance)]], // Show starting balance if no events
theme: 'grid',
styles: {
textColor: textColor.substring(1),
lineColor: '#cccccc',
lineWidth: 0.1,
cellPadding: 3
},
headStyles: {
fillColor: primaryColor.substring(1),
textColor: white.substring(1),
fontStyle: 'bold'
},
bodyStyles: {
fillColor: outputBackgroundColor.substring(1),
textColor: textColor.substring(1)
},
alternateRowStyles: {
fillColor: white.substring(1)
},
columnStyles: {
1: { cellWidth: 'auto' } // Allow event description column to auto-size
},
rowStyles: historyTableBody.reduce((styles, row, index) => { // Apply row styles based on class indicator
if (row[4] === 'final-balance-row') {
styles[index] = { fillColor: '#c0e0ff', textColor: balanceColor.substring(1), fontStyle: 'bold' };
}
return styles;
}, {}),
margin: { top: yPos + 5 }
});
doc.save('pto_balance_history.pdf');
});
});
// Helper function to format hours (defined outside DOMContentLoaded for PDF function access)
function formatHours(hours) {
const numberHours = parseFloat(hours);
if (isNaN(numberHours) || !isFinite(numberHours)) {
return 'N/A';
}
return numberHours.toFixed(2) + ' hours';
}