389 lines
13 KiB
JavaScript
389 lines
13 KiB
JavaScript
// Dashboard functionality
|
|
let supportChart = null;
|
|
let entriesChart = null;
|
|
let signSizesChart = null;
|
|
|
|
// Load dashboard data
|
|
async function loadDashboardData() {
|
|
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);
|
|
} else {
|
|
showStatus('Failed to load dashboard data', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Dashboard loading error:', error);
|
|
showStatus('Error loading dashboard', 'error');
|
|
} finally {
|
|
setLoadingState(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', () => {
|
|
// Update navigation to load dashboard when clicked
|
|
const dashboardLink = document.querySelector('.admin-nav a[href="#dashboard"]');
|
|
if (dashboardLink) {
|
|
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);
|
|
});
|
|
});
|