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 = ` DateAmount ($)Action `; const tbody = table.querySelector('tbody'); // Sort contributions by date, most recent first (optional) const sortedContributions = [...milestone.contributions].sort((a,b) => new Date(b.date) - new Date(a.date)); sortedContributions.forEach(c => { const row = tbody.insertRow(); row.insertCell().textContent = c.date; row.insertCell().textContent = formatCurrency(c.amount); const actionCell = row.insertCell(); const removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'danger-action remove-contribution-btn'; removeBtn.textContent = 'Remove'; removeBtn.dataset.contributionId = c.id; removeBtn.dataset.milestoneId = milestone.id; actionCell.appendChild(removeBtn); }); contributionsLogTableContainer.appendChild(table); // Add event listeners for remove contribution buttons document.querySelectorAll('#financialTrackerApp .remove-contribution-btn').forEach(btn => { btn.addEventListener('click', function() { const contribId = this.dataset.contributionId; const mId = this.dataset.milestoneId; const targetMilestone = appData.milestones.find(m => m.id === mId); if (targetMilestone) { const contribToRemove = targetMilestone.contributions.find(c => c.id === contribId); if (contribToRemove) { targetMilestone.saved -= contribToRemove.amount; // Deduct amount targetMilestone.contributions = targetMilestone.contributions.filter(c => c.id !== contribId); renderMilestonesList(); renderContributionsLogForSelectedMilestone(); renderMilestonesSummary(); } } }); }); } // --- Summary Tab Functions --- function renderMilestonesSummary() { if (!milestonesSummaryContainer) return; milestonesSummaryContainer.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)}%
${m.targetDate ? `

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 = ` Financial Summary

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 += ``; 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); pdfHtml += ` `; }); pdfHtml += `
MilestoneTarget ($)Saved ($)Remaining ($)Progress
${m.name} ${m.targetDate ? `(Target: ${m.targetDate})` : ''} ${formatCurrency(m.target)} ${formatCurrency(m.saved)} ${formatCurrency(remaining)}
${progress.toFixed(1)}%
`; } else { pdfHtml += `

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 += `
`; } if(appData.expenses.length > 0){ pdfHtml += `

Expense Items:

    `; appData.expenses.forEach(exp => { pdfHtml += `
  • ${exp.name}: $${formatCurrency(exp.amount)}
  • `; }); pdfHtml += `
`; } pdfHtml += ''; const pdfWindow = window.open('', '_blank'); if (pdfWindow) { pdfWindow.document.write(pdfHtml); pdfWindow.document.close(); // Important for some browsers setTimeout(() => { // Allow content to render before print dialog pdfWindow.print(); }, 500); } else { alert('Could not open PDF window. Please disable your pop-up blocker for this site.'); } } // --- Event Listeners --- if (addMilestoneBtn) addMilestoneBtn.addEventListener('click', handleAddMilestone); if (addIncomeBtn) addIncomeBtn.addEventListener('click', () => createBudgetItem('Income', incomeSourceNameInput, incomeSourceAmountInput, incomeListContainer)); if (addExpenseBtn) addExpenseBtn.addEventListener('click', () => createBudgetItem('Expense', expenseItemNameInput, expenseItemAmountInput, expenseListContainer)); if (addContributionBtn) addContributionBtn.addEventListener('click', handleAddContribution); if (downloadPdfBtn) downloadPdfBtn.addEventListener('click', downloadPDF); if (milestoneSelectForContribution) milestoneSelectForContribution.addEventListener('change', renderContributionsLogForSelectedMilestone); if (userNameInput) userNameInput.addEventListener('change', (e) => { appData.userName = e.target.value; }); // --- Initialization --- function initializeApp() { updateTabDisplay(); if (contributionDateInput) contributionDateInput.value = new Date().toISOString().split('T')[0]; // Default contribution date // Initial renders renderMilestonesList(); renderBudgetItems('income'); renderBudgetItems('expense'); calculateAndRenderBudgetSummary(); renderMilestoneOptionsForContribution(); renderContributionsLogForSelectedMilestone(); // Will show "select milestone" or first one's log renderAllSummaries(); // For summary tab } initializeApp(); }); // End DOMContentLoaded
Scroll to Top