/** * MYP Admin Dashboard * Core JavaScript für das Admin-Dashboard */ document.addEventListener('DOMContentLoaded', function() { // Initialize navigation initNavigation(); // Initialize modal events initModals(); // Load initial data loadDashboardData(); }); /** * Navigation Initialization */ function initNavigation() { // Desktop navigation const desktopNavItems = document.querySelectorAll('.admin-nav-item'); desktopNavItems.forEach(item => { item.addEventListener('click', function(e) { e.preventDefault(); const section = this.getAttribute('data-section'); activateSection(section); updateActiveNavItem(this, desktopNavItems); }); }); // Mobile navigation const mobileNavItems = document.querySelectorAll('.mobile-nav-item'); mobileNavItems.forEach(item => { item.addEventListener('click', function(e) { e.preventDefault(); const section = this.getAttribute('data-section'); activateSection(section); updateActiveNavItem(this, mobileNavItems); closeMobileNav(); }); }); // Mobile menu toggle const mobileMenuButton = document.getElementById('mobile-menu-button'); const closeMobileNavButton = document.getElementById('close-mobile-nav'); if (mobileMenuButton) { mobileMenuButton.addEventListener('click', openMobileNav); } if (closeMobileNavButton) { closeMobileNavButton.addEventListener('click', closeMobileNav); } // Setup hash navigation window.addEventListener('hashchange', handleHashChange); if (window.location.hash) { handleHashChange(); } } function activateSection(section) { // Hide all sections document.querySelectorAll('.admin-section').forEach(el => { el.classList.remove('active'); el.classList.add('hidden'); }); // Show selected section const targetSection = document.getElementById(`${section}-section`); if (targetSection) { targetSection.classList.remove('hidden'); targetSection.classList.add('active'); // Load section data if needed switch(section) { case 'dashboard': loadDashboardData(); break; case 'users': loadUsers(); break; case 'printers': loadPrinters(); break; case 'scheduler': loadSchedulerStatus(); break; case 'logs': loadLogs(); break; } // Update URL hash window.location.hash = section; } } function updateActiveNavItem(activeItem, allItems) { // Remove active class from all items allItems.forEach(item => { item.classList.remove('active'); }); // Add active class to selected item activeItem.classList.add('active'); } function handleHashChange() { const hash = window.location.hash.substring(1); if (hash) { const navItem = document.querySelector(`.admin-nav-item[data-section="${hash}"]`); if (navItem) { activateSection(hash); updateActiveNavItem(navItem, document.querySelectorAll('.admin-nav-item')); // Also update mobile nav const mobileNavItem = document.querySelector(`.mobile-nav-item[data-section="${hash}"]`); if (mobileNavItem) { updateActiveNavItem(mobileNavItem, document.querySelectorAll('.mobile-nav-item')); } } } } function openMobileNav() { const mobileNav = document.getElementById('mobile-nav'); if (mobileNav) { mobileNav.classList.remove('hidden'); } } function closeMobileNav() { const mobileNav = document.getElementById('mobile-nav'); if (mobileNav) { mobileNav.classList.add('hidden'); } } /** * Modal Initialization */ function initModals() { // Delete modal const deleteModal = document.getElementById('delete-modal'); const closeDeleteModalBtn = document.getElementById('close-delete-modal'); const cancelDeleteBtn = document.getElementById('cancel-delete-btn'); if (closeDeleteModalBtn) { closeDeleteModalBtn.addEventListener('click', closeDeleteModal); } if (cancelDeleteBtn) { cancelDeleteBtn.addEventListener('click', closeDeleteModal); } // Toast notification const closeToastBtn = document.getElementById('close-toast'); if (closeToastBtn) { closeToastBtn.addEventListener('click', closeToast); } // Global refresh button const refreshAllBtn = document.getElementById('refresh-all-btn'); if (refreshAllBtn) { refreshAllBtn.addEventListener('click', refreshAllData); } } function showDeleteModal(message, onConfirm) { const modal = document.getElementById('delete-modal'); const messageEl = document.getElementById('delete-message'); const confirmBtn = document.getElementById('confirm-delete-btn'); if (modal && messageEl && confirmBtn) { messageEl.textContent = message; modal.classList.add('modal-show'); // Setup confirm button action confirmBtn.onclick = function() { closeDeleteModal(); if (typeof onConfirm === 'function') { onConfirm(); } }; } } function closeDeleteModal() { const modal = document.getElementById('delete-modal'); if (modal) { modal.classList.remove('modal-show'); } } function showToast(message, type = 'info') { const toast = document.getElementById('toast-notification'); const messageEl = document.getElementById('toast-message'); const iconEl = document.getElementById('toast-icon'); if (toast && messageEl && iconEl) { messageEl.textContent = message; // Set icon based on type const iconSvg = getToastIcon(type); iconEl.innerHTML = iconSvg; // Show toast toast.classList.add('toast-show'); // Auto-hide after 5 seconds setTimeout(closeToast, 5000); } } function closeToast() { const toast = document.getElementById('toast-notification'); if (toast) { toast.classList.remove('toast-show'); } } function getToastIcon(type) { switch(type) { case 'success': return ''; case 'error': return ''; case 'warning': return ''; case 'info': default: return ''; } } /** * Dashboard Data Loading */ function loadDashboardData() { // Load dashboard stats loadStats(); // Load recent activity loadRecentActivity(); // Load system status loadSystemStatus(); // Setup refresh buttons const refreshActivityBtn = document.getElementById('refresh-activity-btn'); if (refreshActivityBtn) { refreshActivityBtn.addEventListener('click', loadRecentActivity); } const refreshSystemBtn = document.getElementById('refresh-system-btn'); if (refreshSystemBtn) { refreshSystemBtn.addEventListener('click', loadSystemStatus); } } async function loadStats() { try { const response = await fetch('/api/stats'); const data = await response.json(); // Update dashboard counters mit robusten ID-Checks const userCountEl = document.getElementById('live-users-count'); if (userCountEl) { userCountEl.textContent = data.total_users || 0; } const printerCountEl = document.getElementById('live-printers-count'); if (printerCountEl) { printerCountEl.textContent = data.total_printers || 0; } const activeJobsEl = document.getElementById('live-jobs-active'); if (activeJobsEl) { activeJobsEl.textContent = data.active_jobs || 0; } // Update scheduler status updateSchedulerStatusIndicator(data.scheduler_status || false); // Update additional stats if elements exist const onlinePrintersEl = document.getElementById('live-printers-online'); if (onlinePrintersEl) { onlinePrintersEl.textContent = `${data.online_printers || 0} online`; } const queuedJobsEl = document.getElementById('live-jobs-queued'); if (queuedJobsEl) { queuedJobsEl.textContent = `${data.queued_jobs || 0} in Warteschlange`; } const successRateEl = document.getElementById('live-success-rate'); if (successRateEl) { successRateEl.textContent = `${data.success_rate || 0}%`; } // Update progress bars if they exist updateProgressBar('users-progress', data.total_users, 10); updateProgressBar('printers-progress', data.online_printers, data.total_printers); updateProgressBar('jobs-progress', data.active_jobs, 10); updateProgressBar('success-progress', data.success_rate, 100); console.log('✅ Dashboard-Statistiken erfolgreich aktualisiert:', data); } catch (error) { console.error('Error loading stats:', error); showToast('Fehler beim Laden der Statistiken', 'error'); } } /** * Helper-Funktion zum sicheren Aktualisieren von Progress-Bars */ function updateProgressBar(progressId, currentValue, maxValue) { const progressEl = document.getElementById(progressId); if (progressEl && currentValue !== undefined && maxValue > 0) { const percentage = Math.min(100, Math.max(0, (currentValue / maxValue) * 100)); progressEl.style.width = `${percentage}%`; } } function updateSchedulerStatusIndicator(isRunning) { const statusText = document.getElementById('scheduler-status'); const indicator = document.getElementById('scheduler-indicator'); if (statusText && indicator) { if (isRunning) { statusText.textContent = 'Aktiv'; statusText.classList.add('text-green-600', 'dark:text-green-400'); statusText.classList.remove('text-red-600', 'dark:text-red-400'); indicator.classList.add('bg-green-500'); indicator.classList.remove('bg-red-500', 'bg-gray-300'); } else { statusText.textContent = 'Inaktiv'; statusText.classList.add('text-red-600', 'dark:text-red-400'); statusText.classList.remove('text-green-600', 'dark:text-green-400'); indicator.classList.add('bg-red-500'); indicator.classList.remove('bg-green-500', 'bg-gray-300'); } } } async function loadRecentActivity() { const container = document.getElementById('recent-activity-container'); if (!container) return; // Show loading state container.innerHTML = `
`; try { const response = await fetch('/api/activity/recent'); const data = await response.json(); if (data.activities && data.activities.length > 0) { const activities = data.activities; const html = activities.map(activity => `

${activity.description}

${formatDateTime(activity.timestamp)}

`).join(''); container.innerHTML = html; } else { container.innerHTML = `

Keine Aktivitäten gefunden

`; } } catch (error) { console.error('Error loading activities:', error); container.innerHTML = `

Fehler beim Laden der Aktivitäten

`; } } async function loadSystemStatus() { try { const response = await fetch('/api/stats'); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); // Prüfen ob data gültig ist if (!data || typeof data !== 'object') { throw new Error('Ungültige Antwort vom Server erhalten'); } // Update system stats mit Fallback-Werten const totalPrintTimeEl = document.getElementById('total-print-time'); if (totalPrintTimeEl) { totalPrintTimeEl.textContent = formatPrintTime(data.total_print_time_hours); } const completedJobsEl = document.getElementById('completed-jobs-count'); if (completedJobsEl) { completedJobsEl.textContent = data.total_jobs_completed || 0; } const totalMaterialEl = document.getElementById('total-material-used'); if (totalMaterialEl) { totalMaterialEl.textContent = formatMaterialUsed(data.total_material_used); } const lastUpdatedEl = document.getElementById('last-updated-time'); if (lastUpdatedEl) { lastUpdatedEl.textContent = data.last_updated ? formatDateTime(data.last_updated) : '-'; } console.log('✅ Systemstatus erfolgreich geladen:', data); } catch (error) { console.error('Error loading system status:', error); const errorMessage = error.message || 'Unbekannter Systemfehler'; showToast(`Fehler beim Laden des Systemstatus: ${errorMessage}`, 'error'); // Fallback-Werte anzeigen const elements = [ 'total-print-time', 'completed-jobs-count', 'total-material-used', 'last-updated-time' ]; elements.forEach(id => { const el = document.getElementById(id); if (el) { el.textContent = 'Fehler beim Laden'; el.classList.add('text-red-500'); } }); } } function formatPrintTime(hours) { if (!hours) return '-'; return `${hours} Stunden`; } function formatMaterialUsed(grams) { if (!grams) return '-'; return `${grams} g`; } function formatDateTime(dateString) { if (!dateString) return '-'; const date = new Date(dateString); return date.toLocaleString('de-DE', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } /** * Global refresh function */ function refreshAllData() { // Get active section const activeSection = document.querySelector('.admin-section.active'); if (activeSection) { const sectionId = activeSection.id; const section = sectionId.replace('-section', ''); // Reload data based on active section switch(section) { case 'dashboard': loadDashboardData(); break; case 'users': loadUsers(); break; case 'printers': loadPrinters(); break; case 'scheduler': loadSchedulerStatus(); break; case 'logs': loadLogs(); break; } } showToast('Daten aktualisiert', 'success'); } /** * Benutzer laden und anzeigen */ function loadUsers() { const usersContainer = document.getElementById('users-container'); if (!usersContainer) return; // Lade-Animation anzeigen usersContainer.innerHTML = `
`; // Benutzer vom Server laden fetch('/api/users') .then(response => { if (!response.ok) throw new Error('Fehler beim Laden der Benutzer'); return response.json(); }) .then(data => { renderUsers(data.users); updateUserStatistics(data.users); }) .catch(error => { console.error('Fehler beim Laden der Benutzer:', error); usersContainer.innerHTML = `
Fehler beim Laden der Benutzer

${error.message}

`; }); } /** * Drucker laden und anzeigen */ function loadPrinters() { const printersContainer = document.getElementById('printers-container'); if (!printersContainer) return; // Lade-Animation anzeigen printersContainer.innerHTML = `
`; // Drucker vom Server laden fetch('/api/printers') .then(response => { if (!response.ok) throw new Error('Fehler beim Laden der Drucker'); return response.json(); }) .then(data => { renderPrinters(data.printers); updatePrinterStatistics(data.printers); }) .catch(error => { console.error('Fehler beim Laden der Drucker:', error); printersContainer.innerHTML = `
Fehler beim Laden der Drucker

${error.message}

`; }); } /** * Scheduler-Status laden und anzeigen */ function loadSchedulerStatus() { const schedulerContainer = document.getElementById('scheduler-container'); if (!schedulerContainer) return; // Lade-Animation anzeigen schedulerContainer.innerHTML = `
`; // Scheduler-Status vom Server laden fetch('/api/scheduler/status') .then(response => { if (!response.ok) throw new Error('Fehler beim Laden des Scheduler-Status'); return response.json(); }) .then(data => { renderSchedulerStatus(data); updateSchedulerControls(data.active); }) .catch(error => { console.error('Fehler beim Laden des Scheduler-Status:', error); schedulerContainer.innerHTML = `
Fehler beim Laden des Scheduler-Status

${error.message}

`; }); } /** * Logs laden und anzeigen */ function loadLogs() { const logsContainer = document.getElementById('logs-container'); if (!logsContainer) return; // Lade-Animation anzeigen logsContainer.innerHTML = `
`; // Logs vom Server laden fetch('/api/logs') .then(response => { if (!response.ok) throw new Error('Fehler beim Laden der Logs'); return response.json(); }) .then(data => { window.logsData = data.logs; window.filteredLogs = [...data.logs]; renderLogs(); updateLogStatistics(); scrollLogsToBottom(); }) .catch(error => { console.error('Fehler beim Laden der Logs:', error); logsContainer.innerHTML = `
Fehler beim Laden der Logs

${error.message}

`; }); }