With Early Payments
Your Total Monthly Payment: ${slis_formatCurrency(yourTotalMonthlyPayment)}
Total Interest Paid: ${slis_formatCurrency(newTotalInterestPaid)}
New Payoff Time: ${Math.floor(newPayoffMonths/12)} yrs, ${newPayoffMonths % 12} mos
Est. New Payoff Date: ${newPayoffDate}
Total Interest Saved by Early Payments:
${slis_formatCurrency(totalInterestSaved)}
Time Saved on Loan: ${timeSavedString}
`;
slis_populateAmortizationTable('slis-original-amortization-table', originalSchedule, false);
slis_populateAmortizationTable('slis-accelerated-amortization-table', acceleratedSchedule, true);
const origAmortButton = document.getElementById('slis-show-original-amortization');
if (origAmortButton) origAmortButton.click(); // Default to show original schedule first
slis_navigateToTab(2); // Go to results tab
}
function slis_populateAmortizationTable(tableId, scheduleData, isAccelerated) {
const tableBody = document.getElementById(tableId)?.getElementsByTagName('tbody')[0];
if (!tableBody) {
console.error("SLIS Error: Amortization table body not found for ID:", tableId);
return;
}
tableBody.innerHTML = '';
if (!scheduleData || scheduleData.length === 0) {
const tr = tableBody.insertRow();
const cellCount = isAccelerated ? 7 : 6;
const td = tr.insertCell();
td.colSpan = cellCount;
td.textContent = "No schedule data to display.";
td.style.textAlign = "center";
return;
}
scheduleData.forEach(row => {
const tr = tableBody.insertRow();
tr.insertCell().textContent = row.month;
tr.insertCell().textContent = row.date;
tr.insertCell().textContent = slis_formatCurrency(row.payment);
// For accelerated schedule, principal shown is the portion from standard payment + additional payment
// The amortization generation function already provides 'principal from standard payment' and 'additional'
// Let's ensure the columns in the table match what generateAmortizationSchedule provides.
// The provided generateAmortizationSchedule returns:
// principal: totalPrincipalApplied - actualAdditionalPaymentApplied (this is principal from standard portion)
// additional: actualAdditionalPaymentApplied
// So, for the "Principal" column in the accelerated table, it should be (row.principal + row.additional) or just totalPrincipalApplied.
// Let's adjust generateAmortizationSchedule to output `principalApplied` and `standardPrincipalPortion` if needed,
// or adjust table display logic.
// Current output of `generateAmortizationSchedule`:
// row.principal = principal portion from standard payment
// row.additional = additional payment applied
// So, total principal paid in a month = row.principal + row.additional
if (isAccelerated) {
tr.insertCell().textContent = slis_formatCurrency(row.principal + row.additional); // Total Principal Paid
} else {
tr.insertCell().textContent = slis_formatCurrency(row.principal); // Principal (Original schedule has no 'additional' column)
}
tr.insertCell().textContent = slis_formatCurrency(row.interest);
if (isAccelerated) {
tr.insertCell().textContent = slis_formatCurrency(row.additional);
}
tr.insertCell().textContent = slis_formatCurrency(row.balance);
});
}
function slis_generatePDF() {
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') {
alert('PDF generation library (jsPDF) is not loaded.'); return;
}
const JSPDF_CONSTRUCTOR = window.jspdf.jsPDF;
const tempDoc = new JSPDF_CONSTRUCTOR();
if (typeof tempDoc.autoTable !== 'function') {
alert('jsPDF AutoTable plugin is not loaded.'); return;
}
if (Object.keys(slis_loanDataForPdf).length === 0) {
alert('Please calculate savings first to generate a PDF report.'); return;
}
const doc = new JSPDF_CONSTRUCTOR({ orientation: 'p' });
const data = slis_loanDataForPdf;
let yPos = 15;
const pageMargin = 15;
const pageWidth = doc.internal.pageSize.getWidth();
const usableWidth = pageWidth - (2 * pageMargin);
const primaryColor = [0, 119, 204]; // #0077cc
const headingColor = [0, 94, 166]; // #005ea6
const textColor = [61, 79, 96]; // #3d4f60
const savingsColor = [40, 167, 69]; // #28a745
function addMainHeader(text) {
doc.setFontSize(16);
doc.setTextColor(headingColor[0], headingColor[1], headingColor[2]);
doc.text(text, pageWidth / 2, yPos, { align: 'center' });
yPos += 12;
}
function addSectionHeader(text) {
if (yPos > doc.internal.pageSize.getHeight() - 30) { doc.addPage(); yPos = pageMargin; }
doc.setFontSize(12);
doc.setFont(undefined, 'bold');
doc.setTextColor(primaryColor[0], primaryColor[1], primaryColor[2]);
doc.text(text, pageMargin, yPos);
yPos += 8;
doc.setFont(undefined, 'normal');
}
addMainHeader("Student Loan Interest Savings Report");
// Inputs Summary
addSectionHeader("Your Loan & Early Payment Inputs:");
const inputsForPdf = [
["Current Loan Balance:", slis_formatCurrency(data.loanBalance)],
["Annual Interest Rate:", `${data.annualInterestRate.toFixed(2)}%`],
["Original Loan Term:", `${data.originalLoanTermYears} years`],
["Loan Start Date:", data.startDateString ? slis_formatDate(new Date(data.startDateString.replace(/-/g,'/'))) : 'N/A'], // Ensure date is parsed for formatting
["Loan Type:", data.loanType.charAt(0).toUpperCase() + data.loanType.slice(1)],
["Additional Monthly Payment:", slis_formatCurrency(data.additionalMonthlyPayment)],
];
doc.autoTable({
startY: yPos, body: inputsForPdf, theme: 'grid',
styles: { fontSize: 9, cellPadding: 2.5, textColor: textColor },
headStyles: {fillColor: primaryColor},
columnStyles: { 0: { fontStyle: 'bold', cellWidth: usableWidth * 0.45 }, 1 : { cellWidth: usableWidth * 0.55} },
didDrawPage: function (hookData){ yPos = hookData.cursor.y ? hookData.cursor.y +10 : pageMargin; }
});
yPos = doc.lastAutoTable.finalY ? doc.lastAutoTable.finalY + 10 : yPos;
// Savings Summary
if (yPos > doc.internal.pageSize.getHeight() - 80) { doc.addPage(); yPos = pageMargin; }
addSectionHeader("Savings & Comparison Summary:");
const summaryForPdf = [
["Metric", "Standard Repayment", "With Early Payments"],
["Monthly Payment", slis_formatCurrency(data.standardMonthlyPayment), slis_formatCurrency(data.yourTotalMonthlyPayment)],
["Total Interest Paid", slis_formatCurrency(data.originalTotalInterestPaid), slis_formatCurrency(data.newTotalInterestPaid)],
["Payoff Time", `${Math.floor(data.originalPayoffMonths/12)} yrs, ${data.originalPayoffMonths % 12} mos`, `${Math.floor(data.newPayoffMonths/12)} yrs, ${data.newPayoffMonths % 12} mos`],
["Est. Payoff Date", data.originalPayoffDate, data.newPayoffDate],
[{content: "Total Interest Saved:", colSpan:2, styles: {fontStyle:'bold', halign:'left', textColor: savingsColor}}, {content: slis_formatCurrency(data.totalInterestSaved), styles:{fontStyle:'bold', textColor: savingsColor, halign:'right'}}],
[{content: "Time Saved on Loan:", colSpan:2, styles: {fontStyle:'bold', halign:'left'}}, {content: data.timeSavedString, styles:{fontStyle:'bold', halign:'right'}}]
];
doc.autoTable({
startY: yPos,
head: [summaryForPdf[0]],
body: summaryForPdf.slice(1),
theme: 'striped',
headStyles: { fillColor: primaryColor, textColor: [255,255,255], fontStyle: 'bold' },
styles: { fontSize: 9, cellPadding: 2, textColor: textColor },
didParseCell: function(hookData) {
if (hookData.row.index >= summaryForPdf.length - 3) { // Last two rows (Interest Saved, Time Saved)
if(hookData.cell.raw.hasOwnProperty('styles')){ // Check if custom styles object exists
Object.assign(hookData.cell.styles, hookData.cell.raw.styles);
}
}
},
didDrawPage: function (hookData){ yPos = hookData.cursor.y ? hookData.cursor.y +10 : pageMargin; }
});
yPos = doc.lastAutoTable.finalY ? doc.lastAutoTable.finalY + 10 : yPos;
// Accelerated Amortization Schedule
if (data.acceleratedSchedule && data.acceleratedSchedule.length > 0) {
if (yPos > doc.internal.pageSize.getHeight() - 40) { doc.addPage(); yPos = pageMargin; }
addSectionHeader("Accelerated Amortization Schedule (With Early Payments):");
const amortBody = data.acceleratedSchedule.map(row => [
row.month, row.date,
slis_formatCurrency(row.payment),
slis_formatCurrency(row.principal + row.additional), // Total principal paid
slis_formatCurrency(row.interest),
slis_formatCurrency(row.additional),
slis_formatCurrency(row.balance)
]);
doc.autoTable({
startY: yPos,
head: [['Month', 'Date', 'Total Pmt', 'Principal Paid', 'Interest', 'Extra Pmt', 'Balance']],
body: amortBody, theme: 'striped',
headStyles: { fillColor: primaryColor, textColor: [255,255,255], fontStyle: 'bold' },
styles: { fontSize: 7.5, cellPadding: 1.5, halign: 'right', textColor: textColor },
columnStyles: { 0: { halign: 'center' }, 1: { halign: 'left'} },
didDrawPage: function (hookData){ yPos = hookData.cursor.y ? hookData.cursor.y +10 : pageMargin; }
});
}
doc.save("Student_Loan_Interest_Savings_Report.pdf");
}