// Jamf School Reporter - Main Application JavaScript document.addEventListener('DOMContentLoaded', function() { // Initialize tooltips var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); // Initialize DataTables if available if (typeof $.fn.DataTable !== 'undefined' && document.getElementById('reportTable')) { $('#reportTable').DataTable({ responsive: true, pageLength: 25, order: [[2, 'desc']], // Sort by usage hours by default columnDefs: [ { orderable: false, targets: -1 } // Disable sorting on actions column ], language: { search: "Search apps:", lengthMenu: "Show _MENU_ apps per page", info: "Showing _START_ to _END_ of _TOTAL_ apps", infoEmpty: "No apps found", emptyTable: "No app data available" } }); } // Auto-refresh functionality initAutoRefresh(); // Form validation initFormValidation(); // Export functionality initExportHandlers(); // Theme switcher initThemeSwitcher(); }); // Auto-refresh functionality function initAutoRefresh() { const refreshButton = document.createElement('button'); refreshButton.className = 'btn btn-outline-secondary btn-sm position-fixed'; refreshButton.style.cssText = 'bottom: 20px; right: 20px; z-index: 1000;'; refreshButton.innerHTML = ''; refreshButton.setAttribute('data-bs-toggle', 'tooltip'); refreshButton.setAttribute('title', 'Refresh Data'); refreshButton.addEventListener('click', function() { this.innerHTML = ''; this.disabled = true; // Clear cache and reload clearCache(); setTimeout(() => { location.reload(); }, 1000); }); document.body.appendChild(refreshButton); } // Form validation function initFormValidation() { const forms = document.querySelectorAll('.needs-validation'); Array.prototype.slice.call(forms).forEach(function(form) { form.addEventListener('submit', function(event) { if (!form.checkValidity()) { event.preventDefault(); event.stopPropagation(); } form.classList.add('was-validated'); }, false); }); } // Export functionality function initExportHandlers() { // Add loading states to export buttons document.querySelectorAll('a[href*="export="]').forEach(function(link) { link.addEventListener('click', function(e) { const originalText = this.innerHTML; this.innerHTML = 'Exporting...'; this.classList.add('disabled'); setTimeout(() => { this.innerHTML = originalText; this.classList.remove('disabled'); }, 3000); }); }); } // Theme switcher function initThemeSwitcher() { const currentTheme = localStorage.getItem('theme') || 'light'; document.documentElement.setAttribute('data-theme', currentTheme); // Create theme toggle button const themeToggle = document.createElement('button'); themeToggle.className = 'btn btn-outline-secondary btn-sm ms-2'; themeToggle.innerHTML = currentTheme === 'dark' ? '' : ''; themeToggle.setAttribute('data-bs-toggle', 'tooltip'); themeToggle.setAttribute('title', 'Toggle Dark Mode'); themeToggle.addEventListener('click', function() { const currentTheme = document.documentElement.getAttribute('data-theme'); const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', newTheme); localStorage.setItem('theme', newTheme); this.innerHTML = newTheme === 'dark' ? '' : ''; }); // Add to navbar if exists const navbar = document.querySelector('.navbar-nav'); if (navbar) { const themeContainer = document.createElement('li'); themeContainer.className = 'nav-item'; themeContainer.appendChild(themeToggle); navbar.appendChild(themeContainer); } } // Utility functions function clearCache() { // Send request to clear server-side cache fetch('api/clear_cache.php', { method: 'POST', headers: { 'Content-Type': 'application/json', } }).catch(console.error); // Clear any client-side cache if ('caches' in window) { caches.keys().then(function(names) { names.forEach(function(name) { caches.delete(name); }); }); } } function formatBytes(bytes, decimals = 2) { if (bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } function formatDuration(hours) { if (hours < 1) { return Math.round(hours * 60) + ' min'; } else if (hours < 24) { return Math.round(hours * 10) / 10 + ' hrs'; } else { const days = Math.floor(hours / 24); const remainingHours = Math.round((hours % 24) * 10) / 10; return days + 'd ' + remainingHours + 'h'; } } function showNotification(message, type = 'info', duration = 5000) { const notification = document.createElement('div'); notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`; notification.style.cssText = 'top: 20px; right: 20px; z-index: 1050; min-width: 300px;'; notification.innerHTML = ` ${message} `; document.body.appendChild(notification); // Auto-remove after duration setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, duration); } // Error handling window.addEventListener('error', function(e) { console.error('Application Error:', e.error); showNotification('An unexpected error occurred. Please refresh the page.', 'danger'); }); // Chart utilities function createUsageChart(canvasId, data, options = {}) { const ctx = document.getElementById(canvasId); if (!ctx) return null; const defaultOptions = { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top', }, tooltip: { callbacks: { label: function(context) { return context.dataset.label + ': ' + formatDuration(context.parsed.y); } } } }, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return formatDuration(value); } } } } }; const mergedOptions = { ...defaultOptions, ...options }; return new Chart(ctx, { type: 'bar', data: data, options: mergedOptions }); } // Performance monitoring if ('performance' in window) { window.addEventListener('load', function() { setTimeout(function() { const perfData = performance.timing; const loadTime = perfData.loadEventEnd - perfData.navigationStart; if (loadTime > 5000) { console.warn('Page load time is slow:', loadTime + 'ms'); } }, 0); }); } // Service worker registration (for future PWA support) if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/sw.js') .then(function(registration) { console.log('ServiceWorker registration successful'); }) .catch(function(err) { console.log('ServiceWorker registration failed'); }); }); } Jamf School App Usage Reporter