Debt Snowball Budgeting Calculator
Total Amount Paid: $${data.totalAmountPaid.toFixed(2)}
`;
let tableHTML = `
Debt Payoff Order & Details
| Order |
Debt Name |
Starting Balance |
Payoff Month # |
Est. Payoff Date |
Total Paid to Debt |
Total Interest on Debt |
`;
data.payoffDetails.forEach(debt => {
tableHTML += `
| ${debt.order} |
${debt.name} |
$${debt.originalBalance.toFixed(2)} |
${debt.payoffMonthNum} |
${debt.payoffDate} |
$${debt.totalPaidToDebt.toFixed(2)} |
$${debt.totalInterestOnDebt.toFixed(2)} |
`;
});
tableHTML += `
`;
tableContainer.innerHTML = tableHTML;
// Populate hidden monthly log for PDF
let logText = "";
data.monthlyLog.forEach(monthEntry => {
logText += `Month ${monthEntry.month}:\n`;
logText += ` Total Paid: $${monthEntry.totalPaymentThisMonth.toFixed(2)}, Total Interest: $${monthEntry.totalInterestThisMonth.toFixed(2)}\n`;
monthEntry.payments.forEach(p => {
logText += ` - ${p.name}: Paid $${p.payment.toFixed(2)} (Interest: $${p.interest.toFixed(2)}, Principal: $${p.principal.toFixed(2)}), New Bal: $${p.endBalance}\n`;
});
monthEntry.events.forEach(event => logText += ` EVENT: ${event}\n`);
logText += "\n";
});
logContainer.textContent = logText;
}
function downloadSnowballPdf() {
if (!snowballPlanData || Object.keys(snowballPlanData).length === 0) {
alert("Please calculate a plan first.");
return;
}
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') {
alert('PDF generation library (jsPDF) is not loaded.'); return;
}
const jsPDFConstructor = window.jspdf.jsPDF;
const doc = new jsPDFConstructor();
if (typeof doc.autoTable !== 'function') {
alert('jsPDF AutoTable plugin not loaded.'); return;
}
const data = snowballPlanData;
const primaryColor = '#007bff';
const textColor = '#212529';
const tableHeaderColor = '#e9ecef';
let yPos = 22;
doc.setFontSize(18); doc.setTextColor(primaryColor);
doc.text("Debt Snowball Payoff Plan", 14, yPos); yPos += 8;
doc.setFontSize(10); doc.setTextColor(textColor);
doc.text(`Report Date: ${new Date().toLocaleDateString()}`, 14, yPos); yPos += 10;
// Initial Inputs Summary
doc.setFontSize(12); doc.setTextColor(primaryColor);
doc.text("Initial Debt Summary", 14, yPos); yPos += 6;
const initialDebtsBody = data.initialDebts.map(d => [
d.name,
`$${d.balance.toFixed(2)}`,
`$${d.minPayment.toFixed(2)}`,
`${d.annualRate.toFixed(1)}%`
]);
doc.autoTable({
startY: yPos, theme: 'grid',
head: [['Debt Name', 'Balance', 'Min. Payment', 'Rate']],
body: initialDebtsBody,
headStyles: { fillColor: tableHeaderColor, textColor: textColor, fontStyle: 'bold' },
styles: { fontSize: 9 }
});
yPos = doc.lastAutoTable.finalY + 5;
doc.setFontSize(10); doc.setTextColor(textColor);
doc.text(`Extra Monthly Payment: $${data.extraMonthlyPayment.toFixed(2)}`, 14, yPos); yPos += 10;
// Overall Payoff Summary
const years = Math.floor(data.totalMonths / 12);
const remainingMonths = data.totalMonths % 12;
let timeToDebtFree = "";
if (years > 0) timeToDebtFree += `${years} year${years > 1 ? 's' : ''}`;
if (remainingMonths > 0) timeToDebtFree += `${years > 0 ? ' and ' : ''}${remainingMonths} month${remainingMonths > 1 ? 's' : ''}`;
if (timeToDebtFree === "") timeToDebtFree = "0 months";
doc.setFontSize(12); doc.setTextColor(primaryColor);
doc.text("Overall Payoff Summary", 14, yPos); yPos += 6;
doc.autoTable({
startY: yPos, theme: 'grid',
body: [
['Time to Debt-Free:', `${timeToDebtFree} (${data.totalMonths} months)`],
['Total Initial Debt Principal:', `$${data.totalInitialPrincipal.toFixed(2)}`],
['Total Interest Paid:', `$${data.totalInterestPaid.toFixed(2)}`],
['Total Amount Paid:', `$${data.totalAmountPaid.toFixed(2)}`]
],
styles: { fontSize: 9 },
columnStyles: { 0: { fontStyle: 'bold' } }
});
yPos = doc.lastAutoTable.finalY + 10;
// Debt Payoff Details Table
doc.setFontSize(12); doc.setTextColor(primaryColor);
doc.text("Debt Payoff Order & Details", 14, yPos); yPos += 6;
const payoffDetailsBody = data.payoffDetails.map(d => [
d.order, d.name, `$${d.originalBalance.toFixed(2)}`,
d.payoffMonthNum, d.payoffDate,
`$${d.totalPaidToDebt.toFixed(2)}`,
`$${d.totalInterestOnDebt.toFixed(2)}`
]);
doc.autoTable({
startY: yPos, theme: 'grid',
head: [['Order', 'Debt Name', 'Start Bal.', 'Payoff Month #', 'Est. Payoff Date', 'Total Paid', 'Total Interest']],
body: payoffDetailsBody,
headStyles: { fillColor: tableHeaderColor, textColor: textColor, fontStyle: 'bold' },
styles: { fontSize: 8, cellPadding: 1.5 }
});
yPos = doc.lastAutoTable.finalY + 10;
// Monthly Log (simplified for PDF to avoid excessive length)
doc.setFontSize(12); doc.setTextColor(primaryColor);
doc.text("Monthly Payment Log Summary", 14, yPos); yPos += 6;
doc.setFontSize(8); doc.setTextColor(textColor);
const logLines = document.getElementById('monthlyLogContent').textContent.split('\n');
let currentLine = 0;
const maxLinesPerPage = 50; // Adjust as needed
function addLogPage() {
if (currentLine > 0) doc.addPage(); // Add new page if not the first log page
yPos = 20; // Reset yPos for new page
doc.setFontSize(12); doc.setTextColor(primaryColor);
doc.text("Monthly Payment Log Summary (Continued)", 14, yPos); yPos += 6;
doc.setFontSize(8); doc.setTextColor(textColor);
}
for(let i=0; i < logLines.length; i++){
if( (i % maxLinesPerPage === 0 && i > 0) || yPos > 270) { // Check yPos for overflow too
addLogPage();
}
if (logLines[i].trim() !== "") { // Avoid empty lines if any
doc.text(logLines[i], 14, yPos);
yPos += 3.5; // Adjust line height
}
currentLine++;
}
doc.save("Debt_Snowball_Plan.pdf");
}