Latest Margin
${formatPercent(latestMargin)}
`;
};
const renderDataTable = () => {
dataTableBody.innerHTML = '';
state.timePeriods.forEach(p => {
const { netProfit, netProfitMargin } = calculateMetrics(p);
const row = `
| ${p.period} |
${formatCurrency(p.revenue)} |
${formatCurrency(netProfit)} |
${formatPercent(netProfitMargin)} |
`;
dataTableBody.innerHTML += row;
});
};
const renderChart = () => {
if (profitMarginChart) {
profitMarginChart.destroy();
}
const labels = state.timePeriods.map(p => p.period);
const revenueData = state.timePeriods.map(p => p.revenue);
const marginData = state.timePeriods.map(p => calculateMetrics(p).netProfitMargin);
profitMarginChart = new Chart(chartCanvas.getContext('2d'), {
type: 'line',
data: {
labels: labels,
datasets: [
{
type: 'bar',
label: 'Revenue',
data: revenueData,
backgroundColor: 'rgba(59, 130, 246, 0.5)', // Blue-500
borderColor: 'rgba(59, 130, 246, 1)',
yAxisID: 'yRevenue',
},
{
type: 'line',
label: 'Net Profit Margin (%)',
data: marginData,
backgroundColor: 'rgba(13, 148, 136, 1)', // Teal-600
borderColor: 'rgba(13, 148, 136, 1)',
tension: 0.2,
fill: false,
yAxisID: 'yMargin',
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
yRevenue: {
type: 'linear',
display: true,
position: 'left',
title: { display: true, text: 'Revenue ($)' },
ticks: { callback: (value) => formatCurrency(value) }
},
yMargin: {
type: 'linear',
display: true,
position: 'right',
title: { display: true, text: 'Net Profit Margin (%)' },
grid: { drawOnChartArea: false },
ticks: { callback: (value) => `${value}%` }
}
}
}
});
};
const renderConfigRows = () => {
configRowsContainer.innerHTML = '';
state.timePeriods.forEach(p => {
const row = `
`;
configRowsContainer.innerHTML += row;
});
addConfigEventListeners();
};
// --- EVENT HANDLERS ---
const handleConfigChange = (e) => {
const id = parseInt(e.target.dataset.id);
const field = e.target.dataset.field;
const value = field === 'period' ? e.target.value : parseFloat(e.target.value) || 0;
const period = state.timePeriods.find(p => p.id === id);
if (period) {
period[field] = value;
}
renderAll();
};
const handleAddPeriod = () => {
const newId = state.timePeriods.length > 0 ? Math.max(...state.timePeriods.map(p => p.id)) + 1 : 1;
state.timePeriods.push({ id: newId, period: "New Period", revenue: 0, cogs: 0, opEx: 0, interestAndTax: 0 });
renderAll();
};
const handleRemovePeriod = (e) => {
const id = parseInt(e.target.dataset.id);
state.timePeriods = state.timePeriods.filter(p => p.id !== id);
renderAll();
};
const addConfigEventListeners = () => {
document.querySelectorAll('.config-input').forEach(input => input.addEventListener('change', handleConfigChange));
document.querySelectorAll('.remove-period-btn').forEach(button => button.addEventListener('click', handleRemovePeriod));
};
const handleDownloadPdf = () => {
const { jsPDF } = window.jspdf;
const pdfContent = document.getElementById('pdf-content');
document.querySelectorAll('.no-print').forEach(el => el.style.visibility = 'hidden');
Chart.defaults.animation = false;
html2canvas(pdfContent, { scale: 2, useCORS: true, backgroundColor: '#ffffff' }).then(canvas => {
document.querySelectorAll('.no-print').forEach(el => el.style.visibility = 'visible');
Chart.defaults.animation = true;
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const imgWidth = pdfWidth - 20;
const imgHeight = canvas.height * imgWidth / canvas.width;
pdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight);
pdf.save('Net-Profit-Margin-Dashboard.pdf');
});
};
// --- TABBING LOGIC ---
let currentTabIndex = 0;
const updateTabButtons = () => {
prevTabBtn.disabled = currentTabIndex === 0;
nextTabBtn.disabled = currentTabIndex === tabContents.length - 1;
};
const switchTab = (index) => {
tabButtons.forEach(btn => btn.classList.remove('active'));
tabContents.forEach(content => content.classList.remove('active'));
tabButtons[index].classList.add('active');
tabContents[index].classList.add('active');
currentTabIndex = index;
updateTabButtons();
};
tabButtons.forEach((button, index) => button.addEventListener('click', () => switchTab(index)));
prevTabBtn.addEventListener('click', () => { if (currentTabIndex > 0) switchTab(currentTabIndex - 1); });
nextTabBtn.addEventListener('click', () => { if (currentTabIndex < tabContents.length - 1) switchTab(currentTabIndex + 1); });
// --- INITIALIZATION ---
if (kpiCardsContainer && addPeriodBtn && downloadPdfBtn) {
addPeriodBtn.addEventListener('click', handleAddPeriod);
downloadPdfBtn.addEventListener('click', handleDownloadPdf);
renderAll();
updateTabButtons();
} else {
console.error("Essential dashboard elements could not be found.");
}
});