anaylitics dashboard
This commit is contained in:
@@ -43,6 +43,10 @@
|
||||
<button id="close-sidebar" class="close-sidebar">×</button>
|
||||
</div>
|
||||
<nav class="admin-nav">
|
||||
<a href="#dashboard">
|
||||
<span class="nav-icon">📊</span>
|
||||
<span class="nav-text">Dashboard</span>
|
||||
</a>
|
||||
<a href="#start-location" class="active">
|
||||
<span class="nav-icon">📍</span>
|
||||
<span class="nav-text">Start Location</span>
|
||||
@@ -66,6 +70,47 @@
|
||||
</div>
|
||||
|
||||
<div class="admin-content">
|
||||
<!-- Dashboard Section -->
|
||||
<section id="dashboard" class="admin-section" style="display: none;">
|
||||
<h2>Campaign Dashboard</h2>
|
||||
<p>Overview of campaign metrics and statistics</p>
|
||||
|
||||
<div class="dashboard-container">
|
||||
<!-- Summary Cards -->
|
||||
<div class="dashboard-cards">
|
||||
<div class="dashboard-card">
|
||||
<h3>Total Locations</h3>
|
||||
<div class="card-value" id="total-locations">-</div>
|
||||
</div>
|
||||
<div class="dashboard-card">
|
||||
<h3>Overall Score</h3>
|
||||
<div class="card-value" id="overall-score">-</div>
|
||||
<div class="card-subtitle">out of 4.0</div>
|
||||
</div>
|
||||
<div class="dashboard-card">
|
||||
<h3>Sign Requests</h3>
|
||||
<div class="card-value" id="sign-requests">-</div>
|
||||
</div>
|
||||
<div class="dashboard-card">
|
||||
<h3>Total Users</h3>
|
||||
<div class="card-value" id="total-users">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts -->
|
||||
<div class="dashboard-charts">
|
||||
<div class="chart-container">
|
||||
<h3>Support Level Distribution</h3>
|
||||
<canvas id="support-chart"></canvas>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<h3>Daily Entries (Last 30 Days)</h3>
|
||||
<canvas id="entries-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Start Location Section -->
|
||||
<section id="start-location" class="admin-section">
|
||||
<h2>Map Start Location</h2>
|
||||
@@ -372,6 +417,12 @@
|
||||
<!-- Cache Management -->
|
||||
<script src="js/cache-manager.js"></script>
|
||||
|
||||
<!-- Chart.js library -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
|
||||
<!-- Dashboard JavaScript -->
|
||||
<script src="js/dashboard.js"></script>
|
||||
|
||||
<!-- Admin JavaScript -->
|
||||
<script src="js/admin.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/* Admin Panel Specific Styles */
|
||||
@import url("modules/dashboard.css");
|
||||
|
||||
.admin-container {
|
||||
display: flex;
|
||||
height: calc(100vh - var(--header-height));
|
||||
|
||||
85
map/app/public/css/modules/dashboard.css
Normal file
85
map/app/public/css/modules/dashboard.css
Normal file
@@ -0,0 +1,85 @@
|
||||
/* Dashboard Styles */
|
||||
.dashboard-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.dashboard-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.dashboard-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.dashboard-card h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1rem;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.card-subtitle {
|
||||
font-size: 0.875rem;
|
||||
color: #999;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.dashboard-charts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.chart-container h3 {
|
||||
margin: 0 0 1.5rem 0;
|
||||
font-size: 1.25rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.chart-container canvas {
|
||||
height: 300px !important;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.dashboard-cards {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.dashboard-charts {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.dashboard-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
150
map/app/public/js/dashboard.js
Normal file
150
map/app/public/js/dashboard.js
Normal file
@@ -0,0 +1,150 @@
|
||||
// Dashboard functionality
|
||||
let supportChart = null;
|
||||
let entriesChart = null;
|
||||
|
||||
// Load dashboard data
|
||||
async function loadDashboardData() {
|
||||
try {
|
||||
const response = await fetch('/api/admin/dashboard/stats');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
updateDashboardCards(result.data);
|
||||
createSupportLevelChart(result.data.supportLevels);
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
// Update summary cards
|
||||
function updateDashboardCards(data) {
|
||||
document.getElementById('total-locations').textContent = data.totalLocations.toLocaleString();
|
||||
document.getElementById('overall-score').textContent = data.overallScore;
|
||||
document.getElementById('sign-requests').textContent = data.signRequests.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();
|
||||
}
|
||||
|
||||
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: 'bottom'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Create daily entries chart
|
||||
function createEntriesChart(dailyEntries) {
|
||||
const ctx = document.getElementById('entries-chart');
|
||||
if (!ctx) return;
|
||||
|
||||
if (entriesChart) {
|
||||
entriesChart.destroy();
|
||||
}
|
||||
|
||||
// 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];
|
||||
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
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
stepSize: 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();
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user