/** * TASKMATE - Notification Manager * ================================ * Frontend-Verwaltung für das Benachrichtigungssystem */ import api from './api.js'; import store from './store.js'; class NotificationManager { constructor() { this.notifications = []; this.unreadCount = 0; this.isDropdownOpen = false; this.initialized = false; } /** * Initialisierung */ async init() { if (this.initialized) return; this.bindElements(); this.bindEvents(); await this.loadNotifications(); this.initialized = true; } /** * DOM-Elemente binden */ bindElements() { this.bellBtn = document.getElementById('notification-btn'); this.badge = document.getElementById('notification-badge'); this.dropdown = document.getElementById('notification-dropdown'); this.list = document.getElementById('notification-list'); this.emptyState = document.getElementById('notification-empty'); this.markAllBtn = document.getElementById('btn-mark-all-read'); this.bellContainer = document.getElementById('notification-bell'); } /** * Event-Listener binden */ bindEvents() { // Toggle Dropdown this.bellBtn?.addEventListener('click', (e) => { e.stopPropagation(); this.toggleDropdown(); }); // Klick außerhalb schließt Dropdown document.addEventListener('click', (e) => { if (!this.bellContainer?.contains(e.target)) { this.closeDropdown(); } }); // Alle als gelesen markieren this.markAllBtn?.addEventListener('click', (e) => { e.stopPropagation(); this.markAllAsRead(); }); // Klicks in der Liste this.list?.addEventListener('click', (e) => { const deleteBtn = e.target.closest('.notification-delete'); if (deleteBtn) { e.stopPropagation(); const id = parseInt(deleteBtn.dataset.delete); this.deleteNotification(id); return; } const item = e.target.closest('.notification-item'); if (item) { this.handleItemClick(item); } }); // WebSocket Events window.addEventListener('notification:new', (e) => { this.addNotification(e.detail.notification); }); window.addEventListener('notification:count', (e) => { this.updateBadge(e.detail.count); }); window.addEventListener('notification:deleted', (e) => { this.removeNotification(e.detail.notificationId); }); } /** * Benachrichtigungen vom Server laden */ async loadNotifications() { try { const data = await api.getNotifications(); this.notifications = data.notifications || []; this.unreadCount = data.unreadCount || 0; this.render(); } catch (error) { console.error('Fehler beim Laden der Benachrichtigungen:', error); } } /** * Alles rendern */ render() { this.updateBadge(this.unreadCount); this.renderList(); } /** * Badge aktualisieren */ updateBadge(count) { this.unreadCount = count; if (count > 0) { this.badge.textContent = count > 99 ? '99+' : count; this.badge.classList.remove('hidden'); this.bellContainer?.classList.add('has-notifications'); } else { this.badge.classList.add('hidden'); this.bellContainer?.classList.remove('has-notifications'); } } /** * Liste rendern */ renderList() { if (!this.notifications || this.notifications.length === 0) { this.list?.classList.add('hidden'); this.emptyState?.classList.remove('hidden'); return; } this.list?.classList.remove('hidden'); this.emptyState?.classList.add('hidden'); this.list.innerHTML = this.notifications.map(n => this.renderItem(n)).join(''); } /** * Einzelnes Item rendern */ renderItem(notification) { const timeAgo = this.formatTimeAgo(notification.createdAt); const iconClass = this.getIconClass(notification.type); const icon = this.getIcon(notification.type); return `
${this.escapeHtml(message)}
` : ''} `; toast.style.cssText = ` position: fixed; bottom: 20px; right: 20px; padding: 16px 20px; background: var(--bg-card, #fff); border: 1px solid var(--border-default, #e2e8f0); border-radius: 8px; box-shadow: 0 10px 25px rgba(0,0,0,0.15); z-index: 9999; max-width: 320px; animation: slideIn 0.3s ease; `; // Animation const style = document.createElement('style'); style.textContent = ` @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } `; document.head.appendChild(style); document.body.appendChild(toast); // Nach 4 Sekunden entfernen setTimeout(() => { toast.style.animation = 'slideIn 0.3s ease reverse'; setTimeout(() => { toast.remove(); style.remove(); }, 300); }, 4000); } /** * Item-Klick behandeln */ handleItemClick(item) { const taskId = item.dataset.taskId; const proposalId = item.dataset.proposalId; if (taskId) { // Zur Aufgabe navigieren this.closeDropdown(); window.dispatchEvent(new CustomEvent('notification:open-task', { detail: { taskId: parseInt(taskId) } })); } else if (proposalId) { // Zum Genehmigung-Tab wechseln this.closeDropdown(); window.dispatchEvent(new CustomEvent('notification:open-proposal', { detail: { proposalId: parseInt(proposalId) } })); } } /** * Reset */ reset() { this.notifications = []; this.unreadCount = 0; this.isDropdownOpen = false; this.closeDropdown(); this.render(); } } // Singleton-Instanz const notificationManager = new NotificationManager(); export { notificationManager }; export default notificationManager;