`;
html += ``;
html += ``;
html += ``;
html += `
`;
html += `Total Amount Saved (No Investing): ${formatCurrency(data.summary.totalAmountSaved)}
Potential Value if Invested at ${data.inputs.expectedReturnRatePct.toFixed(1)}%: ${formatCurrency(data.summary.totalValueIfInvested)}
Additional Growth from Investing: ${formatCurrency(data.summary.totalGrowthFromInvesting)}
Year-by-Year Projection:
`; if(data.projectionTable.length > 0) { html += ``;
html += `
`;
} else {
html += `| Year | Annual Saving | Cumulative Savings (Not Invested) | Investment Growth This Year | Total Value (If Invested) |
|---|---|---|---|---|
| ${row.year} | ${formatCurrency(row.annualSaving)} | ${formatCurrency(row.cumulativeSavingsOnly)} | ${formatCurrency(row.investmentGrowth)} | ${formatCurrency(row.endValueIfInvested)} |
No projection data to display.
`; } html += ``; impactResultsDiv.innerHTML = html; } if (calculateImpactBtn) { calculateImpactBtn.addEventListener('click', runImpactCalculation); } // --- PDF Download --- function loadJsPdfIfNeeded(callback) { if (jsPdfLoaded) { if (callback) callback(); return; } const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js'; script.onload = () => { jsPdfLoaded = true; console.log("jsPDF loaded dynamically."); if (callback) callback(); }; script.onerror = () => { console.error("Failed to load jsPDF."); alert("Error: Could not load PDF library."); }; document.head.appendChild(script); } function downloadReportAsPdf() { if (!jsPdfLoaded) { alert("PDF library not loaded."); return; } if (!impactDataForPdf) { alert("No projection data to download. Please run the calculation first."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF({ unit: 'pt', format: 'a4' }); const data = impactDataForPdf; const pageMargin = 35; const pageWidth = doc.internal.pageSize.getWidth() - 2 * pageMargin; let y = pageMargin; function addMainTitle(text) { doc.setFontSize(16); doc.setFont(undefined, 'bold'); doc.setTextColor(0, 105, 92); // Primary doc.text(text, doc.internal.pageSize.getWidth() / 2, y, { align: 'center' }); y += 30; } function addSectionTitle(text) { if (y > doc.internal.pageSize.getHeight() - 70) { doc.addPage(); y = pageMargin; } doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.setTextColor(0, 137, 123); // Secondary doc.text(text, pageMargin, y); y += 20; } function addLine(key, value, valueIsHighlight = false, highlightColor = [67,160,71]) { if (y > doc.internal.pageSize.getHeight() - 35) { doc.addPage(); y = pageMargin; } doc.setFontSize(9); doc.setFont(undefined, 'bold'); doc.setTextColor(33,33,33); doc.text(key, pageMargin, y); doc.setFont(undefined, valueIsHighlight ? 'bold' : 'normal'); doc.setTextColor(valueIsHighlight ? highlightColor[0] : 33, valueIsHighlight ? highlightColor[1] : 33, valueIsHighlight ? highlightColor[2] : 33); const valueText = String(value); doc.text(valueText, pageMargin + 250, y, { align: 'left', maxWidth: pageWidth - 250 - 5 }); y += 16; } function addInfo(text) { if (y > doc.internal.pageSize.getHeight() - 45) { doc.addPage(); y = pageMargin; } doc.setFontSize(8); doc.setFont(undefined, 'italic'); doc.setTextColor(100,100,100); const splitText = doc.splitTextToSize(text, pageWidth); doc.setFillColor(245,245,245); doc.rect(pageMargin -5, y - (doc.getTextDimensions(splitText).h / 2) - 2 , pageWidth + 10, doc.getTextDimensions(splitText).h + 8, 'F'); doc.text(splitText, pageMargin, y); y += (doc.getTextDimensions(splitText).h) + 12; } function addTable(headers, tableData, columnWidths) { if (y > doc.internal.pageSize.getHeight() - 50) { doc.addPage(); y = pageMargin; } doc.setFontSize(8); const headerFillColor = [0, 105, 92]; const headerTextColor = [255,255,255]; const rowTextColor = [33,33,33]; doc.setFillColor(headerFillColor[0], headerFillColor[1], headerFillColor[2]); doc.setTextColor(headerTextColor[0], headerTextColor[1], headerTextColor[2]); doc.setFont(undefined, 'bold'); let currentX = pageMargin; headers.forEach((header, i) => { doc.rect(currentX, y, columnWidths[i], 18, 'F'); doc.text(header, currentX + 3, y + 12); currentX += columnWidths[i]; }); y += 18; doc.setTextColor(rowTextColor[0], rowTextColor[1], rowTextColor[2]); doc.setFont(undefined, 'normal'); tableData.forEach((rowArray) => { if (y > doc.internal.pageSize.getHeight() - 30) { doc.addPage(); y = pageMargin; currentX = pageMargin; doc.setFillColor(headerFillColor[0], headerFillColor[1], headerFillColor[2]); doc.setTextColor(headerTextColor[0], headerTextColor[1], headerTextColor[2]); doc.setFont(undefined, 'bold'); headers.forEach((header, i) => { doc.rect(currentX, y, columnWidths[i], 18, 'F'); doc.text(header, currentX + 3, y + 12); currentX += columnWidths[i]; }); y += 18; doc.setTextColor(rowTextColor[0], rowTextColor[1], rowTextColor[2]); doc.setFont(undefined, 'normal'); } currentX = pageMargin; rowArray.forEach((cell, i) => { doc.rect(currentX, y, columnWidths[i], 16); const cellText = String(cell); const textLines = doc.splitTextToSize(cellText, columnWidths[i] - 6); doc.text(textLines, currentX + 3, y + 11); currentX += columnWidths[i]; }); y += 16; }); y += 8; } addMainTitle("Expense Reduction vs. Investment Growth Report"); addInfo(`Report Generated: ${new Date().toLocaleString()}`); y += 5; addSectionTitle("Input Parameters"); addLine("Monthly Expense Reduction:", formatCurrency(data.inputs.monthlyReduction)); addLine("Number of Years Maintained:", `${data.inputs.yearsMaintained} years`); addLine("Expected Avg. Annual Rate of Return:", `${data.inputs.expectedReturnRatePct.toFixed(1)}%`); y += 10; addSectionTitle("Impact Summary"); addLine("Total Amount Saved (No Investing):", formatCurrency(data.summary.totalAmountSaved)); addLine("Potential Value if Savings Were Invested:", formatCurrency(data.summary.totalValueIfInvested), true, [67,160,71]); addLine("Additional Growth from Investing:", formatCurrency(data.summary.totalGrowthFromInvesting), true, [67,160,71]); y += 10; if (data.projectionTable.length > 0) { addSectionTitle("Year-by-Year Projection"); const tableH = ["Year", "Annual Saving", "Cumulative Savings", "Investment Growth", "Total Value (Invested)"]; const tableCW = [40, 100, 110, 100, 110]; const tableFData = data.projectionTable.map(r => [ r.year, formatCurrency(r.annualSaving,0,0), formatCurrency(r.cumulativeSavingsOnly,0,0), formatCurrency(r.investmentGrowth,0,0), formatCurrency(r.endValueIfInvested,0,0) ]); addTable(tableH, tableFData, tableCW); } addInfo("This projection assumes annual savings are invested at the beginning of each year and compounded annually at the specified rate of return. Actual investment performance will vary. This tool is for illustrative and educational purposes only."); const pageCount = doc.internal.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); doc.setFontSize(7); doc.setTextColor(150); doc.text(`Page ${i} of ${pageCount} - Expense Reduction vs. Investment Growth Calculator`, pageMargin, doc.internal.pageSize.getHeight() - 15); } doc.save('Expense_Reduction_Investment_Impact.pdf'); } if (downloadPdfBtn) { downloadPdfBtn.addEventListener('click', () => loadJsPdfIfNeeded(downloadReportAsPdf)); } // --- Initialization --- showTab(0); if (downloadPdfBtn) downloadPdfBtn.disabled = true; if (pdfButtonContainer) pdfButtonContainer.style.display = 'none'; // Hide PDF button container initially loadJsPdfIfNeeded(); });