From d775dea0dc052a6840291cf0c9a97ea77c0bd10c Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 30 Jul 2025 09:15:56 -0600 Subject: [PATCH] mobile friendliness --- map/app/public/css/modules/dashboard.css | 60 +++++++++++ map/app/public/js/dashboard.js | 132 ++++++++++++++++++++++- 2 files changed, 188 insertions(+), 4 deletions(-) diff --git a/map/app/public/css/modules/dashboard.css b/map/app/public/css/modules/dashboard.css index cbe5c33..37c5e56 100644 --- a/map/app/public/css/modules/dashboard.css +++ b/map/app/public/css/modules/dashboard.css @@ -51,6 +51,8 @@ padding: 1.5rem; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); border: 1px solid #e0e0e0; + position: relative; + min-height: 350px; } .chart-container h3 { @@ -61,25 +63,83 @@ .chart-container canvas { height: 300px !important; + max-width: 100%; + width: 100% !important; + touch-action: pan-y; /* Allow vertical scrolling on mobile */ } /* Responsive design */ @media (max-width: 768px) { + .dashboard-container { + gap: 1.5rem; + } + .dashboard-cards { grid-template-columns: repeat(2, 1fr); + gap: 1rem; } .dashboard-charts { grid-template-columns: 1fr; + gap: 1.5rem; + } + + .dashboard-card { + padding: 1rem; } .card-value { font-size: 2rem; } + + .chart-container { + padding: 1rem; + min-height: 300px; + } + + .chart-container canvas { + height: 250px !important; + } } @media (max-width: 480px) { + .dashboard-container { + gap: 1rem; + } + .dashboard-cards { grid-template-columns: 1fr; + gap: 0.75rem; + } + + .dashboard-card { + padding: 0.75rem; + } + + .dashboard-card h3 { + font-size: 0.875rem; + margin-bottom: 0.75rem; + } + + .card-value { + font-size: 1.75rem; + } + + .card-subtitle { + font-size: 0.75rem; + } + + .chart-container { + padding: 0.75rem; + min-height: 250px; + } + + .chart-container h3 { + font-size: 1rem; + margin-bottom: 1rem; + } + + .chart-container canvas { + height: 200px !important; } } diff --git a/map/app/public/js/dashboard.js b/map/app/public/js/dashboard.js index 67a7306..0357145 100644 --- a/map/app/public/js/dashboard.js +++ b/map/app/public/js/dashboard.js @@ -38,6 +38,9 @@ function createSupportLevelChart(supportLevels) { supportChart.destroy(); } + // Check if mobile + const isMobile = window.innerWidth <= 480; + supportChart = new Chart(ctx, { type: 'doughnut', data: { @@ -62,7 +65,26 @@ function createSupportLevelChart(supportLevels) { maintainAspectRatio: false, plugins: { legend: { - position: 'bottom' + 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}%)`; + } + } } } } @@ -78,6 +100,10 @@ function createEntriesChart(dailyEntries) { entriesChart.destroy(); } + // Check if mobile + const isMobile = window.innerWidth <= 480; + const isTablet = window.innerWidth <= 768; + // Generate last 30 days const labels = []; const data = []; @@ -87,7 +113,16 @@ function createEntriesChart(dailyEntries) { 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' })); + + // 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); } @@ -100,17 +135,65 @@ function createEntriesChart(dailyEntries) { data: data, borderColor: 'rgb(75, 192, 192)', backgroundColor: 'rgba(75, 192, 192, 0.2)', - tension: 0.1 + 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 + stepSize: 1, + font: { + size: isMobile ? 9 : 11 + } + }, + grid: { + color: 'rgba(0, 0, 0, 0.1)' } } } @@ -147,4 +230,45 @@ document.addEventListener('DOMContentLoaded', () => { 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 (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); + }); });