Return on Investment (ROI):
0.00%
${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 acq = appData['Land Acquisition Costs'];
const dev = appData['Development & Soft Costs'];
const sales = appData['Projected Sales'];
// Calculate Costs
const acquisitionCost = (parseFloat(acq.purchase_price.value) || 0) + (parseFloat(acq.closing_costs.value) || 0);
const baseDevCost = (parseFloat(dev.surveying_platting.value) || 0) + (parseFloat(dev.infrastructure.value) || 0) + (parseFloat(dev.permits_fees.value) || 0);
const contingency = baseDevCost * 0.15;
dev.contingency.value = contingency.toFixed(2);
document.getElementById('input-Development & Soft Costs-contingency').value = contingency.toFixed(2);
const totalDevCost = baseDevCost + contingency;
const totalProjectCost = acquisitionCost + totalDevCost;
// Calculate Revenue
const totalRevenue = (parseFloat(sales.number_of_lots.value) || 0) * (parseFloat(sales.price_per_lot.value) || 0);
const sellingCosts = totalRevenue * 0.06;
sales.selling_costs.value = sellingCosts.toFixed(2);
document.getElementById('input-Projected Sales-selling_costs').value = sellingCosts.toFixed(2);
const netRevenue = totalRevenue - sellingCosts;
// Calculate Profit & ROI
const grossProfit = netRevenue - totalProjectCost;
const roi = totalProjectCost > 0 ? (grossProfit / totalProjectCost) * 100 : 0;
updateDashboard(totalProjectCost, totalRevenue, grossProfit, roi);
updateChart({
'Acquisition': acquisitionCost,
'Development': totalDevCost,
'Selling Costs': sellingCosts,
'Gross Profit': grossProfit > 0 ? grossProfit : 0
});
}
function updateDashboard(totalCost, totalRevenue, profit, roi) {
const summaryContainer = document.getElementById('summary-container');
summaryContainer.innerHTML = `
Total Projected Revenue:$${totalRevenue.toLocaleString('en-US', {maximumFractionDigits: 2})}
Total Estimated Cost:$${totalCost.toLocaleString('en-US', {maximumFractionDigits: 2})}
`;
document.getElementById('gross-profit').textContent = `$${profit.toLocaleString('en-US', {maximumFractionDigits: 2})}`;
document.getElementById('roi').textContent = `${roi.toFixed(2)}%`;
}
function updateChart(data) {
const ctx = document.getElementById('profit-chart').getContext('2d');
const labels = Object.keys(data);
const values = Object.values(data);
const isProfit = data['Gross Profit'] > 0;
if (profitChart) {
profitChart.data.labels = labels;
profitChart.data.datasets[0].data = values;
profitChart.update();
} else {
profitChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: labels,
datasets: [{
label: 'Financial Breakdown',
data: values,
backgroundColor: ['#fca5a5', '#fdba74', '#fde68a', '#86efac'],
borderColor: 'white',
borderWidth: 2
}]
},
options: {
responsive: true, maintainAspectRatio: false,
plugins: {
legend: { position: 'bottom', labels: { boxWidth: 12, padding: 15 } },
tooltip: {
callbacks: {
label: (c) => `${c.label}: $${parseFloat(c.raw).toLocaleString('en-US', {maximumFractionDigits: 0})}`
}
}
}
}
});
}
}
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;';
let html = `
Land Subdivision Profit Analysis
Gross Profit
${document.getElementById('gross-profit').textContent}
Return on Investment
${document.getElementById('roi').textContent}
${category}
| ${item.label} | ${item.prefix || ''}${value} |
