Personalized Financial Milestone Budget Tracker
Setup & Milestones
Financial Milestones
Add New Milestone
Income & Expenses (Monthly)
Income
Add Income Source
Total Monthly Income: $0.00
Expenses
Add Expense Item
Total Monthly Expenses: $0.00
Net Monthly Savings: $0.00
Track Contributions
Contributions Log for: N/A
Summary & Overview
Milestone Progress
Budget Summary (Monthly)
Total Income: $0.00
Total Expenses: $0.00
Net Savings: $0.00
Target: $${formatCurrency(m.target)} | Saved: $${formatCurrency(m.saved)} ${m.targetDate ? `| Target Date: ${m.targetDate}` : ''}
`; ul.appendChild(li); }); milestonesListContainer.appendChild(ul); // Add event listeners for remove buttons document.querySelectorAll('#financialTrackerApp .remove-milestone-btn').forEach(btn => { btn.addEventListener('click', function() { const milestoneId = this.dataset.id; appData.milestones = appData.milestones.filter(m => m.id !== milestoneId); // Also remove associated contributions (or handle orphaned contributions if necessary) // For simplicity, contributions are tied to milestones which are now removed. renderMilestonesList(); renderMilestoneOptionsForContribution(); renderMilestonesSummary(); renderContributionsLogForSelectedMilestone(); // In case the removed one was selected }); }); } // --- Budget Functions (Income/Expense) --- function createBudgetItem(type, nameInput, amountInput, listContainer) { const name = nameInput.value.trim(); const amount = parseFloat(amountInput.value); if (!name) { alert(`${type} name/category is required.`); return; } if (isNaN(amount) || amount <= 0) { alert(`Valid ${type} amount is required.`); return; } const newItem = { id: getNextId(type.toLowerCase()), name, amount }; appData[type.toLowerCase() === 'income' ? 'incomeSources' : 'expenses'].push(newItem); renderBudgetItems(type.toLowerCase()); calculateAndRenderBudgetSummary(); nameInput.value = ''; amountInput.value = ''; alert(`${type} item added!`); } function renderBudgetItems(itemType) { // 'income' or 'expense' const container = itemType === 'income' ? incomeListContainer : expenseListContainer; const items = itemType === 'income' ? appData.incomeSources : appData.expenses; if (!container) return; container.innerHTML = ''; if (items.length === 0) { container.innerHTML = `No ${itemType} items added yet.
`; return; } const ul = document.createElement('ul'); ul.style.listStyleType = 'none'; ul.style.paddingLeft = '0'; items.forEach(item => { const li = document.createElement('li'); li.className = 'item-entry'; // Re-use some styling li.innerHTML = ` ${item.name}: $${formatCurrency(item.amount)} `; ul.appendChild(li); }); container.appendChild(ul); // Add event listeners for remove buttons for budget items document.querySelectorAll(`#financialTrackerApp .remove-budget-item-btn[data-type="${itemType}"]`).forEach(btn => { btn.addEventListener('click', function() { const itemId = this.dataset.id; if (itemType === 'income') { appData.incomeSources = appData.incomeSources.filter(i => i.id !== itemId); } else { appData.expenses = appData.expenses.filter(e => e.id !== itemId); } renderBudgetItems(itemType); calculateAndRenderBudgetSummary(); }); }); } function calculateAndRenderBudgetSummary() { const totalIncome = appData.incomeSources.reduce((sum, item) => sum + item.amount, 0); const totalExpenses = appData.expenses.reduce((sum, item) => sum + item.amount, 0); const netSavings = totalIncome - totalExpenses; if (totalIncomeDisplay) totalIncomeDisplay.textContent = formatCurrency(totalIncome); if (totalExpensesDisplay) totalExpensesDisplay.textContent = formatCurrency(totalExpenses); if (netSavingsDisplay) { netSavingsDisplay.textContent = formatCurrency(netSavings); netSavingsDisplay.className = netSavings >= 0 ? 'text-success' : 'text-danger'; } // Update summary tab as well if (summaryTotalIncome) summaryTotalIncome.textContent = formatCurrency(totalIncome); if (summaryTotalExpenses) summaryTotalExpenses.textContent = formatCurrency(totalExpenses); if (summaryNetSavings) { summaryNetSavings.textContent = formatCurrency(netSavings); summaryNetSavings.className = netSavings >= 0 ? 'text-success' : 'text-danger'; } } // --- Contribution Functions --- function renderMilestoneOptionsForContribution() { if (!milestoneSelectForContribution) return; const currentSelection = milestoneSelectForContribution.value; milestoneSelectForContribution.innerHTML = ''; appData.milestones.forEach(m => { const option = document.createElement('option'); option.value = m.id; option.textContent = m.name; milestoneSelectForContribution.appendChild(option); }); // Try to preserve selection if still valid if (appData.milestones.find(m => m.id === currentSelection)) { milestoneSelectForContribution.value = currentSelection; } else if (appData.milestones.length > 0) { milestoneSelectForContribution.value = ''; // Reset if previous selection gone } renderContributionsLogForSelectedMilestone(); // Update log based on new options/selection } function handleAddContribution() { const milestoneId = milestoneSelectForContribution.value; const amount = parseFloat(contributionAmountInput.value); const date = contributionDateInput.value; if (!milestoneId) { alert('Please select a milestone.'); return; } if (isNaN(amount) || amount <= 0) { alert('Please enter a valid contribution amount.'); return; } if (!date) { alert('Please select a contribution date.'); return; } const milestone = appData.milestones.find(m => m.id === milestoneId); if (milestone) { if (milestone.saved + amount > milestone.target) { if (!confirm(`This contribution of $${formatCurrency(amount)} will exceed the target of $${formatCurrency(milestone.target)} for "${milestone.name}". Current saved: $${formatCurrency(milestone.saved)}. Add anyway?`)) { return; } } const newContribution = { id: getNextId('contribution'), milestoneId, amount, date }; milestone.contributions.push(newContribution); milestone.saved = (milestone.saved || 0) + amount; alert('Contribution added!'); contributionAmountInput.value = ''; // contributionDateInput.value = new Date().toISOString().split('T')[0]; // Reset date or leave as is? Let's leave for now. renderMilestonesList(); // Update saved amount display in milestone list renderContributionsLogForSelectedMilestone(); renderMilestonesSummary(); // Update progress bars } else { alert('Selected milestone not found. This should not happen.'); } } function renderContributionsLogForSelectedMilestone() { if (!contributionsLogTableContainer || !milestoneSelectForContribution) return; const selectedId = milestoneSelectForContribution.value; const milestone = appData.milestones.find(m => m.id === selectedId); if (selectedMilestoneForLog) selectedMilestoneForLog.textContent = milestone ? milestone.name : 'N/A'; contributionsLogTableContainer.innerHTML = ''; if (!milestone || !milestone.contributions || milestone.contributions.length === 0) { contributionsLogTableContainer.innerHTML = 'No contributions yet for this milestone.
'; return; } const table = document.createElement('table'); table.innerHTML = `Milestone Progress
'; if (appData.milestones.length === 0) { milestonesSummaryContainer.innerHTML += 'No milestones defined yet. Go to "Setup & Milestones" tab.
'; return; } appData.milestones.forEach(m => { const progress = m.target > 0 ? Math.min(100, (m.saved / m.target) * 100) : (m.saved > 0 ? 100 : 0); const remaining = Math.max(0, m.target - m.saved); // Don't show negative remaining const summaryDiv = document.createElement('div'); summaryDiv.className = 'milestone-summary-item'; // Can be same as milestone-entry-item for styling summaryDiv.innerHTML = `${m.name}
Target: $${formatCurrency(m.target)} | Saved: $${formatCurrency(m.saved)} | Remaining: $${formatCurrency(remaining)}
${progress.toFixed(1)}%
Target Date: ${m.targetDate}
` : ''}`; milestonesSummaryContainer.appendChild(summaryDiv); }); } function renderAllSummaries() { calculateAndRenderBudgetSummary(); // Updates budget part in summary too renderMilestonesSummary(); } // --- PDF Download Function --- function downloadPDF() { const userName = userNameInput.value.trim(); let pdfHtml = `
Personalized Financial Milestone Budget Tracker
`; if (userName) { pdfHtml += `Prepared for: ${userName}
`; } pdfHtml += `Date: ${new Date().toLocaleDateString()}
`; // Milestones Data if (appData.milestones.length > 0) { pdfHtml += `Milestones Overview
`; pdfHtml += `| Milestone | Target ($) | Saved ($) | Remaining ($) | Progress |
|---|---|---|---|---|
| ${m.name} ${m.targetDate ? `(Target: ${m.targetDate})` : ''} | ${formatCurrency(m.target)} | ${formatCurrency(m.saved)} | ${formatCurrency(remaining)} |
${progress.toFixed(1)}%
|
Milestones Overview
No milestones defined.
`; } // Budget Data const totalIncome = appData.incomeSources.reduce((sum, item) => sum + item.amount, 0); const totalExpenses = appData.expenses.reduce((sum, item) => sum + item.amount, 0); const netSavings = totalIncome - totalExpenses; const netSavingsClass = netSavings >= 0 ? 'text-success' : 'text-danger'; pdfHtml += `Budget Summary (Monthly)
`; pdfHtml += `Total Income: $${formatCurrency(totalIncome)}
`; pdfHtml += `Total Expenses: $${formatCurrency(totalExpenses)}
`; pdfHtml += `Net Savings: $${formatCurrency(netSavings)}
`; // Optionally list income/expense items if(appData.incomeSources.length > 0){ pdfHtml += `Income Sources:
- `;
appData.incomeSources.forEach(inc => { pdfHtml += `
- ${inc.name}: $${formatCurrency(inc.amount)} `; }); pdfHtml += `
Expense Items:
- `;
appData.expenses.forEach(exp => { pdfHtml += `
- ${exp.name}: $${formatCurrency(exp.amount)} `; }); pdfHtml += `
