export function rentalYieldCalculator(form: FormTs) {
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Rental Yield Calculator',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Property Details Section
const propertySection = form.addSubform('property', { title: '🏠 Property Details' });
propertySection.addRow(row => {
row.addMoney('purchasePrice', {
label: 'Purchase Price',
min: 10000,
max: 10000000,
defaultValue: 350000,
isRequired: true,
tooltip: 'Total property purchase price'
}, '1fr');
row.addMoney('closingCosts', {
label: 'Closing Costs',
min: 0,
max: 100000,
defaultValue: 10000,
tooltip: 'Legal fees, inspections, etc.'
}, '1fr');
});
propertySection.addRow(row => {
row.addMoney('renovations', {
label: 'Renovation/Repair Costs',
min: 0,
max: 500000,
defaultValue: 0,
tooltip: 'Initial improvements before renting'
}, '1fr');
row.addDropdown('propertyType', {
label: 'Property Type',
options: [
{ id: 'single-family', name: 'Single Family Home' },
{ id: 'condo', name: 'Condo/Apartment' },
{ id: 'townhouse', name: 'Townhouse' },
{ id: 'duplex', name: 'Duplex' },
{ id: 'triplex', name: 'Triplex' },
{ id: 'fourplex', name: 'Fourplex' },
{ id: 'multi-family', name: 'Multi-family (5+)' }
],
defaultValue: 'single-family'
}, '1fr');
});
// Rental Income Section
const incomeSection = form.addSubform('income', { title: '💵 Rental Income' });
incomeSection.addRow(row => {
row.addMoney('monthlyRent', {
label: 'Monthly Rent',
min: 100,
max: 50000,
defaultValue: 2200,
isRequired: true,
tooltip: 'Expected monthly rental income'
}, '1fr');
row.addInteger('units', {
label: 'Number of Units',
min: 1,
max: 100,
defaultValue: 1,
tooltip: 'Total rentable units'
}, '1fr');
});
incomeSection.addRow(row => {
row.addDecimal('vacancyRate', {
label: 'Vacancy Rate (%)',
min: 0,
max: 50,
defaultValue: 5,
tooltip: 'Expected vacancy percentage'
}, '1fr');
row.addMoney('otherIncome', {
label: 'Other Monthly Income',
min: 0,
max: 5000,
defaultValue: 0,
tooltip: 'Parking, laundry, storage, etc.'
}, '1fr');
});
// Operating Expenses Section
const expensesSection = form.addSubform('expenses', { title: '📊 Operating Expenses (Monthly)' });
expensesSection.addRow(row => {
row.addMoney('propertyTax', {
label: 'Property Tax',
min: 0,
max: 10000,
defaultValue: 350,
tooltip: 'Monthly property tax'
}, '1fr');
row.addMoney('insurance', {
label: 'Insurance',
min: 0,
max: 2000,
defaultValue: 120,
tooltip: 'Landlord insurance premium'
}, '1fr');
});
expensesSection.addRow(row => {
row.addMoney('hoa', {
label: 'HOA/Condo Fees',
min: 0,
max: 2000,
defaultValue: 0,
tooltip: 'Homeowners association fees'
}, '1fr');
row.addMoney('utilities', {
label: 'Utilities (if owner-paid)',
min: 0,
max: 1000,
defaultValue: 0,
tooltip: 'Water, electric, gas if not tenant-paid'
}, '1fr');
});
expensesSection.addRow(row => {
row.addDecimal('managementFee', {
label: 'Property Management (%)',
min: 0,
max: 20,
defaultValue: 8,
tooltip: 'Percentage of rent to manager'
}, '1fr');
row.addDecimal('maintenanceReserve', {
label: 'Maintenance Reserve (%)',
min: 0,
max: 20,
defaultValue: 5,
tooltip: 'Set aside for repairs'
}, '1fr');
});
expensesSection.addRow(row => {
row.addMoney('otherExpenses', {
label: 'Other Monthly Expenses',
min: 0,
max: 2000,
defaultValue: 0,
tooltip: 'Lawn care, pest control, etc.'
});
});
// Financing Section
const financingSection = form.addSubform('financing', { title: '🏦 Financing (Optional)' });
financingSection.addRow(row => {
row.addCheckbox('useFinancing', {
label: 'Include mortgage financing',
defaultValue: true
});
});
financingSection.addRow(row => {
row.addDecimal('downPayment', {
label: 'Down Payment (%)',
min: 0,
max: 100,
defaultValue: 25,
isVisible: () => financingSection.checkbox('useFinancing')?.value(),
tooltip: 'Investment properties often require 20-25%'
}, '1fr');
row.addDecimal('interestRate', {
label: 'Interest Rate (%)',
min: 0,
max: 15,
defaultValue: 7,
isVisible: () => financingSection.checkbox('useFinancing')?.value()
}, '1fr');
});
financingSection.addRow(row => {
row.addDropdown('loanTerm', {
label: 'Loan Term',
options: [
{ id: '15', name: '15 years' },
{ id: '20', name: '20 years' },
{ id: '25', name: '25 years' },
{ id: '30', name: '30 years' }
],
defaultValue: '30',
isVisible: () => financingSection.checkbox('useFinancing')?.value()
});
});
form.addSpacer({ height: 20, showLine: true, lineStyle: 'dashed' });
// Yields Section
const yieldsSection = form.addSubform('yields', { title: '📈 Yield Analysis', isCollapsible: false });
yieldsSection.addRow(row => {
row.addTextPanel('grossYield', {
computedValue: () => {
const purchasePrice = propertySection.money('purchasePrice')?.value() || 350000;
const monthlyRent = incomeSection.money('monthlyRent')?.value() || 2200;
const units = incomeSection.integer('units')?.value() || 1;
const annualRent = monthlyRent * units * 12;
const grossYield = (annualRent / purchasePrice) * 100;
return `Gross Yield: ${(Number(grossYield) || 0).toFixed(2)}%`;
},
customStyles: { 'font-size': '1.1rem', 'font-weight': '600', 'color': '#059669', 'text-align': 'center' }
}, '1fr');
row.addTextPanel('netYield', {
computedValue: () => {
const purchasePrice = propertySection.money('purchasePrice')?.value() || 350000;
const monthlyRent = incomeSection.money('monthlyRent')?.value() || 2200;
const units = incomeSection.integer('units')?.value() || 1;
const otherIncome = incomeSection.money('otherIncome')?.value() || 0;
const vacancyRate = incomeSection.decimal('vacancyRate')?.value() || 5;
const grossAnnualIncome = (monthlyRent * units + otherIncome) * 12;
const effectiveIncome = grossAnnualIncome * (1 - vacancyRate / 100);
// Annual expenses
const propertyTax = (expensesSection.money('propertyTax')?.value() || 350) * 12;
const insurance = (expensesSection.money('insurance')?.value() || 120) * 12;
const hoa = (expensesSection.money('hoa')?.value() || 0) * 12;
const utilities = (expensesSection.money('utilities')?.value() || 0) * 12;
const managementFee = (expensesSection.decimal('managementFee')?.value() || 8) / 100 * effectiveIncome;
const maintenanceReserve = (expensesSection.decimal('maintenanceReserve')?.value() || 5) / 100 * effectiveIncome;
const otherExpenses = (expensesSection.money('otherExpenses')?.value() || 0) * 12;
const totalExpenses = propertyTax + insurance + hoa + utilities + managementFee + maintenanceReserve + otherExpenses;
const noi = effectiveIncome - totalExpenses;
const netYield = (noi / purchasePrice) * 100;
return `Net Yield: ${(Number(netYield) || 0).toFixed(2)}%`;
},
customStyles: { 'font-size': '1.1rem', 'font-weight': '600', 'color': '#0284c7', 'text-align': 'center' }
}, '1fr');
});
yieldsSection.addRow(row => {
row.addTextPanel('capRate', {
computedValue: () => {
const purchasePrice = propertySection.money('purchasePrice')?.value() || 350000;
const closingCosts = propertySection.money('closingCosts')?.value() || 10000;
const renovations = propertySection.money('renovations')?.value() || 0;
const totalInvestment = purchasePrice + closingCosts + renovations;
const monthlyRent = incomeSection.money('monthlyRent')?.value() || 2200;
const units = incomeSection.integer('units')?.value() || 1;
const otherIncome = incomeSection.money('otherIncome')?.value() || 0;
const vacancyRate = incomeSection.decimal('vacancyRate')?.value() || 5;
const grossAnnualIncome = (monthlyRent * units + otherIncome) * 12;
const effectiveIncome = grossAnnualIncome * (1 - vacancyRate / 100);
const propertyTax = (expensesSection.money('propertyTax')?.value() || 350) * 12;
const insurance = (expensesSection.money('insurance')?.value() || 120) * 12;
const hoa = (expensesSection.money('hoa')?.value() || 0) * 12;
const utilities = (expensesSection.money('utilities')?.value() || 0) * 12;
const managementFee = (expensesSection.decimal('managementFee')?.value() || 8) / 100 * effectiveIncome;
const maintenanceReserve = (expensesSection.decimal('maintenanceReserve')?.value() || 5) / 100 * effectiveIncome;
const otherExpenses = (expensesSection.money('otherExpenses')?.value() || 0) * 12;
const totalExpenses = propertyTax + insurance + hoa + utilities + managementFee + maintenanceReserve + otherExpenses;
const noi = effectiveIncome - totalExpenses;
const capRate = (noi / totalInvestment) * 100;
return `Cap Rate: ${(Number(capRate) || 0).toFixed(2)}%`;
},
customStyles: { 'font-size': '1.1rem', 'font-weight': '600', 'color': '#7c3aed', 'text-align': 'center' }
});
});
// Cash Flow Section
const cashFlowSection = form.addSubform('cashFlow', { title: '💰 Cash Flow', isCollapsible: false });
cashFlowSection.addRow(row => {
row.addPriceDisplay('monthlyCashFlow', {
label: 'Monthly Cash Flow',
computedValue: () => {
const monthlyRent = incomeSection.money('monthlyRent')?.value() || 2200;
const units = incomeSection.integer('units')?.value() || 1;
const otherIncome = incomeSection.money('otherIncome')?.value() || 0;
const vacancyRate = incomeSection.decimal('vacancyRate')?.value() || 5;
const grossMonthlyIncome = monthlyRent * units + otherIncome;
const effectiveMonthlyIncome = grossMonthlyIncome * (1 - vacancyRate / 100);
const propertyTax = expensesSection.money('propertyTax')?.value() || 350;
const insurance = expensesSection.money('insurance')?.value() || 120;
const hoa = expensesSection.money('hoa')?.value() || 0;
const utilities = expensesSection.money('utilities')?.value() || 0;
const managementFee = (expensesSection.decimal('managementFee')?.value() || 8) / 100 * effectiveMonthlyIncome;
const maintenanceReserve = (expensesSection.decimal('maintenanceReserve')?.value() || 5) / 100 * effectiveMonthlyIncome;
const otherExpenses = expensesSection.money('otherExpenses')?.value() || 0;
let totalMonthlyExpenses = propertyTax + insurance + hoa + utilities + managementFee + maintenanceReserve + otherExpenses;
// Add mortgage if using financing
if (financingSection.checkbox('useFinancing')?.value()) {
const purchasePrice = propertySection.money('purchasePrice')?.value() || 350000;
const downPaymentPercent = financingSection.decimal('downPayment')?.value() || 25;
const loanAmount = purchasePrice * (1 - downPaymentPercent / 100);
const interestRate = financingSection.decimal('interestRate')?.value() || 7;
const term = parseInt(financingSection.dropdown('loanTerm')?.value() || '30') * 12;
const monthlyRate = interestRate / 100 / 12;
if (monthlyRate > 0 && loanAmount > 0) {
const mortgagePayment = loanAmount * (monthlyRate * Math.pow(1 + monthlyRate, term)) / (Math.pow(1 + monthlyRate, term) - 1);
totalMonthlyExpenses += mortgagePayment;
}
}
return Math.round((effectiveMonthlyIncome - totalMonthlyExpenses) * 100) / 100;
},
variant: 'large'
});
});
cashFlowSection.addRow(row => {
row.addPriceDisplay('annualCashFlow', {
label: 'Annual Cash Flow',
computedValue: () => {
const monthlyRent = incomeSection.money('monthlyRent')?.value() || 2200;
const units = incomeSection.integer('units')?.value() || 1;
const otherIncome = incomeSection.money('otherIncome')?.value() || 0;
const vacancyRate = incomeSection.decimal('vacancyRate')?.value() || 5;
const grossMonthlyIncome = monthlyRent * units + otherIncome;
const effectiveMonthlyIncome = grossMonthlyIncome * (1 - vacancyRate / 100);
const propertyTax = expensesSection.money('propertyTax')?.value() || 350;
const insurance = expensesSection.money('insurance')?.value() || 120;
const hoa = expensesSection.money('hoa')?.value() || 0;
const utilities = expensesSection.money('utilities')?.value() || 0;
const managementFee = (expensesSection.decimal('managementFee')?.value() || 8) / 100 * effectiveMonthlyIncome;
const maintenanceReserve = (expensesSection.decimal('maintenanceReserve')?.value() || 5) / 100 * effectiveMonthlyIncome;
const otherExpenses = expensesSection.money('otherExpenses')?.value() || 0;
let totalMonthlyExpenses = propertyTax + insurance + hoa + utilities + managementFee + maintenanceReserve + otherExpenses;
if (financingSection.checkbox('useFinancing')?.value()) {
const purchasePrice = propertySection.money('purchasePrice')?.value() || 350000;
const downPaymentPercent = financingSection.decimal('downPayment')?.value() || 25;
const loanAmount = purchasePrice * (1 - downPaymentPercent / 100);
const interestRate = financingSection.decimal('interestRate')?.value() || 7;
const term = parseInt(financingSection.dropdown('loanTerm')?.value() || '30') * 12;
const monthlyRate = interestRate / 100 / 12;
if (monthlyRate > 0 && loanAmount > 0) {
const mortgagePayment = loanAmount * (monthlyRate * Math.pow(1 + monthlyRate, term)) / (Math.pow(1 + monthlyRate, term) - 1);
totalMonthlyExpenses += mortgagePayment;
}
}
return Math.round((effectiveMonthlyIncome - totalMonthlyExpenses) * 12 * 100) / 100;
},
variant: 'success'
}, '1fr');
row.addTextPanel('cashOnCash', {
computedValue: () => {
const purchasePrice = propertySection.money('purchasePrice')?.value() || 350000;
const closingCosts = propertySection.money('closingCosts')?.value() || 10000;
const renovations = propertySection.money('renovations')?.value() || 0;
let totalCashInvested = purchasePrice + closingCosts + renovations;
if (financingSection.checkbox('useFinancing')?.value()) {
const downPaymentPercent = financingSection.decimal('downPayment')?.value() || 25;
totalCashInvested = purchasePrice * (downPaymentPercent / 100) + closingCosts + renovations;
}
const monthlyRent = incomeSection.money('monthlyRent')?.value() || 2200;
const units = incomeSection.integer('units')?.value() || 1;
const otherIncome = incomeSection.money('otherIncome')?.value() || 0;
const vacancyRate = incomeSection.decimal('vacancyRate')?.value() || 5;
const grossMonthlyIncome = monthlyRent * units + otherIncome;
const effectiveMonthlyIncome = grossMonthlyIncome * (1 - vacancyRate / 100);
const propertyTax = expensesSection.money('propertyTax')?.value() || 350;
const insurance = expensesSection.money('insurance')?.value() || 120;
const hoa = expensesSection.money('hoa')?.value() || 0;
const utilities = expensesSection.money('utilities')?.value() || 0;
const managementFee = (expensesSection.decimal('managementFee')?.value() || 8) / 100 * effectiveMonthlyIncome;
const maintenanceReserve = (expensesSection.decimal('maintenanceReserve')?.value() || 5) / 100 * effectiveMonthlyIncome;
const otherExpenses = expensesSection.money('otherExpenses')?.value() || 0;
let totalMonthlyExpenses = propertyTax + insurance + hoa + utilities + managementFee + maintenanceReserve + otherExpenses;
if (financingSection.checkbox('useFinancing')?.value()) {
const loanAmount = purchasePrice * (1 - (financingSection.decimal('downPayment')?.value() || 25) / 100);
const interestRate = financingSection.decimal('interestRate')?.value() || 7;
const term = parseInt(financingSection.dropdown('loanTerm')?.value() || '30') * 12;
const monthlyRate = interestRate / 100 / 12;
if (monthlyRate > 0 && loanAmount > 0) {
const mortgagePayment = loanAmount * (monthlyRate * Math.pow(1 + monthlyRate, term)) / (Math.pow(1 + monthlyRate, term) - 1);
totalMonthlyExpenses += mortgagePayment;
}
}
const annualCashFlow = (effectiveMonthlyIncome - totalMonthlyExpenses) * 12;
const cashOnCash = (annualCashFlow / totalCashInvested) * 100;
return `Cash-on-Cash Return: ${(Number(cashOnCash) || 0).toFixed(2)}%`;
},
customStyles: { 'font-size': '1rem', 'font-weight': '600', 'color': '#dc2626', 'text-align': 'center' }
}, '1fr');
});
// Summary Section
const summarySection = form.addSubform('summary', {
title: '📋 Summary',
isCollapsible: false,
sticky: 'bottom'
});
summarySection.addRow(row => {
row.addTextPanel('summaryText', {
computedValue: () => {
const purchasePrice = propertySection.money('purchasePrice')?.value() || 350000;
const monthlyRent = incomeSection.money('monthlyRent')?.value() || 2200;
const units = incomeSection.integer('units')?.value() || 1;
return `$${purchasePrice.toLocaleString()} property | $${(monthlyRent * units).toLocaleString()}/month rent`;
},
customStyles: { 'font-size': '0.95rem', 'font-weight': '500', 'text-align': 'center', 'color': '#1e293b' }
});
});
summarySection.addRow(row => {
row.addTextPanel('disclaimer', {
computedValue: () => 'Projections are estimates. Actual returns depend on market conditions, tenant quality, and property management.',
customStyles: { 'font-size': '0.8rem', 'color': '#64748b', 'text-align': 'center' }
});
});
form.configureSubmitButton({
label: 'Save Analysis'
});
}