${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}
${category}
| ${item.label}: | ${item.prefix || ''}${item.value}${item.suffix || ''} |
