`;
};
const renderDataTable = () => {
dataTableBody.innerHTML = '';
state.channels.forEach(ch => {
const cpc = ch.spend > 0 && ch.conversions > 0 ? ch.spend / ch.conversions : 0;
const roi = ch.spend > 0 ? (ch.revenue - ch.spend) / ch.spend : (ch.revenue > 0 ? Infinity : 0);
const roiText = roi === Infinity ? 'Infinite' : formatPercent(roi);
const row = `
${ch.name}
${formatCurrency(ch.spend)}
${formatCurrency(ch.revenue)}
${formatNumber(ch.conversions)}
${formatCurrency(cpc)}
${roiText}
`;
dataTableBody.innerHTML += row;
});
};
const renderChart = () => {
if (attributionChart) {
attributionChart.destroy();
}
const ctx = chartCanvas.getContext('2d');
attributionChart = new Chart(ctx, {
type: 'bar',
data: {
labels: state.channels.map(ch => ch.name),
datasets: [{
label: 'Conversions',
data: state.channels.map(ch => ch.conversions),
backgroundColor: 'rgba(79, 70, 229, 0.7)',
borderColor: 'rgba(79, 70, 229, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
grid: {
color: '#e5e7eb'
}
},
x: {
grid: {
display: false
}
}
},
plugins: {
legend: {
display: false
}
}
}
});
};
const renderConfigRows = () => {
configRowsContainer.innerHTML = '';
state.channels.forEach((ch, index) => {
const row = `
`;
configRowsContainer.innerHTML += row;
});
document.querySelectorAll('.config-input').forEach(input => {
input.addEventListener('change', handleConfigChange);
});
document.querySelectorAll('.remove-channel-btn').forEach(button => {
button.addEventListener('click', handleRemoveChannel);
});
};
// --- EVENT HANDLERS ---
const handleConfigChange = (e) => {
const id = parseInt(e.target.dataset.id);
const field = e.target.dataset.field;
const value = field === 'name' ? e.target.value : parseFloat(e.target.value) || 0;
const channel = state.channels.find(ch => ch.id === id);
if (channel) {
channel[field] = value;
}
renderAll();
};
const handleAddChannel = () => {
const newId = state.channels.length > 0 ? Math.max(...state.channels.map(ch => ch.id)) + 1 : 1;
state.channels.push({ id: newId, name: "New Channel", spend: 0, revenue: 0, conversions: 0 });
renderAll();
};
const handleRemoveChannel = (e) => {
const id = parseInt(e.target.dataset.id);
state.channels = state.channels.filter(ch => ch.id !== id);
renderAll();
};
const handleDownloadPdf = () => {
const { jsPDF } = window.jspdf;
const pdfContent = document.getElementById('pdf-content');
const buttonsToHide = document.querySelectorAll('.no-print');
// Hide buttons before capture
buttonsToHide.forEach(btn => btn.style.visibility = 'hidden');
html2canvas(pdfContent, {
scale: 2, // Higher scale for better quality
useCORS: true,
backgroundColor: '#ffffff',
onclone: (document) => {
// This ensures chart animations are off in the clone
Chart.defaults.animation = false;
}
}).then(canvas => {
// Show buttons again after capture
buttonsToHide.forEach(btn => btn.style.visibility = 'visible');
Chart.defaults.animation = true; // Re-enable animations
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; // with margin
const imgHeight = canvas.height * imgWidth / canvas.width;
pdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight);
pdf.save('Multi-Channel-Attribution-Dashboard.pdf');
}).catch(err => {
console.error("Error generating PDF:", err);
buttonsToHide.forEach(btn => btn.style.visibility = 'visible');
Chart.defaults.animation = true;
});
};
// --- 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 && dataTableBody && configRowsContainer && addChannelBtn && downloadPdfBtn && chartCanvas) {
addChannelBtn.addEventListener('click', handleAddChannel);
downloadPdfBtn.addEventListener('click', handleDownloadPdf);
renderAll();
updateTabButtons();
} else {
console.error("One or more essential elements could not be found in the DOM.");
}
});
