Est. Monthly Payout
${finalMonth.totalCommissions.toLocaleString('en-US', currencyFormat)}
Est. Monthly Profit
${finalMonth.netProfit.toLocaleString('en-US', currencyFormat)}
`;
// Render Table
dashboardTableBody.innerHTML = '';
projectionResults.forEach(r => {
const tr = document.createElement('tr');
tr.className = 'bg-white border-b hover:bg-gray-50';
tr.innerHTML = `
${r.month} |
${r.affiliates.toLocaleString()} |
${r.totalSales.toLocaleString('en-US', currencyFormat)} |
${r.totalCommissions.toLocaleString('en-US', currencyFormat)} |
${r.netProfit.toLocaleString('en-US', currencyFormat)} |
`;
dashboardTableBody.appendChild(tr);
});
// Render Charts
renderCharts(projectionResults);
dashboardDate.textContent = `A ${state.projectionPeriod}-month projection based on current configuration.`
};
const renderCharts = (data) => {
const labels = data.map(r => `M${r.month}`);
if (revenueChartInstance) revenueChartInstance.destroy();
revenueChartInstance = new Chart(document.getElementById('revenueChart'), {
type: 'line',
data: {
labels,
datasets: [
{ label: 'Total Sales', data: data.map(r => r.totalSales), borderColor: '#4f46e5', backgroundColor: '#4f46e520', fill: false, tension: 0.1 },
{ label: 'Net Profit', data: data.map(r => r.netProfit), borderColor: '#16a34a', backgroundColor: '#16a34a20', fill: false, tension: 0.1 },
{ label: 'Commissions', data: data.map(r => r.totalCommissions), borderColor: '#dc2626', backgroundColor: '#dc262620', fill: false, tension: 0.1 }
]
},
options: { responsive: true, maintainAspectRatio: true }
});
if (affiliateChartInstance) affiliateChartInstance.destroy();
affiliateChartInstance = new Chart(document.getElementById('affiliateChart'), {
type: 'bar',
data: {
labels,
datasets: [{ label: 'Number of Affiliates', data: data.map(r => r.affiliates), backgroundColor: '#2563eb' }]
},
options: { responsive: true, maintainAspectRatio: true }
});
};
const calculateAndRenderAll = () => {
const results = runProjection();
renderConfig();
renderDashboard(results);
updateNavButtons();
};
// --- Tab & Navigation Logic ---
function switchTab(targetTabId) {
if (currentTab === 'config') {
saveConfig();
}
currentTab = targetTabId;
tabs.forEach(tab => tab.classList.toggle('active', tab.dataset.tab === currentTab));
tabContents.forEach(content => content.classList.toggle('active', content.id === currentTab));
if (currentTab === 'dashboard') {
const results = runProjection();
renderDashboard(results);
}
updateNavButtons();
}
tabs.forEach(tab => tab.addEventListener('click', () => switchTab(tab.dataset.tab)));
nextBtn.addEventListener('click', () => currentTab === 'dashboard' ? switchTab('config') : switchTab('dashboard'));
prevBtn.addEventListener('click', () => currentTab === 'config' ? switchTab('dashboard') : switchTab('config'));
function updateNavButtons() {
if (currentTab === 'dashboard') {
nextBtn.textContent = 'Configure Data';
prevBtn.style.display = 'none';
pdfButtonContainer.style.display = 'block';
} else {
nextBtn.textContent = 'Update Forecast';
prevBtn.style.display = 'inline-flex';
pdfButtonContainer.style.display = 'none';
}
}
// --- PDF Download ---
downloadPdfBtn.addEventListener('click', async () => {
const { jsPDF } = window.jspdf;
const doc = new jsPDF({ orientation: 'l', unit: 'mm', format: 'a4' });
const content = document.getElementById('pdf-content');
await new Promise(r => setTimeout(r, 100)); // Allow charts to render fully
doc.setFont('helvetica', 'bold');
doc.setFontSize(18);
doc.text('Affiliate Program Scalability Forecast', 148, 15, { align: 'center' });
doc.setFontSize(10);
doc.text(dashboardDate.textContent, 148, 22, { align: 'center' });
const canvas = await html2canvas(content, { scale: 2 });
const imgData = canvas.toDataURL('image/jpeg', 1.0);
const imgProps = doc.getImageProperties(imgData);
const pdfWidth = doc.internal.pageSize.getWidth() - 28;
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
doc.addImage(imgData, 'JPEG', 14, 30, pdfWidth, pdfHeight);
doc.save(`Affiliate-Scalability-Forecast-${new Date().toISOString().slice(0,10)}.pdf`);
});
// --- Initial Load ---
calculateAndRenderAll();
});