Total Revenue
${formatCurrency(totalRevenue)}
Avg. Revenue/View
$${avgRevenuePerView.toFixed(4)}
`;
};
const renderDataTable = () => {
dataTableBody.innerHTML = '';
const sortedLogs = [...state.logs].sort((a, b) => new Date(b.date) - new Date(a.date));
sortedLogs.forEach(log => {
const row = `
| ${log.date} |
${log.platform} |
${formatNumber(log.views)} |
${formatNumber(log.followers)} |
${formatCurrency(log.revenue)} |
`;
dataTableBody.innerHTML += row;
});
};
const renderCharts = () => {
// Follower Growth Chart with Plotly
const followersByDate = state.logs.reduce((acc, log) => {
acc[log.date] = (acc[log.date] || 0) + log.followers;
return acc;
}, {});
const sortedDates = Object.keys(followersByDate).sort((a, b) => new Date(a) - new Date(b));
const followerTrace = {
x: sortedDates,
y: sortedDates.map(date => followersByDate[date]),
type: 'scatter',
mode: 'lines+markers',
line: { color: '#8b5cf6' }
};
const followerLayout = {
margin: { l: 50, r: 20, b: 40, t: 20 },
xaxis: { title: 'Date' },
yaxis: { title: 'New Followers' }
};
Plotly.newPlot('followerTrendChart', [followerTrace], followerLayout, {responsive: true});
// Views by Platform with Plotly
const viewsByPlatform = state.logs.reduce((acc, log) => {
acc[log.platform] = (acc[log.platform] || 0) + log.views;
return acc;
}, {});
const platformLabels = Object.keys(viewsByPlatform);
const platformData = platformLabels.map(label => viewsByPlatform[label]);
const platformTrace = {
labels: platformLabels,
values: platformData,
type: 'pie',
hole: 0.4,
marker: {
colors: ['#FF0000', '#9146FF', '#000000'] // YouTube, Twitch, TikTok
}
};
const platformLayout = {
margin: { l: 20, r: 20, b: 20, t: 40 },
showlegend: true
};
Plotly.newPlot('platformChart', [platformTrace], platformLayout, {responsive: true});
};
const renderConfigRows = () => {
configRowsContainer.innerHTML = '';
const sortedLogs = [...state.logs].sort((a, b) => new Date(b.date) - new Date(a.date));
sortedLogs.forEach(log => {
configRowsContainer.innerHTML += `
`.replaceAll('class="config-input"', 'class="config-input w-full border-gray-300 rounded-md shadow-sm"');
});
addConfigEventListeners();
};
// --- EVENT HANDLERS ---
const handleConfigChange = (e) => {
const id = parseInt(e.target.dataset.id);
const field = e.target.dataset.field;
const value = (e.target.type === 'number') ? parseFloat(e.target.value) || 0 : e.target.value;
const log = state.logs.find(l => l.id === id);
if (log) log[field] = value;
renderAll();
};
const handleAddLog = () => {
const newId = state.logs.length > 0 ? Math.max(...state.logs.map(l => l.id)) + 1 : 1;
const today = new Date().toISOString().split('T')[0];
state.logs.push({ id: newId, date: today, platform: "YouTube", views: 0, followers: 0, revenue: 0 });
renderAll();
};
const handleRemoveLog = (e) => {
const id = parseInt(e.target.dataset.id);
state.logs = state.logs.filter(l => l.id !== id);
renderAll();
};
const addConfigEventListeners = () => {
document.querySelectorAll('.config-input').forEach(input => input.addEventListener('change', handleConfigChange));
document.querySelectorAll('.remove-btn').forEach(button => button.addEventListener('click', handleRemoveLog));
};
const handleDownloadPdf = () => {
const { jsPDF } = window.jspdf;
const pdfContent = document.getElementById('pdf-content');
document.querySelectorAll('.no-print').forEach(el => el.style.visibility = 'hidden');
html2canvas(pdfContent, { scale: 2, useCORS: true, backgroundColor: '#ffffff' }).then(canvas => {
document.querySelectorAll('.no-print').forEach(el => el.style.visibility = 'visible');
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('Platform-Analytics-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();
if (index === 0) {
// Redraw plotly charts on tab switch to ensure correct sizing
setTimeout(renderCharts, 10);
}
};
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 (kpiContainer && addLogBtn && downloadPdfBtn) {
addLogBtn.addEventListener('click', handleAddLog);
downloadPdfBtn.addEventListener('click', handleDownloadPdf);
renderAll();
updateTabButtons();
} else {
console.error("Essential dashboard elements could not be found.");
}
});