// Dashboard functionality let supportChart = null; let entriesChart = null; let signSizesChart = null; let dashboardLoading = false; // Load dashboard data async function loadDashboardData() { // Prevent multiple simultaneous loads if (dashboardLoading) { console.log('Dashboard already loading, skipping duplicate request'); return; } dashboardLoading = true; console.log('Loading dashboard data from dashboard.js'); try { // Show loading state setLoadingState(true); const response = await fetch('/api/admin/dashboard/stats'); const result = await response.json(); if (result.success) { updateDashboardCards(result.data); createSupportLevelChart(result.data.supportLevels); createSignSizesChart(result.data.signSizes); createEntriesChart(result.data.dailyEntries); console.log('Dashboard data loaded successfully from dashboard.js'); } else { showStatus('Failed to load dashboard data', 'error'); } } catch (error) { console.error('Dashboard loading error:', error); showStatus('Error loading dashboard', 'error'); } finally { setLoadingState(false); dashboardLoading = false; } } // Set loading state function setLoadingState(isLoading) { const cards = document.querySelectorAll('.card-value'); cards.forEach(card => { if (isLoading) { card.textContent = '...'; card.style.opacity = '0.6'; } else { card.style.opacity = '1'; } }); } // Update summary cards function updateDashboardCards(data) { document.getElementById('total-locations').textContent = data.totalLocations.toLocaleString(); document.getElementById('overall-score').textContent = data.overallScore; document.getElementById('sign-delivered').textContent = data.signDelivered.toLocaleString(); document.getElementById('total-users').textContent = data.totalUsers.toLocaleString(); } // Create support level distribution chart function createSupportLevelChart(supportLevels) { const ctx = document.getElementById('support-chart'); if (!ctx) return; if (supportChart) { supportChart.destroy(); } // Check if mobile const isMobile = window.innerWidth <= 480; supportChart = new Chart(ctx, { type: 'doughnut', data: { labels: ['Strong Support (1)', 'Support (2)', 'Neutral (3)', 'Opposed (4)'], datasets: [{ data: [ supportLevels['1'] || 0, supportLevels['2'] || 0, supportLevels['3'] || 0, supportLevels['4'] || 0 ], backgroundColor: [ '#4CAF50', '#FFC107', '#FF9800', '#F44336' ] }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: isMobile ? 'bottom' : 'bottom', labels: { padding: isMobile ? 10 : 20, font: { size: isMobile ? 10 : 12 }, usePointStyle: true, pointStyle: 'circle' } }, tooltip: { callbacks: { label: function(context) { const label = context.label || ''; const value = context.parsed || 0; const total = context.dataset.data.reduce((a, b) => a + b, 0); const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : 0; return `${label}: ${value} (${percentage}%)`; } } } } } }); } // Create sign sizes chart function createSignSizesChart(signSizes) { const ctx = document.getElementById('sign-sizes-chart'); if (!ctx) return; if (signSizesChart) { signSizesChart.destroy(); } // Check if mobile const isMobile = window.innerWidth <= 480; signSizesChart = new Chart(ctx, { type: 'bar', data: { labels: ['Regular', 'Large', 'Unsure'], datasets: [{ label: 'Signs Requested', data: [ signSizes['Regular'] || 0, signSizes['Large'] || 0, signSizes['Unsure'] || 0 ], backgroundColor: [ '#2196F3', // Blue for Regular '#4CAF50', // Green for Large '#FF9800' // Orange for Unsure ], borderColor: [ '#1976D2', '#388E3C', '#F57C00' ], borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: !isMobile, position: 'top' }, tooltip: { callbacks: { label: function(context) { const label = context.label || ''; const value = context.parsed.y || 0; const total = context.dataset.data.reduce((a, b) => a + b, 0); const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : 0; return `${label}: ${value} (${percentage}%)`; } } } }, scales: { y: { beginAtZero: true, ticks: { stepSize: 1, font: { size: isMobile ? 10 : 12 } }, grid: { display: !isMobile } }, x: { ticks: { font: { size: isMobile ? 10 : 12 } }, grid: { display: false } } } } }); } // Create daily entries chart function createEntriesChart(dailyEntries) { const ctx = document.getElementById('entries-chart'); if (!ctx) return; if (entriesChart) { entriesChart.destroy(); } // Check if mobile const isMobile = window.innerWidth <= 480; const isTablet = window.innerWidth <= 768; // Generate last 30 days const labels = []; const data = []; const today = new Date(); for (let i = 29; i >= 0; i--) { const date = new Date(today); date.setDate(date.getDate() - i); const dateKey = date.toISOString().split('T')[0]; // Format labels based on screen size if (isMobile) { labels.push(date.toLocaleDateString('en-US', { day: 'numeric' })); } else if (isTablet) { labels.push(date.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' })); } else { labels.push(date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })); } data.push(dailyEntries[dateKey] || 0); } entriesChart = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [{ label: 'New Entries', data: data, borderColor: 'rgb(75, 192, 192)', backgroundColor: 'rgba(75, 192, 192, 0.2)', tension: 0.1, fill: true, pointRadius: isMobile ? 2 : 3, pointHoverRadius: isMobile ? 4 : 5 }] }, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: 'index' }, plugins: { legend: { display: !isMobile, labels: { font: { size: isMobile ? 10 : 12 } } }, tooltip: { callbacks: { title: function(context) { const index = context[0].dataIndex; const date = new Date(today); date.setDate(date.getDate() - (29 - index)); return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); } } } }, scales: { x: { ticks: { maxTicksLimit: isMobile ? 6 : isTablet ? 10 : 15, font: { size: isMobile ? 9 : 11 } }, grid: { display: !isMobile } }, y: { beginAtZero: true, ticks: { stepSize: 1, font: { size: isMobile ? 9 : 11 } }, grid: { color: 'rgba(0, 0, 0, 0.1)' } } } } }); } // Add event listener for dashboard navigation document.addEventListener('DOMContentLoaded', () => { // Dashboard navigation is handled by admin-core.js // This listener is kept for backward compatibility but should not interfere const dashboardLink = document.querySelector('.admin-nav a[href="#dashboard"]'); if (dashboardLink) { // Only add our listener if admin-core.js hasn't already handled it const hasAdminCore = window.adminCore && typeof window.adminCore.showSection === 'function'; if (!hasAdminCore) { dashboardLink.addEventListener('click', (e) => { e.preventDefault(); // Hide all sections document.querySelectorAll('.admin-section').forEach(section => { section.style.display = 'none'; }); // Show dashboard const dashboardSection = document.getElementById('dashboard'); if (dashboardSection) { dashboardSection.style.display = 'block'; } // Update active nav document.querySelectorAll('.admin-nav a').forEach(link => { link.classList.remove('active'); }); dashboardLink.classList.add('active'); // Load dashboard data loadDashboardData(); }); } } // Handle window resize for chart responsiveness let resizeTimeout; window.addEventListener('resize', () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { // Only refresh charts if dashboard is visible const dashboardSection = document.getElementById('dashboard'); if (dashboardSection && dashboardSection.style.display !== 'none') { // Refresh charts with current data if (supportChart) { const currentData = supportChart.data.datasets[0].data; const supportLevels = { '1': currentData[0] || 0, '2': currentData[1] || 0, '3': currentData[2] || 0, '4': currentData[3] || 0 }; createSupportLevelChart(supportLevels); } if (signSizesChart) { const currentData = signSizesChart.data.datasets[0].data; const signSizes = { 'Regular': currentData[0] || 0, 'Large': currentData[1] || 0, 'Unsure': currentData[2] || 0 }; createSignSizesChart(signSizes); } if (entriesChart) { const currentLabels = entriesChart.data.labels; const currentData = entriesChart.data.datasets[0].data; // Reconstruct dailyEntries object from current chart data const dailyEntries = {}; const today = new Date(); currentData.forEach((value, index) => { const date = new Date(today); date.setDate(date.getDate() - (29 - index)); const dateKey = date.toISOString().split('T')[0]; dailyEntries[dateKey] = value; }); createEntriesChart(dailyEntries); } } }, 250); }); });