VOD Views & Engagement Dashboard

VOD Views & Engagement

Total Views

0

Avg. View Duration

0m

Avg. Completion Rate

0%

Total Unique Viewers

0

Views Over Time

Engagement by Video (Avg. Minutes Watched)

VOD Asset Performance

No VOD data to display.

`; } renderViewsChart(metrics.viewsByDate); renderEngagementChart(metrics.engagementByVideo); }; const renderViewsChart = (viewsByDate) => { const ctx = elements.viewsChartCanvas?.getContext('2d'); if (!ctx) return; const sortedDates = Object.keys(viewsByDate).sort((a, b) => new Date(a) - new Date(b)); const chartData = sortedDates.map(date => viewsByDate[date]); if (viewsChart) viewsChart.destroy(); viewsChart = new Chart(ctx, { type: 'line', data: { labels: sortedDates, datasets: [{ label: 'Total Views', data: chartData, borderColor: '#7c3aed', backgroundColor: 'rgba(124, 58, 237, 0.1)', fill: true, tension: 0.3 }] }, options: { responsive: true, maintainAspectRatio: true } }); }; const renderEngagementChart = (engagementByVideo) => { const ctx = elements.engagementChartCanvas?.getContext('2d'); if (!ctx) return; const labels = Object.keys(engagementByVideo); const data = Object.values(engagementByVideo); if (engagementChart) engagementChart.destroy(); engagementChart = new Chart(ctx, { type: 'bar', data: { labels, datasets: [{ label: 'Avg. Minutes Watched', data, backgroundColor: '#3b82f6', borderColor: '#2563eb', borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: true, indexAxis: 'y' } }); }; const renderConfigPanel = () => { const container = elements.editableVodList; if (!container) return; container.innerHTML = ''; vodData.forEach(v => { const card = document.createElement('div'); card.className = 'grid grid-cols-1 md:grid-cols-4 gap-4 items-center bg-white p-3 rounded-lg border'; card.innerHTML = `
`; container.appendChild(card); }); }; const handleAddVod = (e) => { e.preventDefault(); try { const getNumericValue = (id) => parseFloat(document.getElementById(id).value) || 0; const newVod = { id: vodData.length > 0 ? Math.max(...vodData.map(v => v.id)) + 1 : 1, title: document.getElementById('videoTitle').value, views: getNumericValue('videoViews'), watchTime: getNumericValue('totalWatchTime'), duration: getNumericValue('videoDuration'), uniqueViewers: getNumericValue('uniqueViewers'), date: document.getElementById('publishDate').value }; vodData.push(newVod); elements.addVodForm.reset(); renderAll(); showToast('VOD record added!'); } catch (error) { console.error("Error adding VOD record:", error); showToast("Error: Could not add record."); } }; const handleConfigListClick = (e) => { const target = e.target.closest('button'); if (!target) return; const action = target.dataset.action; const id = parseInt(target.dataset.id, 10); if (action === 'update') { try { const index = vodData.findIndex(v => v.id === id); if (index === -1) return; const inputs = target.closest('.grid').querySelectorAll('input'); inputs.forEach(input => { const field = input.dataset.field; let value = input.value; if (input.type === 'number') value = parseFloat(value) || 0; vodData[index][field] = value; }); renderAll(); showToast('Record updated!'); } catch (error) { console.error("Error updating record:", error); showToast("Error: Could not save data."); } } else if (action === 'delete') { vodData = vodData.filter(v => v.id !== id); renderAll(); showToast('Record deleted.'); } }; const handleGeneratePDF = () => { try { const { jsPDF } = window.jspdf; const doc = new jsPDF(); const metrics = calculateMetrics(); if (metrics.totalViews === 0) { showToast("Cannot generate PDF with no data."); return; } doc.setFontSize(20); doc.text("VOD Views & Engagement Report", 105, 20, null, null, 'center'); doc.setFontSize(10); doc.text(`Generated on: ${new Date().toLocaleDateString()}`, 105, 26, null, null, 'center'); doc.autoTable({ startY: 35, head: [['Metric', 'Value']], body: [ ['Total Views', metrics.totalViews.toLocaleString()], ['Avg. View Duration (min)', metrics.avgDuration.toFixed(1)], ['Avg. Completion Rate', `${metrics.completionRate.toFixed(1)}%`], ['Total Unique Viewers', metrics.uniqueViewers.toLocaleString()] ], theme: 'grid' }); let finalY = doc.lastAutoTable.finalY || 60; doc.setFontSize(14); doc.text("Views Over Time", 14, finalY + 15); doc.addImage(elements.viewsChartCanvas.toDataURL('image/png', 1.0), 'PNG', 14, finalY + 20, 90, 50); doc.text("Engagement by Video", 115, finalY + 15); doc.addImage(elements.engagementChartCanvas.toDataURL('image/png', 1.0), 'PNG', 115, finalY + 20, 80, 60); finalY += 75; doc.setFontSize(14); doc.text("VOD Asset Performance", 14, finalY); const tableBody = vodData.map(v => { const avgDur = v.views > 0 ? v.watchTime / v.views : 0; const compRate = v.duration > 0 ? (avgDur / v.duration) * 100 : 0; return [v.title, v.views.toLocaleString(), `${avgDur.toFixed(1)}m`, `${compRate.toFixed(1)}%`]; }); doc.autoTable({ head: [['Title', 'Views', 'Avg. Duration', 'Completion %']], body: tableBody, startY: finalY + 5, theme: 'striped', headStyles: { fillColor: [124, 58, 237] } }); doc.save('VOD-Engagement-Report.pdf'); } catch (error) { console.error("Failed to generate PDF:", error); showToast("Error: Could not generate PDF."); } }; const switchTab = (tabName) => { currentTab = tabName; Object.values(elements.tabs).forEach(tab => tab.classList.add('hidden')); Object.values(elements.tabButtons).forEach(btn => btn.classList.remove('active')); elements.tabs[tabName].classList.remove('hidden'); elements.tabButtons[tabName].classList.add('active'); updateNavButtons(); }; const navigateTabs = (direction) => { const currentIndex = tabOrder.indexOf(currentTab); const newIndex = direction === 'next' ? Math.min(currentIndex + 1, tabOrder.length - 1) : Math.max(currentIndex - 1, 0); if (newIndex !== currentIndex) switchTab(tabOrder[newIndex]); }; const updateNavButtons = () => { const currentIndex = tabOrder.indexOf(currentTab); elements.navButtons.prev.disabled = currentIndex === 0; elements.navButtons.next.disabled = currentIndex === tabOrder.length - 1; }; const showToast = (message) => { if (!elements.toast) return; elements.toast.textContent = message; elements.toast.classList.add('show'); setTimeout(() => { elements.toast.classList.remove('show'); }, 3000); } elements.addVodForm.addEventListener('submit', handleAddVod); elements.editableVodList.addEventListener('click', handleConfigListClick); elements.pdfButton.addEventListener('click', handleGeneratePDF); elements.tabButtons.dashboard.addEventListener('click', () => switchTab('dashboard')); elements.tabButtons.config.addEventListener('click', () => switchTab('config')); elements.navButtons.prev.addEventListener('click', () => navigateTabs('prev')); elements.navButtons.next.addEventListener('click', () => navigateTabs('next')); renderAll(); updateNavButtons(); });
Scroll to Top