Multi-Channel Attribution Dashboard

Multi-Channel Attribution Dashboard

Analyze marketing spend, conversions, and ROI across channels.

Overall Performance

Conversions per Channel

Channel Breakdown

Channel Spend Revenue Conversions Cost/Conversion ROI

Enter Channel Data

${formatPercent(overallRoi)}

`; }; 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."); } });
Scroll to Top