Target Date must be after the Start Date.
`;
return;
}
const requiredContribution = calculateContribution(
targetAmount,
initialDeposit,
interestRate,
totalMonths
);
// Final sanity check for display
const pmtDisplay = Math.max(0, requiredContribution);
const totalPrincipalNeeded = targetAmount - initialDeposit;
const interestEarned = totalPrincipalNeeded - (pmtDisplay * totalMonths);
// Populate dashboard
dashboardOutput.innerHTML = `
Calculation Summary
Time to Goal:
${totalMonths} months (${(totalMonths / 12).toFixed(1)} years)
Total Principal Needed:
$${(targetAmount - initialDeposit).toFixed(2)}
Est. Interest Earned:
$${(interestEarned > 0 ? interestEarned : 0).toFixed(2)}
REQUIRED MONTHLY CONTRIBUTION
Monthly Deposit:
$${pmtDisplay.toFixed(2)}
`;
// Attach listeners to newly created dashboard inputs for live editing
dashboardOutput.querySelectorAll('input').forEach(input => {
input.addEventListener('change', handleDashboardUpdate);
});
}
/**
* Updates config fields when dashboard fields are edited
*/
function handleDashboardUpdate(e) {
const input = e.target;
const value = input.value;
const id = input.id;
if (id === 'dash-goal-name') goalNameInput.value = value;
else if (id === 'dash-target-amount') targetAmountInput.value = value;
else if (id === 'dash-initial-deposit') initialDepositInput.value = value;
else if (id === 'dash-interest-rate') interestRateInput.value = value;
// Recalculate immediately after any change
handleGenerate();
}
/**
* Generates a PDF report from the dashboard data
*/
function downloadPDF() {
if (typeof window.jspdf === 'undefined') {
alert("Error: PDF library (jsPDF) could not be loaded. Please try again.");
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF("p", "pt", "a4");
const margin = 40;
let yPos = margin;
const lineHeight = 18;
// Get values directly from the (potentially edited) dashboard inputs
const name = sgcContainer.querySelector("#dash-goal-name")?.value || "Savings Goal";
const target = sgcContainer.querySelector("#dash-target-amount")?.value || "0";
const initial = sgcContainer.querySelector("#dash-initial-deposit")?.value || "0";
const rate = sgcContainer.querySelector("#dash-interest-rate")?.value || "0";
const monthly = sgcContainer.querySelector(".sgc-result-box .value")?.textContent || "$0.00";
const months = sgcContainer.querySelector(".sgc-result-box:nth-child(2) .value:first-of-type")?.textContent || "0 months";
const interestEarned = sgcContainer.querySelector(".sgc-result-box:nth-child(2) .value:last-of-type")?.textContent || "$0.00";
doc.setFontSize(18);
doc.setFont(undefined, 'bold');
doc.text(`Savings Goal Plan: ${name}`, margin, yPos);
yPos += lineHeight * 2;
// Section 1: Parameters
doc.setFontSize(14);
doc.text("1. Goal Parameters", margin, yPos);
yPos += lineHeight;
doc.setFontSize(11);
doc.setFont(undefined, 'normal');
doc.text(`Target Amount: $${parseFloat(target).toFixed(2)}`, margin, yPos);
doc.text(`Initial Deposit: $${parseFloat(initial).toFixed(2)}`, margin + 200, yPos);
yPos += lineHeight;
doc.text(`Annual Rate: ${rate}%`, margin, yPos);
doc.text(`Time to Goal: ${months}`, margin + 200, yPos);
yPos += lineHeight * 2;
// Section 2: Results
doc.setFontSize(14);
doc.setFont(undefined, 'bold');
doc.text("2. Calculation Results", margin, yPos);
yPos += lineHeight;
doc.setFontSize(12);
doc.setFont(undefined, 'normal');
doc.text(`Estimated Interest Earned: ${interestEarned}`, margin, yPos);
yPos += lineHeight * 2;
doc.setFontSize(16);
doc.setFont(undefined, 'bold');
doc.text("Required Monthly Contribution:", margin, yPos);
yPos += lineHeight * 1.5;
doc.setFontSize(22);
doc.setTextColor(0, 115, 230); // Blue color
doc.text(`${monthly}`, margin, yPos);
doc.save(`${name.replace(/ /g,"_")}_Savings_Report.pdf`);
}
/**
* Helper to escape HTML
*/
function escapeHTML(str) {
if (!str) return "";
return str
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// --- 3. INITIALIZATION & EVENT LISTENERS ---
// Tab Listeners
tabButtons.forEach((btn) => {
btn.addEventListener("click", () => showTab(btn.dataset.target));
});
navButtons.forEach((btn) => {
btn.addEventListener("click", () => showTab(btn.dataset.target));
});
// Config Tab Listeners
if (generateBtn) {
generateBtn.addEventListener("click", handleGenerate);
}
// Dashboard Tab Listeners
if (recalculateBtn) {
recalculateBtn.addEventListener("click", handleGenerate);
}
if (pdfBtn) {
pdfBtn.addEventListener("click", downloadPDF);
}
// Set default dates
const today = new Date();
const futureDate = new Date();
futureDate.setFullYear(today.getFullYear() + 3); // 3 years out
if (startDateInput) startDateInput.valueAsDate = today;
if (targetDateInput) targetDateInput.valueAsDate = futureDate;
// --- Initial State FIX ---
// 1. Run the generation function to populate the dashboard with defaults
// This addresses the "empty tool" issue directly.
handleGenerate();
// 2. Ensure the dashboard tab is active
showTab("sgc-tab-dashboard");
});