/** * Benachrichtigungssystem für die MYP 3D-Druck Platform * Verwaltet die Anzeige und Interaktion mit Benachrichtigungen */ class NotificationManager { constructor() { this.notificationToggle = document.getElementById('notificationToggle'); this.notificationDropdown = document.getElementById('notificationDropdown'); this.notificationBadge = document.getElementById('notificationBadge'); this.notificationList = document.getElementById('notificationList'); this.markAllReadBtn = document.getElementById('markAllRead'); this.isOpen = false; this.notifications = []; // CSRF-Token aus Meta-Tag holen this.csrfToken = this.getCSRFToken(); this.init(); } /** * Holt das CSRF-Token aus dem Meta-Tag * @returns {string} Das CSRF-Token */ getCSRFToken() { const metaTag = document.querySelector('meta[name="csrf-token"]'); return metaTag ? metaTag.getAttribute('content') : ''; } /** * Erstellt die Standard-Headers für API-Anfragen mit CSRF-Token * @returns {Object} Headers-Objekt */ getAPIHeaders() { const headers = { 'Content-Type': 'application/json', }; if (this.csrfToken) { headers['X-CSRFToken'] = this.csrfToken; } return headers; } init() { if (!this.notificationToggle) return; // Event Listeners this.notificationToggle.addEventListener('click', (e) => { e.stopPropagation(); this.toggleDropdown(); }); if (this.markAllReadBtn) { this.markAllReadBtn.addEventListener('click', () => { this.markAllAsRead(); }); } // Dropdown schließen bei Klick außerhalb document.addEventListener('click', (e) => { if (!this.notificationDropdown.contains(e.target) && !this.notificationToggle.contains(e.target)) { this.closeDropdown(); } }); // Benachrichtigungen laden this.loadNotifications(); // Regelmäßige Updates setInterval(() => { this.loadNotifications(); }, 30000); // Alle 30 Sekunden } toggleDropdown() { if (this.isOpen) { this.closeDropdown(); } else { this.openDropdown(); } } openDropdown() { this.notificationDropdown.classList.remove('hidden'); this.notificationToggle.setAttribute('aria-expanded', 'true'); this.isOpen = true; // Animation this.notificationDropdown.style.opacity = '0'; this.notificationDropdown.style.transform = 'translateY(-10px)'; requestAnimationFrame(() => { this.notificationDropdown.style.transition = 'opacity 0.2s ease, transform 0.2s ease'; this.notificationDropdown.style.opacity = '1'; this.notificationDropdown.style.transform = 'translateY(0)'; }); } closeDropdown() { this.notificationDropdown.style.transition = 'opacity 0.2s ease, transform 0.2s ease'; this.notificationDropdown.style.opacity = '0'; this.notificationDropdown.style.transform = 'translateY(-10px)'; setTimeout(() => { this.notificationDropdown.classList.add('hidden'); this.notificationToggle.setAttribute('aria-expanded', 'false'); this.isOpen = false; }, 200); } async loadNotifications() { try { const response = await fetch('/api/notifications'); if (response.ok) { const data = await response.json(); this.notifications = data.notifications || []; this.updateUI(); } } catch (error) { console.error('Fehler beim Laden der Benachrichtigungen:', error); } } updateUI() { this.updateBadge(); this.updateNotificationList(); } updateBadge() { const unreadCount = this.notifications.filter(n => !n.read).length; if (unreadCount > 0) { this.notificationBadge.textContent = unreadCount > 99 ? '99+' : unreadCount.toString(); this.notificationBadge.classList.remove('hidden'); } else { this.notificationBadge.classList.add('hidden'); } } updateNotificationList() { if (this.notifications.length === 0) { this.notificationList.innerHTML = `
Keine neuen Benachrichtigungen
`; return; } const notificationHTML = this.notifications.map(notification => { const isUnread = !notification.read; const timeAgo = this.formatTimeAgo(new Date(notification.created_at)); return `
${this.getNotificationIcon(notification.type)}

${this.getNotificationTitle(notification.type)}

${isUnread ? '
' : ''}

${this.getNotificationMessage(notification)}

${timeAgo}

`; }).join(''); this.notificationList.innerHTML = notificationHTML; // Event Listeners für Benachrichtigungen this.notificationList.querySelectorAll('.notification-item').forEach(item => { item.addEventListener('click', (e) => { const notificationId = item.dataset.notificationId; this.markAsRead(notificationId); }); }); } getNotificationIcon(type) { const icons = { 'guest_request': `
`, 'job_completed': `
`, 'system': `
` }; return icons[type] || icons['system']; } getNotificationTitle(type) { const titles = { 'guest_request': 'Neue Gastanfrage', 'job_completed': 'Druckauftrag abgeschlossen', 'system': 'System-Benachrichtigung' }; return titles[type] || 'Benachrichtigung'; } getNotificationMessage(notification) { try { const payload = JSON.parse(notification.payload || '{}'); switch (notification.type) { case 'guest_request': return `${payload.guest_name || 'Ein Gast'} hat eine neue Druckanfrage gestellt.`; case 'job_completed': return `Der Druckauftrag "${payload.job_name || 'Unbekannt'}" wurde erfolgreich abgeschlossen.`; default: return payload.message || 'Neue Benachrichtigung erhalten.'; } } catch (error) { return 'Neue Benachrichtigung erhalten.'; } } formatTimeAgo(date) { const now = new Date(); const diffInSeconds = Math.floor((now - date) / 1000); if (diffInSeconds < 60) { return 'Gerade eben'; } else if (diffInSeconds < 3600) { const minutes = Math.floor(diffInSeconds / 60); return `vor ${minutes} Minute${minutes !== 1 ? 'n' : ''}`; } else if (diffInSeconds < 86400) { const hours = Math.floor(diffInSeconds / 3600); return `vor ${hours} Stunde${hours !== 1 ? 'n' : ''}`; } else { const days = Math.floor(diffInSeconds / 86400); return `vor ${days} Tag${days !== 1 ? 'en' : ''}`; } } async markAsRead(notificationId) { try { const response = await fetch(`/api/notifications/${notificationId}/read`, { method: 'POST', headers: this.getAPIHeaders() }); if (response.ok) { // Benachrichtigung als gelesen markieren const notification = this.notifications.find(n => n.id == notificationId); if (notification) { notification.read = true; this.updateUI(); } } else { console.error('Fehler beim Markieren als gelesen:', response.status, response.statusText); } } catch (error) { console.error('Fehler beim Markieren als gelesen:', error); } } async markAllAsRead() { try { const response = await fetch('/api/notifications/mark-all-read', { method: 'POST', headers: this.getAPIHeaders() }); if (response.ok) { // Alle Benachrichtigungen als gelesen markieren this.notifications.forEach(notification => { notification.read = true; }); this.updateUI(); } else { console.error('Fehler beim Markieren aller als gelesen:', response.status, response.statusText); } } catch (error) { console.error('Fehler beim Markieren aller als gelesen:', error); } } } // Initialisierung nach DOM-Load document.addEventListener('DOMContentLoaded', () => { new NotificationManager(); });