Tenant Retention Strategy Analyzer

Tenant Retention Strategy Analyzer

Analyze the ROI of your tenant retention efforts.

Net Annual Savings with Strategy:

$0.00

`; } else { let formHtml = `

${category}

`; for (const key in appData[category]) { formHtml += createFormGroup(key, appData[category][key], category); } formHtml += '
'; content.innerHTML = formHtml; } tabContentsContainer.appendChild(content); tabContents.push(content); }); updateTabs(); calculateAll(); setupEventListeners(); } function calculateAll() { const prop = appData['Property & Turnover Data']; const turn = appData['Turnover Costs (Per Unit)']; const ret = appData['Retention Strategy']; const monthlyRent = parseFloat(prop.monthly_rent.value) || 0; const lostRent = (parseFloat(turn.vacancy_days.value) / 30) * monthlyRent; const costPerTurnover = lostRent + (parseFloat(turn.marketing_cost.value) || 0) + (parseFloat(turn.repair_cost.value) || 0) + (parseFloat(turn.admin_cost.value) || 0); const numUnits = parseInt(prop.num_units.value) || 0; const currentTurnoverRate = (parseFloat(prop.turnover_rate.value) || 0) / 100; const initialTurnovers = numUnits * currentTurnoverRate; const costWithoutStrategy = initialTurnovers * costPerTurnover; const retentionIncrease = (parseFloat(ret.retention_increase.value) || 0) / 100; const turnoversPrevented = initialTurnovers * retentionIncrease; const newTurnovers = initialTurnovers - turnoversPrevented; const costOfNewTurnovers = newTurnovers * costPerTurnover; const costOfStrategy = numUnits * (parseFloat(ret.strategy_cost.value) || 0); const costWithStrategy = costOfNewTurnovers + costOfStrategy; const netSavings = costWithoutStrategy - costWithStrategy; updateDashboard(costWithoutStrategy, costWithStrategy, netSavings); updateChart(costWithoutStrategy, costWithStrategy); } function updateDashboard(costWithout, costWith, savings) { const summaryContainer = document.getElementById('summary-container'); summaryContainer.innerHTML = `
Annual Cost (No Strategy): $${costWithout.toLocaleString('en-US', {maximumFractionDigits: 2})}
Annual Cost (With Strategy): $${costWith.toLocaleString('en-US', {maximumFractionDigits: 2})}
`; document.getElementById('net-savings').textContent = `$${savings.toLocaleString('en-US', {maximumFractionDigits: 2})}`; } function updateChart(costWithout, costWith) { const ctx = document.getElementById('analysis-chart').getContext('2d'); const data = { labels: ['Without Strategy', 'With Strategy'], datasets: [{ label: 'Total Annual Cost', data: [costWithout, costWith], backgroundColor: ['#ef4444', '#3b82f6'], borderColor: ['#dc2626', '#2563eb'], borderWidth: 1, borderRadius: 5, }] }; if (analysisChart) { analysisChart.data.datasets[0].data = [costWithout, costWith]; analysisChart.update(); } else { analysisChart = new Chart(ctx, { type: 'bar', data: data, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { ticks: { callback: (v) => '$' + (v/1000) + 'k' } } } } }); } } function updateTabs() { tabs.forEach((tab, i) => tab.classList.toggle('active', i === currentTabIndex)); tabContents.forEach((c, i) => c.classList.toggle('active', i === currentTabIndex)); prevBtn.disabled = currentTabIndex === 0; nextBtn.disabled = currentTabIndex === tabs.length - 1; prevBtn.classList.toggle('opacity-50', currentTabIndex === 0); nextBtn.classList.toggle('opacity-50', currentTabIndex === tabs.length - 1); } function setupEventListeners() { tabsContainer.addEventListener('click', (e) => { if (e.target.classList.contains('tab')) { currentTabIndex = parseInt(e.target.dataset.index); updateTabs(); } }); prevBtn.addEventListener('click', () => { if (currentTabIndex > 0) { currentTabIndex--; updateTabs(); } }); nextBtn.addEventListener('click', () => { if (currentTabIndex < tabs.length - 1) { currentTabIndex++; updateTabs(); } }); tabContentsContainer.addEventListener('input', (e) => { if (e.target.tagName === 'INPUT') { const category = e.target.dataset.category; const key = e.target.dataset.key; if (appData[category] && appData[category][key]) { appData[category][key].value = e.target.value; calculateAll(); } } }); document.getElementById('pdf-download-btn').addEventListener('click', generatePdf); } function generatePdf() { const { jsPDF } = window.jspdf; const pdf = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' }); const pdfContainer = document.createElement('div'); pdfContainer.style.cssText = 'position:absolute; left:-9999px; width:800px; padding:20px; font-family:Inter,sans-serif; color:#1f2937; background:white;'; const chartCanvas = document.getElementById('analysis-chart'); const chartImgData = chartCanvas.toDataURL('image/png', 1.0); let html = `

Tenant Retention Analysis

Net Annual Savings:

${document.getElementById('net-savings').textContent}

`; for (const category in appData) { if (category === 'Dashboard') continue; html += `

${category}

`; for (const key in appData[category]) { const item = appData[category][key]; html += ``; } html += '
${item.label}:${item.prefix || ''}${item.value}${item.suffix || ''}
'; } pdfContainer.innerHTML = html; document.body.appendChild(pdfContainer); html2canvas(pdfContainer, { scale: 2 }).then(canvas => { const imgData = canvas.toDataURL('image/png'); const { width, height } = pdf.internal.pageSize; const imgHeight = canvas.height * width / canvas.width; pdf.addImage(imgData, 'PNG', 0, 0, width, imgHeight); pdf.save('tenant-retention-analysis.pdf'); document.body.removeChild(pdfContainer); }); } initialize(); });
Scroll to Top