// Dashboard specific functionality for Relevant Reflex (function() { 'use strict'; // Dashboard module window.RR = window.RR || {}; RR.dashboard = { charts: {}, updateInterval: null, refreshRate: 30000, // 30 seconds init: function() { this.initializeCharts(); this.setupQuickActions(); this.setupRealTimeUpdates(); this.setupInteractions(); this.loadDashboardData(); }, initializeCharts: function() { // Performance Chart const performanceCanvas = document.getElementById('performanceChart'); if (performanceCanvas) { this.charts.performance = this.createPerformanceChart(performanceCanvas); } // Revenue Chart (for finance page) const revenueCanvas = document.getElementById('revenueChart'); if (revenueCanvas) { this.charts.revenue = this.createRevenueChart(revenueCanvas); } // Demand Chart (for demand page) const demandCanvas = document.getElementById('demandChart'); if (demandCanvas) { this.charts.demand = this.createDemandChart(demandCanvas); } // Setup chart period controls this.setupChartControls(); }, createPerformanceChart: function(canvas) { const ctx = canvas.getContext('2d'); // Sample performance data - replace with real API data const data = { labels: ['Week 1', 'Week 2', 'Week 3', 'Week 4', 'Week 5', 'Week 6'], datasets: [{ label: 'Panel Responses', data: [320, 450, 380, 520, 430, 580], borderColor: getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim(), backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() + '20', borderWidth: 3, tension: 0.4, fill: true }, { label: 'Active Users', data: [280, 390, 420, 480, 510, 540], borderColor: getComputedStyle(document.documentElement).getPropertyValue('--success-color').trim(), backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--success-color').trim() + '20', borderWidth: 3, tension: 0.4, fill: true }] }; return this.drawChart(ctx, canvas, data, 'line'); }, createRevenueChart: function(canvas) { const ctx = canvas.getContext('2d'); // Sample revenue data for last 12 months const data = { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], datasets: [{ label: 'Revenue', data: [28500, 31200, 29800, 33500, 35200, 38900, 42100, 39800, 43500, 45230, 41800, 44600], backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim(), borderColor: getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim(), borderWidth: 1 }] }; return this.drawChart(ctx, canvas, data, 'bar'); }, createDemandChart: function(canvas) { const ctx = canvas.getContext('2d'); // Sample demand trend data const currentData = [6.2, 6.8, 7.1, 7.5, 8.2, 8.0, 8.5, 8.7, 9.1, 8.9, 8.7, 9.2]; const forecastData = [9.0, 9.2, 9.5, 9.7, 9.9, 10.2]; const data = { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan+', 'Feb+', 'Mar+', 'Apr+', 'May+', 'Jun+'], datasets: [{ label: 'Current Demand', data: [...currentData, ...Array(6).fill(null)], borderColor: getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim(), backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() + '30', borderWidth: 3, tension: 0.4, fill: false }, { label: 'Forecast', data: [...Array(11).fill(null), currentData[11], ...forecastData], borderColor: getComputedStyle(document.documentElement).getPropertyValue('--success-color').trim(), backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--success-color').trim() + '30', borderWidth: 2, borderDash: [5, 5], tension: 0.4, fill: false }] }; return this.drawChart(ctx, canvas, data, 'line'); }, drawChart: function(ctx, canvas, data, type) { // Simple chart implementation (replace with Chart.js for production) const chart = { data: data, type: type, canvas: canvas, ctx: ctx }; this.renderChart(chart); return chart; }, renderChart: function(chart) { const { ctx, canvas, data, type } = chart; const padding = 40; const chartWidth = canvas.width - 2 * padding; const chartHeight = canvas.height - 2 * padding; // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); if (type === 'line') { this.renderLineChart(ctx, canvas, data, padding, chartWidth, chartHeight); } else if (type === 'bar') { this.renderBarChart(ctx, canvas, data, padding, chartWidth, chartHeight); } }, renderLineChart: function(ctx, canvas, data, padding, chartWidth, chartHeight) { const datasets = data.datasets; const labels = data.labels; // Find max and min values const allValues = datasets.flatMap(d => d.data.filter(v => v !== null)); const maxValue = Math.max(...allValues); const minValue = Math.min(...allValues); const range = maxValue - minValue || 1; // Draw axes ctx.strokeStyle = '#e9ecef'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(padding, padding); ctx.lineTo(padding, canvas.height - padding); ctx.lineTo(canvas.width - padding, canvas.height - padding); ctx.stroke(); // Draw grid lines ctx.strokeStyle = '#f1f3f4'; ctx.lineWidth = 1; for (let i = 1; i < 5; i++) { const y = padding + (i / 5) * chartHeight; ctx.beginPath(); ctx.moveTo(padding, y); ctx.lineTo(canvas.width - padding, y); ctx.stroke(); } // Draw labels ctx.fillStyle = '#666'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; labels.forEach((label, index) => { const x = padding + (index / (labels.length - 1)) * chartWidth; ctx.fillText(label, x, canvas.height - padding + 20); }); // Draw datasets datasets.forEach(dataset => { ctx.strokeStyle = dataset.borderColor; ctx.lineWidth = dataset.borderWidth || 2; if (dataset.borderDash) { ctx.setLineDash(dataset.borderDash); } else { ctx.setLineDash([]); } ctx.beginPath(); let firstPoint = true; dataset.data.forEach((value, index) => { if (value !== null) { const x = padding + (index / (labels.length - 1)) * chartWidth; const y = canvas.height - padding - ((value - minValue) / range) * chartHeight; if (firstPoint) { ctx.moveTo(x, y); firstPoint = false; } else { ctx.lineTo(x, y); } // Draw data points ctx.save(); ctx.fillStyle = dataset.borderColor; ctx.beginPath(); ctx.arc(x, y, 3, 0, 2 * Math.PI); ctx.fill(); ctx.restore(); } }); ctx.stroke(); }); }, renderBarChart: function(ctx, canvas, data, padding, chartWidth, chartHeight) { const dataset = data.datasets[0]; const labels = data.labels; const values = dataset.data; const maxValue = Math.max(...values); const barWidth = chartWidth / values.length * 0.8; // Draw axes ctx.strokeStyle = '#e9ecef'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(padding, padding); ctx.lineTo(padding, canvas.height - padding); ctx.lineTo(canvas.width - padding, canvas.height - padding); ctx.stroke(); // Draw bars ctx.fillStyle = dataset.backgroundColor; values.forEach((value, index) => { const barHeight = (value / maxValue) * chartHeight; const x = padding + (index / values.length) * chartWidth + (chartWidth / values.length - barWidth) / 2; const y = canvas.height - padding - barHeight; ctx.fillRect(x, y, barWidth, barHeight); // Draw value labels ctx.fillStyle = '#666'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; ctx.fillText('$' + (value / 1000).toFixed(0) + 'k', x + barWidth / 2, y - 5); // Draw month labels ctx.fillText(labels[index], x + barWidth / 2, canvas.height - padding + 15); ctx.fillStyle = dataset.backgroundColor; }); }, setupChartControls: function() { // Chart period selectors const periodSelects = document.querySelectorAll('#chartPeriod, #trendPeriod, #revenuePeriod'); periodSelects.forEach(select => { select.addEventListener('change', function() { const chartId = this.id.replace('Period', 'Chart'); RR.dashboard.updateChartPeriod(chartId, this.value); }); }); }, updateChartPeriod: function(chartId, period) { const chart = this.charts[chartId.replace('Chart', '')]; if (!chart) return; // Update chart data based on period // This would typically fetch new data from API console.log(`Updating ${chartId} for period: ${period}`); // Show loading state const canvas = chart.canvas; const ctx = chart.ctx; ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#666'; ctx.font = '16px Arial'; ctx.textAlign = 'center'; ctx.fillText('Loading...', canvas.width / 2, canvas.height / 2); // Simulate API call setTimeout(() => { this.renderChart(chart); RR.toast.show('Chart updated successfully', 'success'); }, 800); }, setupQuickActions: function() { // Quick action buttons const quickActions = { createPanel: () => { window.location.href = 'panel.php?action=create'; }, manageUsers: () => { window.location.href = 'users.php'; }, viewReports: () => { window.location.href = 'finance.php?tab=reports'; }, systemSettings: () => { window.location.href = 'settings.php'; } }; // Expose to global scope window.quickActions = quickActions; // Setup quick action cards document.querySelectorAll('.quick-action-card').forEach(card => { card.addEventListener('click', function() { const action = this.getAttribute('data-action'); if (action && quickActions[action]) { quickActions[action](); } }); }); // Setup header action buttons document.getElementById('addUserBtn')?.addEventListener('click', function() { RR.modals.open('addUserModal'); }); document.getElementById('createPanelBtn')?.addEventListener('click', function() { RR.modals.open('createPanelModal'); }); document.getElementById('addSupplyBtn')?.addEventListener('click', function() { RR.modals.open('addSupplyModal'); }); document.getElementById('addTransactionBtn')?.addEventListener('click', function() { RR.modals.open('addTransactionModal'); }); }, setupRealTimeUpdates: function() { // Auto-refresh dashboard data this.updateInterval = setInterval(() => { this.updateRealTimeData(); }, this.refreshRate); // Pause updates when page is not visible document.addEventListener('visibilitychange', () => { if (document.hidden) { clearInterval(this.updateInterval); } else { this.updateInterval = setInterval(() => { this.updateRealTimeData(); }, this.refreshRate); } }); }, updateRealTimeData: function() { // Update stat cards with simulated real-time data const statCards = document.querySelectorAll('.stat-card'); statCards.forEach(card => { const valueElement = card.querySelector('h3'); if (!valueElement) return; const currentText = valueElement.textContent; // Update revenue values if (currentText.includes('$')) { const currentValue = parseInt(currentText.replace(/[^0-9]/g, '')); const variation = Math.floor(Math.random() * 200) - 100; // -100 to +100 const newValue = Math.max(0, currentValue + variation); valueElement.textContent = '$' + RR.utils.formatNumber(newValue); // Add pulse animation card.style.transform = 'scale(1.02)'; setTimeout(() => { card.style.transform = ''; }, 200); } // Update numeric values else if (/^\d+$/.test(currentText)) { const currentValue = parseInt(currentText); const variation = Math.floor(Math.random() * 6) - 3; // -3 to +3 const newValue = Math.max(0, currentValue + variation); valueElement.textContent = newValue.toString(); // Add subtle highlight card.classList.add('updated'); setTimeout(() => { card.classList.remove('updated'); }, 1000); } }); // Update charts Object.keys(this.charts).forEach(chartKey => { if (Math.random() > 0.7) { // 30% chance to update each chart this.updateChartData(this.charts[chartKey]); } }); }, updateChartData: function(chart) { if (!chart || !chart.data) return; // Add new data point and remove oldest chart.data.datasets.forEach(dataset => { if (dataset.data.length > 0) { // Generate new data point based on last value const lastValue = dataset.data[dataset.data.length - 1]; const variation = (Math.random() - 0.5) * (lastValue * 0.1); const newValue = Math.max(0, lastValue + variation); dataset.data.push(Math.round(newValue)); // Remove oldest point if we have too many if (dataset.data.length > 12) { dataset.data.shift(); } } }); this.renderChart(chart); }, setupInteractions: function() { // Activity item interactions document.querySelectorAll('.activity-item').forEach(item => { item.addEventListener('click', function() { this.classList.toggle('expanded'); }); }); // Stat card interactions document.querySelectorAll('.stat-card').forEach(card => { card.addEventListener('click', function() { const cardType = this.querySelector('p').textContent.toLowerCase(); RR.dashboard.navigateToDetails(cardType); }); }); // Export functionality document.querySelectorAll('[id$="ExportBtn"], [id$="exportBtn"]').forEach(btn => { btn.addEventListener('click', function() { RR.dashboard.exportData(this.id); }); }); // Generate report functionality document.getElementById('generateReportBtn')?.addEventListener('click', function() { RR.dashboard.generateReport(); }); }, navigateToDetails: function(cardType) { const navigationMap = { 'total users': 'users.php', 'active panels': 'panel.php', 'pending supplies': 'supply.php', 'monthly revenue': 'finance.php', 'total revenue': 'finance.php', 'net profit': 'finance.php' }; const url = navigationMap[cardType]; if (url) { window.location.href = url; } }, exportData: function(buttonId) { const button = document.getElementById(buttonId); if (!button) return; // Show loading state const originalText = button.textContent; button.textContent = 'Exporting...'; button.disabled = true; // Simulate export process setTimeout(() => { // Reset button button.textContent = originalText; button.disabled = false; // Show success message RR.toast.show('Data exported successfully!', 'success'); // Simulate file download const link = document.createElement('a'); link.href = 'data:text/csv;charset=utf-8,Sample Export Data\nColumn1,Column2,Column3\nValue1,Value2,Value3'; link.download = `export-${new Date().toISOString().split('T')[0]}.csv`; document.body.appendChild(link); link.click(); document.body.removeChild(link); }, 2000); }, generateReport: function() { const button = document.getElementById('generateReportBtn'); if (!button) return; // Show loading state button.innerHTML = '⏳ Generating...'; button.disabled = true; // Simulate report generation setTimeout(() => { // Reset button button.innerHTML = '📊 Generate Report'; button.disabled = false; // Show success and open modal with report preview RR.toast.show('Report generated successfully!', 'success'); // Create and show report preview modal RR.dashboard.showReportPreview(); }, 3000); }, showReportPreview: function() { const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'reportPreviewModal'; modal.innerHTML = `
`; document.body.appendChild(modal); RR.modals.open('reportPreviewModal'); }, loadDashboardData: function() { // Load initial dashboard data console.log('Loading dashboard data...'); // This would typically make API calls to load real data // For now, we'll simulate data loading with a timeout setTimeout(() => { console.log('Dashboard data loaded successfully'); }, 1000); }, // Keyboard shortcuts setupKeyboardShortcuts: function() { document.addEventListener('keydown', function(e) { // Only trigger shortcuts if not typing in input fields if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { return; } if (e.ctrlKey || e.metaKey) { switch(e.key) { case '1': e.preventDefault(); window.location.href = 'users.php'; break; case '2': e.preventDefault(); window.location.href = 'panel.php'; break; case '3': e.preventDefault(); window.location.href = 'supply.php'; break; case '4': e.preventDefault(); window.location.href = 'demand.php'; break; case '5': e.preventDefault(); window.location.href = 'finance.php'; break; case 'h': e.preventDefault(); window.location.href = 'index.php'; break; } } }); }, // Cleanup function destroy: function() { if (this.updateInterval) { clearInterval(this.updateInterval); } } }; // Initialize dashboard when DOM is ready document.addEventListener('DOMContentLoaded', function() { // Only initialize on dashboard-related pages const isDashboardPage = document.querySelector('.dashboard-container, .stats-grid, .chart-section'); if (isDashboardPage) { RR.dashboard.init(); RR.dashboard.setupKeyboardShortcuts(); } }); // Initialize dashboard function for other pages to call window.initDashboard = function() { if (RR.dashboard) { RR.dashboard.init(); } }; // Cleanup on page unload window.addEventListener('beforeunload', function() { if (RR.dashboard) { RR.dashboard.destroy(); } }); // Add CSS for updated state const style = document.createElement('style'); style.textContent = ` .stat-card.updated { background: linear-gradient(135deg, var(--primary-light) 0%, rgba(0, 102, 204, 0.05) 100%); border-color: var(--primary-color); transform: translateY(-1px); } .report-preview h3 { color: var(--primary-color); margin-bottom: var(--spacing-lg); } .report-preview h4 { color: var(--gray-700); margin: var(--spacing-lg) 0 var(--spacing-md); } .report-preview ul { list-style: none; padding-left: 0; } .report-preview li { padding: var(--spacing-sm) 0; border-bottom: 1px solid var(--gray-200); } .report-preview li:last-child { border-bottom: none; } `; document.head.appendChild(style); })();