export function emergencyFundCalculator(form: FormTs) {
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Emergency Fund Calculator',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Monthly Expenses Section
const expensesSection = form.addSubform('expenses', { title: 'Monthly Expenses' });
expensesSection.addRow(row => {
row.addMoney('housing', {
label: 'Housing (Rent/Mortgage)',
min: 0,
max: 50000,
defaultValue: 1500,
isRequired: true,
tooltip: 'Monthly rent or mortgage payment'
}, '1fr');
row.addMoney('utilities', {
label: 'Utilities',
min: 0,
max: 5000,
defaultValue: 200,
tooltip: 'Electric, gas, water, internet, phone'
}, '1fr');
});
expensesSection.addRow(row => {
row.addMoney('food', {
label: 'Food & Groceries',
min: 0,
max: 10000,
defaultValue: 600,
tooltip: 'Monthly grocery and dining expenses'
}, '1fr');
row.addMoney('transportation', {
label: 'Transportation',
min: 0,
max: 5000,
defaultValue: 400,
tooltip: 'Car payment, insurance, gas, public transit'
}, '1fr');
});
expensesSection.addRow(row => {
row.addMoney('insurance', {
label: 'Insurance (Health, Life)',
min: 0,
max: 10000,
defaultValue: 300,
tooltip: 'Health insurance, life insurance premiums'
}, '1fr');
row.addMoney('debtPayments', {
label: 'Minimum Debt Payments',
min: 0,
max: 20000,
defaultValue: 200,
tooltip: 'Credit cards, student loans, personal loans'
}, '1fr');
});
expensesSection.addRow(row => {
row.addMoney('otherExpenses', {
label: 'Other Essential Expenses',
min: 0,
max: 20000,
defaultValue: 300,
tooltip: 'Childcare, medications, subscriptions, etc.'
});
});
// Risk Factors Section
const riskSection = form.addSubform('risk', { title: 'Your Situation' });
riskSection.addRow(row => {
row.addDropdown('incomeStability', {
label: 'Income Stability',
options: [
{ id: 'very-stable', name: 'Very Stable (Government, tenured)' },
{ id: 'stable', name: 'Stable (Salaried employee)' },
{ id: 'moderate', name: 'Moderate (Contract, commission-based)' },
{ id: 'variable', name: 'Variable (Freelance, seasonal)' },
{ id: 'unstable', name: 'Unstable (Gig economy, new business)' }
],
defaultValue: 'stable',
isRequired: true,
tooltip: 'How predictable is your income?'
}, '1fr');
row.addDropdown('incomeEarners', {
label: 'Household Income Earners',
options: [
{ id: 'single', name: 'Single Income' },
{ id: 'dual', name: 'Dual Income' }
],
defaultValue: 'single',
tooltip: 'Does your household have one or two earners?'
}, '1fr');
});
riskSection.addRow(row => {
row.addInteger('dependents', {
label: 'Number of Dependents',
min: 0,
max: 20,
defaultValue: 0,
tooltip: 'Children, elderly parents, or others who depend on you'
}, '1fr');
row.addDropdown('healthStatus', {
label: 'Health Considerations',
options: [
{ id: 'excellent', name: 'Excellent - No ongoing issues' },
{ id: 'good', name: 'Good - Minor concerns' },
{ id: 'moderate', name: 'Moderate - Ongoing conditions' },
{ id: 'significant', name: 'Significant - High medical costs' }
],
defaultValue: 'good',
tooltip: 'Do you have health issues that could lead to unexpected costs?'
}, '1fr');
});
riskSection.addRow(row => {
row.addDropdown('jobMarket', {
label: 'Job Market for Your Field',
options: [
{ id: 'high-demand', name: 'High Demand - Easy to find work' },
{ id: 'moderate', name: 'Moderate - Average job market' },
{ id: 'competitive', name: 'Competitive - Harder to find work' },
{ id: 'specialized', name: 'Specialized - Very few opportunities' }
],
defaultValue: 'moderate',
tooltip: 'How quickly could you find a new job if needed?'
});
});
// Current Savings Section
const savingsSection = form.addSubform('savings', { title: 'Current Savings' });
savingsSection.addRow(row => {
row.addMoney('currentEmergencyFund', {
label: 'Current Emergency Savings',
min: 0,
max: 1000000,
defaultValue: 5000,
tooltip: 'Amount currently saved for emergencies'
}, '1fr');
row.addMoney('monthlySavings', {
label: 'Monthly Amount You Can Save',
min: 0,
max: 50000,
defaultValue: 500,
tooltip: 'How much can you put toward your emergency fund monthly?'
}, '1fr');
});
form.addSpacer({ height: 20, showLine: true, lineStyle: 'dashed' });
// Results Section
const resultsSection = form.addSubform('results', { title: 'Your Emergency Fund Goal' });
resultsSection.addRow(row => {
row.addPriceDisplay('monthlyExpenses', {
label: 'Total Monthly Expenses',
computedValue: () => {
const housing = expensesSection.money('housing')?.value() || 0;
const utilities = expensesSection.money('utilities')?.value() || 0;
const food = expensesSection.money('food')?.value() || 0;
const transportation = expensesSection.money('transportation')?.value() || 0;
const insurance = expensesSection.money('insurance')?.value() || 0;
const debtPayments = expensesSection.money('debtPayments')?.value() || 0;
const otherExpenses = expensesSection.money('otherExpenses')?.value() || 0;
return housing + utilities + food + transportation + insurance + debtPayments + otherExpenses;
},
variant: 'default'
}, '1fr');
row.addTextPanel('monthsRecommended', {
label: 'Months Recommended',
computedValue: () => {
const incomeStability = riskSection.dropdown('incomeStability')?.value() || 'stable';
const incomeEarners = riskSection.dropdown('incomeEarners')?.value() || 'single';
const dependents = riskSection.integer('dependents')?.value() || 0;
const healthStatus = riskSection.dropdown('healthStatus')?.value() || 'good';
const jobMarket = riskSection.dropdown('jobMarket')?.value() || 'moderate';
let baseMonths = 3;
// Income stability factor
const stabilityFactors: Record<string, number> = {
'very-stable': 0,
'stable': 1,
'moderate': 2,
'variable': 3,
'unstable': 4
};
baseMonths += stabilityFactors[incomeStability] || 1;
// Dual income reduces need
if (incomeEarners === 'dual') {
baseMonths -= 1;
}
// Dependents increase need
baseMonths += Math.min(dependents, 3);
// Health factors
const healthFactors: Record<string, number> = {
'excellent': 0,
'good': 0,
'moderate': 1,
'significant': 2
};
baseMonths += healthFactors[healthStatus] || 0;
// Job market factors
const jobFactors: Record<string, number> = {
'high-demand': -1,
'moderate': 0,
'competitive': 1,
'specialized': 2
};
baseMonths += jobFactors[jobMarket] || 0;
// Ensure reasonable bounds
baseMonths = Math.max(3, Math.min(baseMonths, 12));
return `${baseMonths} months`;
},
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'text-align': 'center', 'color': '#0284c7' }
}, '1fr');
});
resultsSection.addRow(row => {
row.addPriceDisplay('targetFund', {
label: 'Target Emergency Fund',
computedValue: () => {
const housing = expensesSection.money('housing')?.value() || 0;
const utilities = expensesSection.money('utilities')?.value() || 0;
const food = expensesSection.money('food')?.value() || 0;
const transportation = expensesSection.money('transportation')?.value() || 0;
const insurance = expensesSection.money('insurance')?.value() || 0;
const debtPayments = expensesSection.money('debtPayments')?.value() || 0;
const otherExpenses = expensesSection.money('otherExpenses')?.value() || 0;
const monthlyTotal = housing + utilities + food + transportation + insurance + debtPayments + otherExpenses;
const incomeStability = riskSection.dropdown('incomeStability')?.value() || 'stable';
const incomeEarners = riskSection.dropdown('incomeEarners')?.value() || 'single';
const dependents = riskSection.integer('dependents')?.value() || 0;
const healthStatus = riskSection.dropdown('healthStatus')?.value() || 'good';
const jobMarket = riskSection.dropdown('jobMarket')?.value() || 'moderate';
let baseMonths = 3;
const stabilityFactors: Record<string, number> = {
'very-stable': 0,
'stable': 1,
'moderate': 2,
'variable': 3,
'unstable': 4
};
baseMonths += stabilityFactors[incomeStability] || 1;
if (incomeEarners === 'dual') {
baseMonths -= 1;
}
baseMonths += Math.min(dependents, 3);
const healthFactors: Record<string, number> = {
'excellent': 0,
'good': 0,
'moderate': 1,
'significant': 2
};
baseMonths += healthFactors[healthStatus] || 0;
const jobFactors: Record<string, number> = {
'high-demand': -1,
'moderate': 0,
'competitive': 1,
'specialized': 2
};
baseMonths += jobFactors[jobMarket] || 0;
baseMonths = Math.max(3, Math.min(baseMonths, 12));
return monthlyTotal * baseMonths;
},
variant: 'large'
});
});
// Progress Section
const progressSection = form.addSubform('progress', { title: 'Your Progress' });
progressSection.addRow(row => {
row.addPriceDisplay('currentSaved', {
label: 'Currently Saved',
computedValue: () => {
return savingsSection.money('currentEmergencyFund')?.value() || 0;
},
variant: 'success'
}, '1fr');
row.addPriceDisplay('amountNeeded', {
label: 'Still Needed',
computedValue: () => {
const housing = expensesSection.money('housing')?.value() || 0;
const utilities = expensesSection.money('utilities')?.value() || 0;
const food = expensesSection.money('food')?.value() || 0;
const transportation = expensesSection.money('transportation')?.value() || 0;
const insurance = expensesSection.money('insurance')?.value() || 0;
const debtPayments = expensesSection.money('debtPayments')?.value() || 0;
const otherExpenses = expensesSection.money('otherExpenses')?.value() || 0;
const monthlyTotal = housing + utilities + food + transportation + insurance + debtPayments + otherExpenses;
const incomeStability = riskSection.dropdown('incomeStability')?.value() || 'stable';
const incomeEarners = riskSection.dropdown('incomeEarners')?.value() || 'single';
const dependents = riskSection.integer('dependents')?.value() || 0;
const healthStatus = riskSection.dropdown('healthStatus')?.value() || 'good';
const jobMarket = riskSection.dropdown('jobMarket')?.value() || 'moderate';
let baseMonths = 3;
const stabilityFactors: Record<string, number> = {
'very-stable': 0,
'stable': 1,
'moderate': 2,
'variable': 3,
'unstable': 4
};
baseMonths += stabilityFactors[incomeStability] || 1;
if (incomeEarners === 'dual') {
baseMonths -= 1;
}
baseMonths += Math.min(dependents, 3);
const healthFactors: Record<string, number> = {
'excellent': 0,
'good': 0,
'moderate': 1,
'significant': 2
};
baseMonths += healthFactors[healthStatus] || 0;
const jobFactors: Record<string, number> = {
'high-demand': -1,
'moderate': 0,
'competitive': 1,
'specialized': 2
};
baseMonths += jobFactors[jobMarket] || 0;
baseMonths = Math.max(3, Math.min(baseMonths, 12));
const targetFund = monthlyTotal * baseMonths;
const currentSaved = savingsSection.money('currentEmergencyFund')?.value() || 0;
return Math.max(0, targetFund - currentSaved);
},
variant: 'default'
}, '1fr');
});
progressSection.addRow(row => {
row.addTextPanel('progressPercent', {
label: 'Progress',
computedValue: () => {
const housing = expensesSection.money('housing')?.value() || 0;
const utilities = expensesSection.money('utilities')?.value() || 0;
const food = expensesSection.money('food')?.value() || 0;
const transportation = expensesSection.money('transportation')?.value() || 0;
const insurance = expensesSection.money('insurance')?.value() || 0;
const debtPayments = expensesSection.money('debtPayments')?.value() || 0;
const otherExpenses = expensesSection.money('otherExpenses')?.value() || 0;
const monthlyTotal = housing + utilities + food + transportation + insurance + debtPayments + otherExpenses;
const incomeStability = riskSection.dropdown('incomeStability')?.value() || 'stable';
const incomeEarners = riskSection.dropdown('incomeEarners')?.value() || 'single';
const dependents = riskSection.integer('dependents')?.value() || 0;
const healthStatus = riskSection.dropdown('healthStatus')?.value() || 'good';
const jobMarket = riskSection.dropdown('jobMarket')?.value() || 'moderate';
let baseMonths = 3;
const stabilityFactors: Record<string, number> = {
'very-stable': 0,
'stable': 1,
'moderate': 2,
'variable': 3,
'unstable': 4
};
baseMonths += stabilityFactors[incomeStability] || 1;
if (incomeEarners === 'dual') {
baseMonths -= 1;
}
baseMonths += Math.min(dependents, 3);
const healthFactors: Record<string, number> = {
'excellent': 0,
'good': 0,
'moderate': 1,
'significant': 2
};
baseMonths += healthFactors[healthStatus] || 0;
const jobFactors: Record<string, number> = {
'high-demand': -1,
'moderate': 0,
'competitive': 1,
'specialized': 2
};
baseMonths += jobFactors[jobMarket] || 0;
baseMonths = Math.max(3, Math.min(baseMonths, 12));
const targetFund = monthlyTotal * baseMonths;
const currentSaved = savingsSection.money('currentEmergencyFund')?.value() || 0;
if (targetFund === 0) return '0%';
const percent = Math.min(100, Math.round((currentSaved / targetFund) * 100));
return `${percent}% funded`;
},
customStyles: { 'font-size': '1.25rem', 'font-weight': '600', 'text-align': 'center', 'color': '#059669' }
}, '1fr');
row.addTextPanel('timeToGoal', {
label: 'Time to Goal',
computedValue: () => {
const housing = expensesSection.money('housing')?.value() || 0;
const utilities = expensesSection.money('utilities')?.value() || 0;
const food = expensesSection.money('food')?.value() || 0;
const transportation = expensesSection.money('transportation')?.value() || 0;
const insurance = expensesSection.money('insurance')?.value() || 0;
const debtPayments = expensesSection.money('debtPayments')?.value() || 0;
const otherExpenses = expensesSection.money('otherExpenses')?.value() || 0;
const monthlyTotal = housing + utilities + food + transportation + insurance + debtPayments + otherExpenses;
const incomeStability = riskSection.dropdown('incomeStability')?.value() || 'stable';
const incomeEarners = riskSection.dropdown('incomeEarners')?.value() || 'single';
const dependents = riskSection.integer('dependents')?.value() || 0;
const healthStatus = riskSection.dropdown('healthStatus')?.value() || 'good';
const jobMarket = riskSection.dropdown('jobMarket')?.value() || 'moderate';
let baseMonths = 3;
const stabilityFactors: Record<string, number> = {
'very-stable': 0,
'stable': 1,
'moderate': 2,
'variable': 3,
'unstable': 4
};
baseMonths += stabilityFactors[incomeStability] || 1;
if (incomeEarners === 'dual') {
baseMonths -= 1;
}
baseMonths += Math.min(dependents, 3);
const healthFactors: Record<string, number> = {
'excellent': 0,
'good': 0,
'moderate': 1,
'significant': 2
};
baseMonths += healthFactors[healthStatus] || 0;
const jobFactors: Record<string, number> = {
'high-demand': -1,
'moderate': 0,
'competitive': 1,
'specialized': 2
};
baseMonths += jobFactors[jobMarket] || 0;
baseMonths = Math.max(3, Math.min(baseMonths, 12));
const targetFund = monthlyTotal * baseMonths;
const currentSaved = savingsSection.money('currentEmergencyFund')?.value() || 0;
const monthlySavings = savingsSection.money('monthlySavings')?.value() || 0;
const remaining = targetFund - currentSaved;
if (remaining <= 0) return 'Goal reached!';
if (monthlySavings <= 0) return 'Start saving to see timeline';
const monthsToGoal = Math.ceil(remaining / monthlySavings);
if (monthsToGoal <= 12) {
return `${monthsToGoal} month${monthsToGoal !== 1 ? 's' : ''}`;
} else {
const years = Math.floor(monthsToGoal / 12);
const remainingMonths = monthsToGoal % 12;
if (remainingMonths === 0) {
return `${years} year${years !== 1 ? 's' : ''}`;
}
return `${years}y ${remainingMonths}m`;
}
},
customStyles: { 'font-size': '1.25rem', 'font-weight': '600', 'text-align': 'center', 'color': '#0284c7' }
}, '1fr');
});
// Summary Section
const summarySection = form.addSubform('summary', {
title: 'Recommendation',
isCollapsible: false,
sticky: 'bottom'
});
summarySection.addRow(row => {
row.addTextPanel('summaryText', {
computedValue: () => {
const housing = expensesSection.money('housing')?.value() || 0;
const utilities = expensesSection.money('utilities')?.value() || 0;
const food = expensesSection.money('food')?.value() || 0;
const transportation = expensesSection.money('transportation')?.value() || 0;
const insurance = expensesSection.money('insurance')?.value() || 0;
const debtPayments = expensesSection.money('debtPayments')?.value() || 0;
const otherExpenses = expensesSection.money('otherExpenses')?.value() || 0;
const monthlyTotal = housing + utilities + food + transportation + insurance + debtPayments + otherExpenses;
const currentSaved = savingsSection.money('currentEmergencyFund')?.value() || 0;
const currentMonths = monthlyTotal > 0 ? (currentSaved / monthlyTotal) : 0;
if (currentMonths >= 6) {
return 'Excellent! You have a solid emergency fund. Consider investing any additional savings.';
} else if (currentMonths >= 3) {
return 'Good progress! Keep building toward your full goal for greater security.';
} else if (currentMonths >= 1) {
return 'You have a start. Prioritize reaching at least 3 months of expenses.';
} else {
return 'Building an emergency fund should be a top priority. Start with a goal of $1,000, then build from there.';
}
},
customStyles: { 'font-size': '0.95rem', 'font-weight': '500', 'text-align': 'center', 'color': '#1e293b' }
});
});
summarySection.addRow(row => {
row.addTextPanel('disclaimer', {
computedValue: () => 'This calculator provides general guidance. Your actual needs may vary based on personal circumstances.',
customStyles: { 'font-size': '0.8rem', 'color': '#64748b', 'text-align': 'center' }
});
});
form.configureSubmitButton({
label: 'Save My Plan'
});
}