/** * Admin Core Module * Contains core utilities, navigation, viewport management, and initialization coordination */ // Global admin state let adminAppState = { currentSection: 'dashboard', dashboardLoading: false }; // Utility function to create a local date from YYYY-MM-DD string // This prevents timezone issues when displaying dates function createLocalDate(dateString) { if (!dateString) return null; const parts = dateString.split('-'); if (parts.length !== 3) return new Date(dateString); // fallback to original behavior // Create date using local timezone (year, month-1, day) return new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2])); } // A function to set viewport dimensions for admin page function setAdminViewportDimensions() { const doc = document.documentElement; // Set height and width doc.style.setProperty('--app-height', `${window.innerHeight}px`); doc.style.setProperty('--app-width', `${window.innerWidth}px`); // Handle safe area insets for devices with notches or home indicators if (CSS.supports('padding: env(safe-area-inset-top)')) { doc.style.setProperty('--safe-area-top', 'env(safe-area-inset-top)'); doc.style.setProperty('--safe-area-bottom', 'env(safe-area-inset-bottom)'); doc.style.setProperty('--safe-area-left', 'env(safe-area-inset-left)'); doc.style.setProperty('--safe-area-right', 'env(safe-area-inset-right)'); } else { doc.style.setProperty('--safe-area-top', '0px'); doc.style.setProperty('--safe-area-bottom', '0px'); doc.style.setProperty('--safe-area-left', '0px'); doc.style.setProperty('--safe-area-right', '0px'); } } // Add mobile menu functionality function setupMobileMenu() { console.log('🔧 Setting up mobile menu...'); const menuToggle = document.getElementById('mobile-menu-toggle'); const sidebar = document.getElementById('admin-sidebar'); const closeSidebar = document.getElementById('close-sidebar'); const adminNavLinks = document.querySelectorAll('.admin-nav a'); console.log('📱 Mobile menu elements found:', { menuToggle: !!menuToggle, sidebar: !!sidebar, closeSidebar: !!closeSidebar, adminNavLinks: adminNavLinks.length }); if (menuToggle && sidebar) { console.log('✅ Setting up mobile menu event listeners...'); // Remove any existing listeners to prevent duplicates const newMenuToggle = menuToggle.cloneNode(true); menuToggle.parentNode.replaceChild(newMenuToggle, menuToggle); // Toggle menu function const toggleMobileMenu = (e) => { console.log('🔄 Mobile menu toggle triggered!', e.type); e.preventDefault(); e.stopPropagation(); const sidebar = document.getElementById('admin-sidebar'); const menuToggle = document.getElementById('mobile-menu-toggle'); if (!sidebar || !menuToggle) { console.error('❌ Sidebar or menu toggle not found during toggle'); return; } const isActive = sidebar.classList.contains('active'); console.log('📱 Current menu state:', isActive ? 'open' : 'closed'); if (isActive) { sidebar.classList.remove('active'); menuToggle.classList.remove('active'); document.body.classList.remove('sidebar-open'); console.log('✅ Menu closed'); } else { sidebar.classList.add('active'); menuToggle.classList.add('active'); document.body.classList.add('sidebar-open'); console.log('✅ Menu opened'); } }; // Use pointer events for better mobile support const toggleButton = document.getElementById('mobile-menu-toggle'); // Add click event for all devices toggleButton.addEventListener('click', toggleMobileMenu, { passive: false }); // Add pointer events for better mobile support toggleButton.addEventListener('pointerdown', (e) => { // Visual feedback e.currentTarget.style.backgroundColor = 'rgba(255, 255, 255, 0.2)'; }, { passive: true }); toggleButton.addEventListener('pointerup', (e) => { // Remove visual feedback e.currentTarget.style.backgroundColor = ''; }, { passive: true }); // Close sidebar button if (closeSidebar) { closeSidebar.addEventListener('click', () => { const sidebar = document.getElementById('admin-sidebar'); const menuToggle = document.getElementById('mobile-menu-toggle'); if (sidebar && menuToggle) { sidebar.classList.remove('active'); menuToggle.classList.remove('active'); document.body.classList.remove('sidebar-open'); console.log('✅ Sidebar closed via close button'); } }); } // Close sidebar when clicking outside document.addEventListener('click', (e) => { const sidebar = document.getElementById('admin-sidebar'); const menuToggle = document.getElementById('mobile-menu-toggle'); if (sidebar && menuToggle && sidebar.classList.contains('active') && !sidebar.contains(e.target) && !menuToggle.contains(e.target)) { sidebar.classList.remove('active'); menuToggle.classList.remove('active'); document.body.classList.remove('sidebar-open'); console.log('✅ Sidebar closed by clicking outside'); } }); // Close sidebar when navigation link is clicked on mobile const navLinks = document.querySelectorAll('.admin-nav a'); navLinks.forEach(link => { link.addEventListener('click', () => { if (window.innerWidth <= 768) { const sidebar = document.getElementById('admin-sidebar'); const menuToggle = document.getElementById('mobile-menu-toggle'); if (sidebar && menuToggle) { sidebar.classList.remove('active'); menuToggle.classList.remove('active'); document.body.classList.remove('sidebar-open'); console.log('✅ Sidebar closed after navigation'); } } }); }); console.log('✅ Mobile menu setup complete!'); } else { console.error('❌ Mobile menu elements not found:', { menuToggle: !!menuToggle, sidebar: !!sidebar }); } } // Setup navigation between admin sections function setupNavigation() { const navLinks = document.querySelectorAll('.admin-nav a'); const sections = document.querySelectorAll('.admin-section'); navLinks.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const targetId = link.getAttribute('href').substring(1); // Update active nav navLinks.forEach(l => l.classList.remove('active')); link.classList.add('active'); // Show target section sections.forEach(section => { section.style.display = section.id === targetId ? 'block' : 'none'; }); // Update URL hash and app state window.location.hash = targetId; adminAppState.currentSection = targetId; // Load section-specific data loadSectionData(targetId); // Close mobile menu if open const sidebar = document.getElementById('admin-sidebar'); if (sidebar && sidebar.classList.contains('open')) { sidebar.classList.remove('open'); } }); }); // Set initial active state based on current hash or default const currentHash = window.location.hash || '#dashboard'; const activeLink = document.querySelector(`.admin-nav a[href="${currentHash}"]`); if (activeLink) { activeLink.classList.add('active'); } // Check for initial hash routing const hash = window.location.hash; if (hash) { const sectionId = hash.substring(1); adminAppState.currentSection = sectionId; if (sectionId === 'shifts') { showSection('shifts'); } } } // Helper function to show a specific section function showSection(sectionId) { const sections = document.querySelectorAll('.admin-section'); const navLinks = document.querySelectorAll('.admin-nav a'); // Hide all sections sections.forEach(section => { section.style.display = section.id === sectionId ? 'block' : 'none'; }); // Update active nav navLinks.forEach(link => { const linkTarget = link.getAttribute('href').substring(1); link.classList.toggle('active', linkTarget === sectionId); }); // Update app state adminAppState.currentSection = sectionId; // Load section-specific data loadSectionData(sectionId); } // Load section-specific data based on current section function loadSectionData(sectionId) { switch(sectionId) { case 'walk-sheet': if (typeof checkAndLoadWalkSheetConfig === 'function') { checkAndLoadWalkSheetConfig(); } break; case 'dashboard': // Always use our guarded dashboard loading function loadDashboardDataFromDashboardModule(); break; case 'shifts': if (typeof loadAdminShifts === 'function') { console.log('Loading shifts for admin panel...'); loadAdminShifts(); } break; case 'users': if (typeof loadUsers === 'function') { loadUsers(); } break; case 'start-location': // Initialize or refresh map when start location section becomes visible setTimeout(() => { if (window.adminMap && window.adminMap.initializeAdminMap) { console.log('Ensuring admin map is initialized for start-location section...'); // This will either initialize the map (if not done) or invalidate size (if already initialized) window.adminMap.initializeAdminMap(); // Also load the current start location data if (window.adminMap.loadCurrentStartLocation) { window.adminMap.loadCurrentStartLocation(); } } }, 100); break; case 'convert-data': // Initialize data convert event listeners when section is shown setTimeout(() => { if (typeof window.setupDataConvertEventListeners === 'function') { console.log('Setting up data convert event listeners...'); window.setupDataConvertEventListeners(); } else { console.error('setupDataConvertEventListeners not found'); } }, 100); break; case 'cuts': // Initialize admin cuts manager when section is shown setTimeout(() => { if (typeof window.adminCutsManager === 'object' && window.adminCutsManager.initialize) { if (!window.adminCutsManager.isInitialized) { console.log('Initializing admin cuts manager from showSection...'); window.adminCutsManager.initialize().catch(error => { console.error('Failed to initialize cuts manager:', error); }); } else { console.log('Admin cuts manager already initialized, refreshing map...'); // Refresh map size when section becomes visible if (window.adminCutsManager.refreshMapSize) { window.adminCutsManager.refreshMapSize(); } } } else { console.error('adminCutsManager not found in showSection'); } }, 100); break; } } // Show status message function showStatus(message, type = 'info') { let container = document.getElementById('status-container'); // Create container if it doesn't exist if (!container) { container = document.createElement('div'); container.id = 'status-container'; container.className = 'status-container'; // Ensure proper z-index even if CSS hasn't loaded container.style.zIndex = '12300'; document.body.appendChild(container); } const messageDiv = document.createElement('div'); messageDiv.className = `status-message ${type}`; messageDiv.textContent = message; // Add click to dismiss functionality messageDiv.addEventListener('click', () => { messageDiv.remove(); }); // Add a small close button for better UX const closeBtn = document.createElement('span'); closeBtn.innerHTML = ' ×'; closeBtn.style.float = 'right'; closeBtn.style.fontWeight = 'bold'; closeBtn.style.marginLeft = '10px'; closeBtn.style.cursor = 'pointer'; closeBtn.setAttribute('title', 'Click to dismiss'); messageDiv.appendChild(closeBtn); container.appendChild(messageDiv); // Auto-remove after 5 seconds setTimeout(() => { if (messageDiv.parentNode) { messageDiv.remove(); } }, 5000); } // Escape HTML function escapeHtml(text) { if (text === null || text === undefined) { return ''; } const div = document.createElement('div'); div.textContent = String(text); return div.innerHTML; } // Debounce function for input events function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Fallback function to load dashboard data directly from dashboard.js module async function loadDashboardDataFromDashboardModule() { // Prevent multiple simultaneous loads if (adminAppState.dashboardLoading) { console.log('Dashboard already loading, skipping duplicate request'); return; } adminAppState.dashboardLoading = true; console.log('Loading dashboard data from dashboard module'); try { // Show loading state const cards = document.querySelectorAll('.card-value'); cards.forEach(card => { card.textContent = '...'; card.style.opacity = '0.6'; }); const response = await fetch('/api/admin/dashboard/stats'); const result = await response.json(); if (result.success) { // Update summary cards const data = result.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(); // Clear loading state cards.forEach(card => { card.style.opacity = '1'; }); // Create charts if functions are available globally if (typeof createSupportLevelChart === 'function') { createSupportLevelChart(data.supportLevels); } if (typeof createSignSizesChart === 'function') { createSignSizesChart(data.signSizes); } if (typeof createEntriesChart === 'function') { createEntriesChart(data.dailyEntries); } console.log('Dashboard data loaded successfully'); } else { showStatus('Failed to load dashboard data', 'error'); } } catch (error) { console.error('Dashboard loading error:', error); showStatus('Error loading dashboard', 'error'); // Clear loading state const cards = document.querySelectorAll('.card-value'); cards.forEach(card => { card.style.opacity = '1'; card.textContent = 'Error'; }); } finally { adminAppState.dashboardLoading = false; } } // Initialize the admin core when DOM is loaded function initializeAdminCore() { console.log('🚀 Initializing Admin Core...'); // Set initial viewport dimensions and listen for resize events setAdminViewportDimensions(); window.addEventListener('resize', setAdminViewportDimensions); window.addEventListener('orientationchange', () => { setTimeout(setAdminViewportDimensions, 100); }); // Setup navigation first setupNavigation(); // Setup mobile menu with a small delay to ensure DOM is ready setTimeout(() => { setupMobileMenu(); }, 100); // Check if URL has a hash to show specific section const hash = window.location.hash; if (hash === '#walk-sheet') { console.log('Direct navigation to walk-sheet section'); showSection('walk-sheet'); } else if (hash) { const sectionId = hash.substring(1); showSection(sectionId); } console.log('✅ Admin Core initialized'); } // Make sure we wait for DOM to be fully loaded if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeAdminCore); } else { // DOM is already loaded initializeAdminCore(); } // Export functions for use by other modules window.adminCore = { showSection, showStatus, escapeHtml, debounce, createLocalDate, initializeAdminCore, loadDashboardDataFromDashboardModule, adminAppState };