No loans added. Please add loan details in Tab 2.
';
downloadPdfButton.disabled = true;
return;
}
let combinedYearlyTotals = {}; // year: { payments, principal, interest }
loansData.forEach(loan => {
html += `
${loan.name}
`;
const monthlyPayment = calculateMonthlyPayment(loan.principal, loan.annualRate, loan.termMonths);
html += `
Calculated Monthly Payment (P&I): ${currency}${monthlyPayment.toFixed(2)}
`;
const amortNoExtra = calculateAmortization(loan.principal, loan.annualRate, loan.termMonths, monthlyPayment, 0);
html += `
Original Total Interest: ${currency}${amortNoExtra.totalInterestPaid.toFixed(2)} | Original Payoff: ${Math.ceil(amortNoExtra.monthsToPayOff / 12)} yrs ${Math.ceil(amortNoExtra.monthsToPayOff % 12)} mos
`;
if (loan.extraMonthlyPayment > 0) {
const amortWithExtra = calculateAmortization(loan.principal, loan.annualRate, loan.termMonths, monthlyPayment, loan.extraMonthlyPayment);
html += `
Total Interest (with ${currency}${loan.extraMonthlyPayment.toFixed(2)} extra/mo): ${currency}${amortWithExtra.totalInterestPaid.toFixed(2)}
`;
html += `
Est. Payoff (with extra): ${Math.floor(amortWithExtra.monthsToPayOff / 12)} yrs ${amortWithExtra.monthsToPayOff % 12} mos (Savings: ${amortNoExtra.monthsToPayOff - amortWithExtra.monthsToPayOff} mos)
`;
}
// Display yearly breakdown
const displayAmort = loan.extraMonthlyPayment > 0 ? calculateAmortization(loan.principal, loan.annualRate, loan.termMonths, monthlyPayment, loan.extraMonthlyPayment) : amortNoExtra;
let budgetHorizonYears = budgetHorizonOption === "0" ? Math.ceil(displayAmort.monthsToPayOff / 12) : parseInt(budgetHorizonOption);
budgetHorizonYears = Math.min(budgetHorizonYears, 30); // Cap display for sanity
html += '
| Year | Total Payments | Principal Paid | Interest Paid | Ending Balance |
';
displayAmort.yearlyBreakdown.slice(0, budgetHorizonYears).forEach(yearData => {
html += `
| ${yearData.year} |
${currency}${yearData.payments.toFixed(2)} |
${currency}${yearData.principal.toFixed(2)} |
${currency}${yearData.interest.toFixed(2)} |
${currency}${yearData.endBalance.toFixed(2)} |
`;
// Aggregate for combined summary
if (!combinedYearlyTotals[yearData.year]) {
combinedYearlyTotals[yearData.year] = { payments: 0, principal: 0, interest: 0 };
}
combinedYearlyTotals[yearData.year].payments += yearData.payments;
combinedYearlyTotals[yearData.year].principal += yearData.principal;
combinedYearlyTotals[yearData.year].interest += yearData.interest;
});
html += `
`;
});
if (loansData.length > 1) {
html += `
Combined Loan Projections
`;
html += `
| Year | Total Combined Payments | Total Principal Paid | Total Interest Paid |
`;
let grandTotalPayments = 0, grandTotalPrincipal = 0, grandTotalInterest = 0;
let maxYearCombined = 0;
Object.keys(combinedYearlyTotals).forEach(yr => maxYearCombined = Math.max(maxYearCombined, parseInt(yr)));
let budgetHorizonYearsCombined = budgetHorizonOption === "0" ? maxYearCombined : parseInt(budgetHorizonOption);
budgetHorizonYearsCombined = Math.min(budgetHorizonYearsCombined, 30);
for(let y = 1; y <= budgetHorizonYearsCombined; y++) {
const yearData = combinedYearlyTotals[y] || { payments: 0, principal: 0, interest: 0 };
html += `
| ${y} |
${currency}${yearData.payments.toFixed(2)} |
${currency}${yearData.principal.toFixed(2)} |
${currency}${yearData.interest.toFixed(2)} |
`;
grandTotalPayments += yearData.payments;
grandTotalPrincipal += yearData.principal;
grandTotalInterest += yearData.interest;
}
html += `
| TOTALS |
${currency}${grandTotalPayments.toFixed(2)} |
${currency}${grandTotalPrincipal.toFixed(2)} |
${currency}${grandTotalInterest.toFixed(2)} |
`;
html += `
`;
}
summaryOutput.innerHTML = html;
downloadPdfButton.disabled = loansData.length === 0;
}
// PDF Download
downloadPdfButton.addEventListener('click', function () {
if (typeof jspdf === 'undefined' || typeof jspdf.jsPDF === 'undefined') {
alert('Error: jsPDF library is not loaded.'); return;
}
const { jsPDF } = jspdf;
const doc = new jsPDF('p');
const currency = currencySymbolInput.value || '$';
const budgetHorizonOption = document.getElementById('blrb-budget-horizon').value;
const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--blrb-primary-color').trim();
const secondaryColor = getComputedStyle(document.documentElement).getPropertyValue('--blrb-secondary-color').trim();
const textColor = getComputedStyle(document.documentElement).getPropertyValue('--blrb-text-color').trim();
const buttonTextColor = getComputedStyle(document.documentElement).getPropertyValue('--blrb-button-text-color').trim();
doc.setFontSize(18);
doc.setTextColor(primaryColor);
doc.text('Business Loan Repayment Plan', doc.internal.pageSize.getWidth() / 2, 20, { align: 'center' });
doc.setFontSize(12);
doc.setTextColor(textColor);
doc.text(`Business Name: ${document.getElementById('blrb-business-name').value || 'N/A'}`, 14, 30);
doc.text(`Currency: ${currency}`, 14, 37);
let budgetHorizonText = budgetHorizonOption === "0" ? "Full Loan Term (Max 30 Yrs Displayed)" : `${budgetHorizonOption} Years`;
doc.text(`Budget Horizon: ${budgetHorizonText}`, 14, 44);
let lastY = 55;
const loansDataForPdf = []; // Collect all data before iterating for PDF
document.querySelectorAll('.blrb-loan-item').forEach(item => {
const termValue = getNum(item.querySelector('.blrb-loan-term-value').value);
const termUnit = item.querySelector('.blrb-loan-term-unit').value;
loansDataForPdf.push({
name: item.querySelector('.blrb-loan-name').value || `Loan ${item.dataset.loanId}`,
principal: getNum(item.querySelector('.blrb-loan-principal').value),
annualRate: getNum(item.querySelector('.blrb-loan-rate').value),
termMonths: termUnit === 'Years' ? termValue * 12 : termValue,
extraMonthlyPayment: getNum(item.querySelector('.blrb-loan-extra-payment').value)
});
});
let combinedYearlyTotalsPdf = {};
loansDataForPdf.forEach(loan => {
if (lastY > 250) { doc.addPage(); lastY = 20; }
doc.setFontSize(14);
doc.setTextColor(primaryColor);
doc.text(loan.name, 14, lastY); lastY += 6;
doc.setFontSize(10);
doc.setTextColor(textColor);
doc.text(`Principal: ${currency}${loan.principal.toFixed(2)}, Rate: ${loan.annualRate.toFixed(2)}%, Term: ${loan.termMonths} months`, 14, lastY); lastY += 5;
if (loan.extraMonthlyPayment > 0) {
doc.text(`Extra Monthly Payment: ${currency}${loan.extraMonthlyPayment.toFixed(2)}`, 14, lastY); lastY += 5;
}
const monthlyPayment = calculateMonthlyPayment(loan.principal, loan.annualRate, loan.termMonths);
doc.text(`Calculated Monthly Payment (P&I): ${currency}${monthlyPayment.toFixed(2)}`, 14, lastY); lastY += 5;
const amortNoExtra = calculateAmortization(loan.principal, loan.annualRate, loan.termMonths, monthlyPayment, 0);
doc.text(`Original Total Interest: ${currency}${amortNoExtra.totalInterestPaid.toFixed(2)} | Original Payoff: ${Math.ceil(amortNoExtra.monthsToPayOff / 12)} yrs ${Math.ceil(amortNoExtra.monthsToPayOff % 12)} mos`, 14, lastY); lastY +=5;
if (loan.extraMonthlyPayment > 0) {
const amortWithExtra = calculateAmortization(loan.principal, loan.annualRate, loan.termMonths, monthlyPayment, loan.extraMonthlyPayment);
doc.text(`Total Interest (with extra): ${currency}${amortWithExtra.totalInterestPaid.toFixed(2)} | Est. Payoff (with extra): ${Math.floor(amortWithExtra.monthsToPayOff / 12)} yrs ${amortWithExtra.monthsToPayOff % 12} mos`, 14, lastY); lastY += 7;
} else {
lastY +=2; // little less space if no extra payment info
}
const displayAmort = loan.extraMonthlyPayment > 0 ? calculateAmortization(loan.principal, loan.annualRate, loan.termMonths, monthlyPayment, loan.extraMonthlyPayment) : amortNoExtra;
let budgetHorizonYears = budgetHorizonOption === "0" ? Math.ceil(displayAmort.monthsToPayOff / 12) : parseInt(budgetHorizonOption);
budgetHorizonYears = Math.min(budgetHorizonYears, 30);
const head = [['Year', 'Total Payments', 'Principal Paid', 'Interest Paid', 'Ending Balance']];
const body = [];
displayAmort.yearlyBreakdown.slice(0, budgetHorizonYears).forEach(yearData => {
body.push([
yearData.year.toString(),
`${currency}${yearData.payments.toFixed(2)}`,
`${currency}${yearData.principal.toFixed(2)}`,
`${currency}${yearData.interest.toFixed(2)}`,
`${currency}${yearData.endBalance.toFixed(2)}`
]);
// Aggregate for combined summary
if (!combinedYearlyTotalsPdf[yearData.year]) {
combinedYearlyTotalsPdf[yearData.year] = { payments: 0, principal: 0, interest: 0 };
}
combinedYearlyTotalsPdf[yearData.year].payments += yearData.payments;
combinedYearlyTotalsPdf[yearData.year].principal += yearData.principal;
combinedYearlyTotalsPdf[yearData.year].interest += yearData.interest;
});
doc.autoTable({ startY: lastY, head: head, body: body, theme: 'grid', headStyles: {fillColor: secondaryColor, textColor: buttonTextColor, fontSize:9}, styles:{fontSize:8, cellPadding:1.5}, columnStyles: {0:{halign:'center'},1:{halign:'right'},2:{halign:'right'},3:{halign:'right'},4:{halign:'right'}}});
lastY = doc.lastAutoTable.finalY + 10;
});
if (loansDataForPdf.length > 1) {
if (lastY > 250) { doc.addPage(); lastY = 20; }
doc.setFontSize(14); doc.setTextColor(primaryColor);
doc.text('Combined Loan Projections', 14, lastY); lastY += 7;
const combinedHead = [['Year', 'Total Combined Payments', 'Total Principal Paid', 'Total Interest Paid']];
const combinedBody = [];
let grandTotalPaymentsPdf = 0, grandTotalPrincipalPdf = 0, grandTotalInterestPdf = 0;
let maxYearCombinedPdf = 0;
Object.keys(combinedYearlyTotalsPdf).forEach(yr => maxYearCombinedPdf = Math.max(maxYearCombinedPdf, parseInt(yr)));
let budgetHorizonYearsCombinedPdf = budgetHorizonOption === "0" ? maxYearCombinedPdf : parseInt(budgetHorizonOption);
budgetHorizonYearsCombinedPdf = Math.min(budgetHorizonYearsCombinedPdf, 30);
for(let y = 1; y <= budgetHorizonYearsCombinedPdf; y++) {
const yearData = combinedYearlyTotalsPdf[y] || { payments: 0, principal: 0, interest: 0 };
combinedBody.push([
y.toString(),
`${currency}${yearData.payments.toFixed(2)}`,
`${currency}${yearData.principal.toFixed(2)}`,
`${currency}${yearData.interest.toFixed(2)}`
]);
grandTotalPaymentsPdf += yearData.payments;
grandTotalPrincipalPdf += yearData.principal;
grandTotalInterestPdf += yearData.interest;
}
combinedBody.push([ // Grand Total Row
{content: 'TOTALS', styles: {fontStyle: 'bold', fillColor: primaryColor, textColor: buttonTextColor, halign: 'center'}},
{content: `${currency}${grandTotalPaymentsPdf.toFixed(2)}`, styles: {fontStyle: 'bold', fillColor: primaryColor, textColor: buttonTextColor, halign: 'right'}},
{content: `${currency}${grandTotalPrincipalPdf.toFixed(2)}`, styles: {fontStyle: 'bold', fillColor: primaryColor, textColor: buttonTextColor, halign: 'right'}},
{content: `${currency}${grandTotalInterestPdf.toFixed(2)}`, styles: {fontStyle: 'bold', fillColor: primaryColor, textColor: buttonTextColor, halign: 'right'}}
]);
doc.autoTable({ startY: lastY, head: combinedHead, body: combinedBody, theme: 'grid', headStyles: {fillColor: secondaryColor, textColor: buttonTextColor, fontSize:9}, styles:{fontSize:8, cellPadding:1.5}, columnStyles: {0:{halign:'center'},1:{halign:'right'},2:{halign:'right'},3:{halign:'right'}}});
}
doc.save(`Business_Loan_Repayment_Plan_${document.getElementById('blrb-business-name').value.replace(/\s+/g, '_') || 'Report'}.pdf`);
});
checkEmptyState('#blrb-loans-container', 'blrb-no-items-text');
showTab(0);
updateAllCurrencyPrefixes();
});