/** * MYP Platform UI Components * JavaScript-Utilities für erweiterte UI-Funktionalität * Version: 3.0.0 */ (function() { 'use strict'; // Namespace für MYP UI Components window.MYP = window.MYP || {}; window.MYP.UI = window.MYP.UI || {}; /** * Dark Mode Handler */ class DarkModeManager { constructor() { // DOM-Elemente this.darkModeToggle = document.getElementById('darkModeToggle'); this.sunIcon = this.darkModeToggle ? this.darkModeToggle.querySelector('.sun-icon') : null; this.moonIcon = this.darkModeToggle ? this.darkModeToggle.querySelector('.moon-icon') : null; this.html = document.documentElement; // Local Storage Key this.STORAGE_KEY = 'myp-dark-mode'; this.init(); } /** * Aktuellen Dark Mode Status aus Local Storage oder Systemeinstellung abrufen * @returns {boolean} True wenn Dark Mode aktiviert ist */ isDarkMode() { const savedMode = localStorage.getItem(this.STORAGE_KEY); if (savedMode !== null) { return savedMode === 'true'; } return window.matchMedia('(prefers-color-scheme: dark)').matches; } /** * Dark Mode aktivieren/deaktivieren * @param {boolean} enable - Dark Mode aktivieren (true) oder deaktivieren (false) */ setDarkMode(enable) { if (enable) { this.html.classList.add('dark'); this.html.setAttribute('data-theme', 'dark'); this.html.style.colorScheme = 'dark'; this.updateDarkModeIcon(true); this.darkModeToggle.setAttribute('aria-pressed', 'true'); this.darkModeToggle.setAttribute('title', 'Light Mode aktivieren'); if (this.sunIcon && this.moonIcon) { this.sunIcon.classList.add('hidden'); this.moonIcon.classList.remove('hidden'); } } else { this.html.classList.remove('dark'); this.html.setAttribute('data-theme', 'light'); this.html.style.colorScheme = 'light'; this.updateDarkModeIcon(false); this.darkModeToggle.setAttribute('aria-pressed', 'false'); this.darkModeToggle.setAttribute('title', 'Dark Mode aktivieren'); if (this.sunIcon && this.moonIcon) { this.sunIcon.classList.remove('hidden'); this.moonIcon.classList.add('hidden'); } } // Einstellung im Local Storage speichern localStorage.setItem(this.STORAGE_KEY, enable); // ThemeColor Meta-Tag aktualisieren const metaThemeColor = document.getElementById('metaThemeColor'); if (metaThemeColor) { metaThemeColor.setAttribute('content', enable ? '#000000' : '#ffffff'); } // Event für andere Komponenten auslösen window.dispatchEvent(new CustomEvent('darkModeChanged', { detail: { isDark: enable } })); console.log(`${enable ? '🌙' : '☀️'} Design umgeschaltet auf: ${enable ? 'Dark Mode' : 'Light Mode'}`); } /** * Icon für Dark Mode Toggle aktualisieren * @param {boolean} isDark - Ob Dark Mode aktiv ist */ updateDarkModeIcon(isDark) { if (!this.darkModeToggle) return; // Stellen sicher, dass die Icons korrekt angezeigt werden if (this.sunIcon && this.moonIcon) { if (isDark) { this.sunIcon.classList.add('hidden'); this.moonIcon.classList.remove('hidden'); } else { this.sunIcon.classList.remove('hidden'); this.moonIcon.classList.add('hidden'); } } } /** * Event Listener einrichten und Darkmode initialisieren */ init() { if (!this.darkModeToggle) { console.error('⚠️ Dark Mode Toggle Button nicht gefunden! UI-Komponente nicht verfügbar.'); return; } console.log('🚀 Dark Mode Manager erfolgreich initialisiert'); // Event Listener für den Dark Mode Toggle Button this.darkModeToggle.addEventListener('click', () => { console.log('👆 Dark Mode Toggle Button angeklickt'); const newDarkModeState = !this.isDarkMode(); this.setDarkMode(newDarkModeState); }); // Alternative Event-Listener für Buttons mit data-action document.addEventListener('click', (e) => { if (e.target.closest('[data-action="toggle-dark-mode"]')) { console.log('👆 Dark Mode Toggle über data-action aktiviert'); const newDarkModeState = !this.isDarkMode(); this.setDarkMode(newDarkModeState); } }); // Tastaturkürzel: Strg+Shift+D für Dark Mode Toggle document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.shiftKey && e.key === 'D') { const newDarkModeState = !this.isDarkMode(); this.setDarkMode(newDarkModeState); e.preventDefault(); } }); // System-Preference für Dark Mode überwachen const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); darkModeMediaQuery.addEventListener('change', (e) => { // Nur ändern, wenn der Benutzer noch keine eigene Einstellung hat if (localStorage.getItem(this.STORAGE_KEY) === null) { this.setDarkMode(e.matches); } }); // Initialisierung: Aktuellen Status setzen const isDark = this.isDarkMode(); console.log(`🔍 Initialer Dark Mode Status: ${isDark ? '🌙 aktiviert' : '☀️ deaktiviert'}`); this.setDarkMode(isDark); } } /** * Mobile Menu Manager */ class MobileMenuManager { constructor() { this.mobileMenuToggle = document.getElementById('mobileMenuToggle'); this.mobileMenu = document.getElementById('mobileMenu'); this.isOpen = false; this.init(); } init() { if (!this.mobileMenuToggle || !this.mobileMenu) { console.log('ℹ️ Mobile Menu Komponenten nicht gefunden - Feature deaktiviert'); return; } console.log('📱 Mobile Menu Manager erfolgreich initialisiert'); // Event Listener für den Mobile Menu Toggle Button this.mobileMenuToggle.addEventListener('click', () => { this.toggleMenu(); }); // Event Listener für Klicks außerhalb des Menüs document.addEventListener('click', (e) => { if (this.isOpen && !this.mobileMenuToggle.contains(e.target) && !this.mobileMenu.contains(e.target)) { this.closeMenu(); } }); // Event Listener für das Schließen bei Klick auf einen Menüpunkt this.mobileMenu.querySelectorAll('a').forEach(link => { link.addEventListener('click', () => { this.closeMenu(); }); }); // Beim Scrollen das Menü schließen window.addEventListener('scroll', () => { if (this.isOpen && window.scrollY > 50) { this.closeMenu(); } }); } toggleMenu() { if (this.isOpen) { this.closeMenu(); } else { this.openMenu(); } } openMenu() { if (!this.mobileMenu || this.isOpen) return; this.mobileMenu.classList.remove('hidden'); this.isOpen = true; this.mobileMenuToggle.setAttribute('aria-expanded', 'true'); // Icon ändern const menuIcon = this.mobileMenuToggle.querySelector('svg'); if (menuIcon) { menuIcon.innerHTML = ''; } // Animation - kleine Verzögerung für sanfte Animation setTimeout(() => { this.mobileMenu.classList.add('open'); }, 10); // Body-Scroll verhindern (optional) // document.body.style.overflow = 'hidden'; } closeMenu() { if (!this.mobileMenu || !this.isOpen) return; this.mobileMenu.classList.remove('open'); this.isOpen = false; this.mobileMenuToggle.setAttribute('aria-expanded', 'false'); // Icon zurücksetzen const menuIcon = this.mobileMenuToggle.querySelector('svg'); if (menuIcon) { menuIcon.innerHTML = ''; } // Nach der Animation ausblenden setTimeout(() => { this.mobileMenu.classList.add('hidden'); }, 300); // Body-Scroll wiederherstellen // document.body.style.overflow = ''; } } /** * User Dropdown Manager */ class UserDropdownManager { constructor() { this.toggleButton = document.getElementById('user-menu-button'); this.container = document.getElementById('user-menu-container'); this.dropdown = document.getElementById('user-dropdown'); this.isOpen = false; // Dropdown erstellen, falls nicht vorhanden if (!this.dropdown && this.container) { this.createDropdown(); } this.init(); } /** * Dropdown-Menü dynamisch erstellen, falls es nicht existiert */ createDropdown() { if (!this.container) return; // Neue Dropdown-Box erstellen this.dropdown = document.createElement('div'); this.dropdown.id = 'user-dropdown'; this.dropdown.className = 'user-dropdown hidden'; this.dropdown.setAttribute('role', 'menu'); this.dropdown.setAttribute('aria-orientation', 'vertical'); this.dropdown.setAttribute('aria-labelledby', 'user-menu-button'); // HTML für das Dropdown-Menü this.dropdown.innerHTML = ` Mein Profil Einstellungen `; // Dropdown zum Container hinzufügen this.container.appendChild(this.dropdown); } /** * Benutzername aus E-Mail extrahieren oder Standardwert verwenden */ getCurrentUsername() { const metaUsername = document.querySelector('meta[name="user-name"]'); if (metaUsername) { return metaUsername.getAttribute('content'); } // Aus dem vorhandenen DOM extrahieren const usernameElement = this.container.querySelector('.text-sm.font-medium'); if (usernameElement) { return usernameElement.textContent.trim(); } return 'Benutzer'; } /** * E-Mail-Adresse aus dem DOM extrahieren */ getCurrentUserEmail() { const metaEmail = document.querySelector('meta[name="user-email"]'); if (metaEmail) { return metaEmail.getAttribute('content'); } // Aus dem vorhandenen DOM extrahieren const emailElement = this.container.querySelector('.text-xs'); if (emailElement) { return emailElement.textContent.trim(); } return 'Mercedes-Benz Mitarbeiter'; } /** * Initial des Benutzers extrahieren */ getCurrentUserInitial() { const avatar = this.container.querySelector('.user-avatar'); if (avatar) { return avatar.textContent.trim(); } return 'U'; } open() { if (!this.dropdown || this.isOpen) return; this.dropdown.classList.remove('hidden'); this.isOpen = true; this.toggleButton.setAttribute('aria-expanded', 'true'); // Rotate dropdown arrow const arrow = this.toggleButton.querySelector('svg'); if (arrow) { arrow.style.transform = 'rotate(180deg)'; } // Close when clicking outside setTimeout(() => { document.addEventListener('click', this.outsideClickHandler); }, 10); // Close when pressing escape document.addEventListener('keydown', this.escapeKeyHandler); } close() { if (!this.dropdown || !this.isOpen) return; this.dropdown.classList.add('hidden'); this.isOpen = false; this.toggleButton.setAttribute('aria-expanded', 'false'); // Reset dropdown arrow const arrow = this.toggleButton.querySelector('svg'); if (arrow) { arrow.style.transform = 'rotate(0deg)'; } // Remove event listeners document.removeEventListener('click', this.outsideClickHandler); document.removeEventListener('keydown', this.escapeKeyHandler); } toggle() { if (this.isOpen) { this.close(); } else { this.open(); } } init() { if (!this.toggleButton || !this.dropdown) return; // Bind methods to this instance this.outsideClickHandler = this.handleOutsideClick.bind(this); this.escapeKeyHandler = this.handleEscapeKey.bind(this); // Set up click listener for toggle button this.toggleButton.addEventListener('click', (e) => { e.stopPropagation(); this.toggle(); }); // Set up logout button const logoutButton = this.dropdown.querySelector('[data-action="logout"]'); if (logoutButton) { logoutButton.addEventListener('click', this.handleLogout.bind(this)); } // Set up settings button const settingsLink = this.dropdown.querySelector('#settings-link'); if (settingsLink) { settingsLink.addEventListener('click', this.handleSettings.bind(this)); } } handleOutsideClick(e) { if (this.container && !this.container.contains(e.target)) { this.close(); } } handleEscapeKey(e) { if (e.key === 'Escape') { this.close(); } } handleLogout(e) { e.preventDefault(); // Add smooth logout animation document.body.style.opacity = '0.7'; // Create and submit logout form const form = document.createElement('form'); form.method = 'POST'; form.action = '/logout'; // Add CSRF token if available const csrfToken = document.querySelector('meta[name="csrf-token"]'); if (csrfToken) { const input = document.createElement('input'); input.type = 'hidden'; input.name = 'csrf_token'; input.value = csrfToken.getAttribute('content'); form.appendChild(input); } // Submit the form document.body.appendChild(form); form.submit(); } handleSettings(e) { // Optionales preventDefault für spezielle Handhabung // e.preventDefault(); } } /** * Toast-Benachrichtigungen */ class ToastManager { constructor() { this.container = this.createContainer(); this.toasts = new Map(); } createContainer() { let container = document.getElementById('toast-container'); if (!container) { container = document.createElement('div'); container.id = 'toast-container'; container.className = 'fixed top-4 right-4 z-50 space-y-2'; document.body.appendChild(container); } return container; } show(message, type = 'info', duration = 5000) { const id = 'toast-' + Date.now(); const toast = this.createToast(id, message, type); this.container.appendChild(toast); this.toasts.set(id, toast); // Animation einblenden requestAnimationFrame(() => { toast.classList.remove('translate-x-full', 'opacity-0'); }); // Auto-Hide nach duration if (duration > 0) { setTimeout(() => this.hide(id), duration); } return id; } createToast(id, message, type) { const toast = document.createElement('div'); toast.id = id; toast.className = `transform translate-x-full opacity-0 transition-all duration-300 ease-in-out max-w-sm w-full bg-white dark:bg-slate-800 shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden`; const typeClasses = { 'success': 'border-l-4 border-green-400', 'error': 'border-l-4 border-red-400', 'warning': 'border-l-4 border-yellow-400', 'info': 'border-l-4 border-blue-400' }; const iconHTML = { 'success': '', 'error': '', 'warning': '', 'info': '' }; toast.className += ' ' + (typeClasses[type] || typeClasses.info); toast.innerHTML = `
${iconHTML[type] || iconHTML.info}

${message}

`; return toast; } hide(id) { const toast = this.toasts.get(id); if (toast) { toast.classList.add('translate-x-full', 'opacity-0'); setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } this.toasts.delete(id); }, 300); } } success(message, duration) { return this.show(message, 'success', duration); } error(message, duration) { return this.show(message, 'error', duration); } warning(message, duration) { return this.show(message, 'warning', duration); } info(message, duration) { return this.show(message, 'info', duration); } } /** * Modal-Dialog-Manager */ class ModalManager { constructor() { this.activeModals = new Set(); this.setupEventListeners(); } setupEventListeners() { // ESC-Taste zum Schließen document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.activeModals.size > 0) { this.closeTopModal(); } }); } open(modalId, options = {}) { const modal = document.getElementById(modalId); if (!modal) { console.error(`Modal mit ID '${modalId}' nicht gefunden`); return; } // Backdrop erstellen const backdrop = this.createBackdrop(modalId); document.body.appendChild(backdrop); // Modal anzeigen modal.style.display = 'block'; this.activeModals.add(modalId); // Animation requestAnimationFrame(() => { backdrop.classList.remove('opacity-0'); modal.querySelector('.modal-content')?.classList.remove('scale-95', 'opacity-0'); }); // Body-Scroll verhindern document.body.style.overflow = 'hidden'; // Auto-Focus auf erstes Input-Element const firstInput = modal.querySelector('input, select, textarea, button'); if (firstInput) { firstInput.focus(); } } close(modalId) { const modal = document.getElementById(modalId); const backdrop = document.getElementById(`backdrop-${modalId}`); if (modal && backdrop) { // Animation ausblenden backdrop.classList.add('opacity-0'); modal.querySelector('.modal-content')?.classList.add('scale-95', 'opacity-0'); setTimeout(() => { modal.style.display = 'none'; if (backdrop.parentNode) { backdrop.parentNode.removeChild(backdrop); } this.activeModals.delete(modalId); // Body-Scroll wiederherstellen, falls kein Modal mehr offen if (this.activeModals.size === 0) { document.body.style.overflow = ''; } }, 150); } } closeTopModal() { if (this.activeModals.size > 0) { const lastModal = Array.from(this.activeModals).pop(); this.close(lastModal); } } createBackdrop(modalId) { const backdrop = document.createElement('div'); backdrop.id = `backdrop-${modalId}`; backdrop.className = 'fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity opacity-0 z-40'; backdrop.onclick = () => this.close(modalId); return backdrop; } } /** * Dropdown-Manager */ class DropdownManager { constructor() { this.activeDropdowns = new Set(); this.setupEventListeners(); } setupEventListeners() { document.addEventListener('click', (e) => { // Schließe alle Dropdowns, wenn außerhalb geklickt wird if (!e.target.closest('[data-dropdown]')) { this.closeAll(); } }); } toggle(dropdownId) { const dropdown = document.querySelector(`[data-dropdown="${dropdownId}"]`); if (!dropdown) return; const menu = dropdown.querySelector('.dropdown-menu'); if (!menu) return; if (this.activeDropdowns.has(dropdownId)) { this.close(dropdownId); } else { this.closeAll(); // Andere schließen this.open(dropdownId); } } open(dropdownId) { const dropdown = document.querySelector(`[data-dropdown="${dropdownId}"]`); const menu = dropdown?.querySelector('.dropdown-menu'); if (dropdown && menu) { menu.classList.remove('hidden'); this.activeDropdowns.add(dropdownId); // Position berechnen this.positionDropdown(dropdown, menu); } } close(dropdownId) { const dropdown = document.querySelector(`[data-dropdown="${dropdownId}"]`); const menu = dropdown?.querySelector('.dropdown-menu'); if (menu) { menu.classList.add('hidden'); this.activeDropdowns.delete(dropdownId); } } closeAll() { this.activeDropdowns.forEach(id => this.close(id)); } positionDropdown(dropdown, menu) { const rect = dropdown.getBoundingClientRect(); const menuRect = menu.getBoundingClientRect(); const viewportHeight = window.innerHeight; // Prüfen ob Platz nach unten ist if (rect.bottom + menuRect.height > viewportHeight) { // Nach oben öffnen menu.style.bottom = '100%'; menu.style.top = 'auto'; } else { // Nach unten öffnen menu.style.top = '100%'; menu.style.bottom = 'auto'; } } } /** * Loading-Spinner-Manager */ class LoadingManager { show(target = document.body, message = 'Laden...') { const loadingId = 'loading-' + Date.now(); const overlay = document.createElement('div'); overlay.id = loadingId; overlay.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50'; overlay.innerHTML = `
${message}
`; target.appendChild(overlay); return loadingId; } hide(loadingId) { const overlay = document.getElementById(loadingId); if (overlay) { overlay.remove(); } } } /** * Status-Badge-Helper */ class StatusHelper { static getPrinterStatusClass(status) { const statusMap = { 'ready': 'printer-ready', 'busy': 'printer-busy', 'error': 'printer-error', 'offline': 'printer-offline', 'maintenance': 'printer-maintenance' }; return `printer-status ${statusMap[status] || 'printer-offline'}`; } static getJobStatusClass(status) { const statusMap = { 'queued': 'job-queued', 'printing': 'job-printing', 'completed': 'job-completed', 'failed': 'job-failed', 'cancelled': 'job-cancelled', 'paused': 'job-paused' }; return `job-status ${statusMap[status] || 'job-queued'}`; } static formatStatus(status, type = 'job') { const translations = { job: { 'queued': 'In Warteschlange', 'printing': 'Wird gedruckt', 'completed': 'Abgeschlossen', 'failed': 'Fehlgeschlagen', 'cancelled': 'Abgebrochen', 'paused': 'Pausiert' }, printer: { 'ready': 'Bereit', 'busy': 'Beschäftigt', 'error': 'Fehler', 'offline': 'Offline', 'maintenance': 'Wartung' } }; return translations[type]?.[status] || status; } } /** * Connection Status Manager */ class ConnectionStatusManager { constructor() { this.statusElement = document.getElementById('connection-status'); if (this.statusElement) { this.init(); } } updateStatus() { if (!this.statusElement) return; if (navigator.onLine) { this.statusElement.innerHTML = '
Online'; } else { this.statusElement.innerHTML = '
Offline'; } } init() { // Initial Update this.updateStatus(); // Event Listener für Netzwerkänderungen window.addEventListener('online', () => this.updateStatus()); window.addEventListener('offline', () => this.updateStatus()); } } /** * API-Aufruf Hilfsfunktion für Fetch-Requests * @param {string} url - API-Endpunkt * @param {object} options - Fetch-Optionen * @returns {Promise} API-Antwort als JSON */ async function apiCall(url, options = {}) { // CSRF-Token aus Meta-Tag auslesen const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content; // Standard-Headers setzen const headers = { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }; // CSRF-Token hinzufügen, wenn vorhanden if (csrfToken) { headers['X-CSRFToken'] = csrfToken; } // Optionen zusammenführen const fetchOptions = { ...options, headers: { ...headers, ...(options.headers || {}) } }; try { const response = await fetch(url, fetchOptions); // Prüfen, ob die Antwort JSON ist const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { const data = await response.json(); // Wenn die Antwort einen Fehler enthält, werfen wir eine Ausnahme if (!response.ok) { throw new Error(data.error || `HTTP-Fehler: ${response.status}`); } return data; } // Für nicht-JSON-Antworten if (!response.ok) { throw new Error(`HTTP-Fehler: ${response.status}`); } return { success: true }; } catch (error) { console.error('API-Aufruf fehlgeschlagen:', error); throw error; } } /** * Erweiterte Flash-Nachricht anzeigen mit glasigen Effekten * @param {string} message - Nachrichtentext * @param {string} type - Nachrichtentyp (success, error, info, warning) * @param {number} duration - Anzeigedauer in Millisekunden (Standard: 5000) */ function showFlashMessage(message, type = 'info', duration = 5000) { // Unique ID für die Nachricht const messageId = 'flash-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); // Flash-Message-Element erstellen const flashElement = document.createElement('div'); flashElement.id = messageId; flashElement.className = `flash-message ${type}`; // Icon basierend auf Typ let icon = ''; switch(type) { case 'success': icon = ` `; break; case 'error': icon = ` `; break; case 'warning': icon = ` `; break; case 'info': default: icon = ` `; } // Inhalt der Flash Message flashElement.innerHTML = `
${icon}

${message}

`; // Flash Message zum DOM hinzufügen document.body.appendChild(flashElement); // Flash Messages vertikal stapeln repositionFlashMessages(); // Einblende-Animation starten requestAnimationFrame(() => { flashElement.style.transform = 'translateX(0) translateY(0)'; flashElement.style.opacity = '1'; }); // Nach der angegebenen Zeit automatisch entfernen setTimeout(() => { closeFlashMessage(messageId); }, duration); return messageId; } /** * Flash Message schließen * @param {string} messageId - ID der zu schließenden Nachricht */ function closeFlashMessage(messageId) { const flashElement = document.getElementById(messageId); if (flashElement) { flashElement.classList.add('hiding'); setTimeout(() => { if (flashElement.parentNode) { flashElement.parentNode.removeChild(flashElement); } repositionFlashMessages(); }, 400); // Dauer der Ausblende-Animation } } /** * Flash Messages neu positionieren für Stapel-Effekt */ function repositionFlashMessages() { const flashMessages = document.querySelectorAll('.flash-message:not(.hiding)'); const gap = 12; // Abstand zwischen Messages const startTop = 16; // Top-Offset flashMessages.forEach((flash, index) => { const yPosition = startTop + (index * (80 + gap)); // 80px Höhe + gap // Position setzen flash.style.position = 'fixed'; flash.style.top = `${yPosition}px`; flash.style.right = '16px'; flash.style.zIndex = 50 + (flashMessages.length - index); // Neueste Messages haben höheren z-index flash.style.width = 'auto'; flash.style.maxWidth = '420px'; flash.style.minWidth = '320px'; // Initiale Position für Animation (nur bei neuen Messages) if (!flash.style.transform) { flash.style.transform = 'translateX(100%) translateY(-20px)'; flash.style.opacity = '0'; } }); } /** * Navbar Scroll Manager - Glassmorphism Effekte beim Scrollen */ class NavbarScrollManager { constructor() { this.navbar = document.querySelector('.navbar'); this.isScrolled = false; this.scrollThreshold = 50; // Ab wie vielen Pixeln der Effekt aktiviert wird this.ticking = false; // Für requestAnimationFrame Optimierung this.init(); } /** * Scroll-Handler mit Performance-Optimierung */ handleScroll() { if (!this.ticking) { requestAnimationFrame(() => { this.updateNavbar(); this.ticking = false; }); this.ticking = true; } } /** * Navbar basierend auf Scroll-Position aktualisieren */ updateNavbar() { if (!this.navbar) return; const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const shouldBeScrolled = scrollTop > this.scrollThreshold; if (shouldBeScrolled !== this.isScrolled) { this.isScrolled = shouldBeScrolled; if (this.isScrolled) { this.navbar.classList.add('scrolled'); this.navbar.style.transform = 'translateY(0)'; } else { this.navbar.classList.remove('scrolled'); } // Event für andere Komponenten auslösen window.dispatchEvent(new CustomEvent('navbarScrolled', { detail: { isScrolled: this.isScrolled, scrollTop } })); } } /** * Sanfte Navbar-Animation beim Scrollen nach oben/unten */ handleDirectionalScroll() { let lastScrollTop = 0; return () => { if (!this.navbar) return; const scrollTop = window.pageYOffset || document.documentElement.scrollTop; if (scrollTop > lastScrollTop && scrollTop > this.scrollThreshold) { // Scrollen nach unten - Navbar ausblenden this.navbar.style.transform = 'translateY(-100%)'; } else { // Scrollen nach oben - Navbar einblenden this.navbar.style.transform = 'translateY(0)'; } lastScrollTop = scrollTop <= 0 ? 0 : scrollTop; }; } /** * Parallax-Effekt für die Navbar */ applyParallaxEffect() { if (!this.navbar) return; const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const parallaxSpeed = 0.5; const yPos = -(scrollTop * parallaxSpeed); // Nur bei größeren Bildschirmen anwenden if (window.innerWidth > 768) { this.navbar.style.transform = `translateY(${yPos}px)`; } } /** * Glassmorphism-Intensität basierend auf Scroll-Position */ updateGlassmorphismIntensity() { if (!this.navbar) return; const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const maxScroll = 200; // Maximale Scroll-Distanz für vollen Effekt const intensity = Math.min(scrollTop / maxScroll, 1); // CSS Custom Properties für dynamische Blur-Werte const blurValue = 40 + (intensity * 20); // Von 40px zu 60px const opacityValue = 0.15 + (intensity * 0.2); // Von 0.15 zu 0.35 this.navbar.style.setProperty('--navbar-blur', `${blurValue}px`); this.navbar.style.setProperty('--navbar-opacity', opacityValue); } /** * Navbar-Manager initialisieren */ init() { if (!this.navbar) { console.log('ℹ️ Navbar nicht gefunden - Scroll-Effekte deaktiviert'); return; } console.log('🌊 Navbar Scroll Manager erfolgreich initialisiert'); // CSS Custom Properties initialisieren this.navbar.style.setProperty('--navbar-blur', '40px'); this.navbar.style.setProperty('--navbar-opacity', '0.15'); // Event Listener für Scroll-Events window.addEventListener('scroll', this.handleScroll.bind(this), { passive: true }); // Optionale erweiterte Effekte (können aktiviert werden) // const directionalScroll = this.handleDirectionalScroll(); // window.addEventListener('scroll', directionalScroll, { passive: true }); // Resize-Handler für responsive Anpassungen window.addEventListener('resize', () => { this.updateNavbar(); }); // Initialer Status setzen this.updateNavbar(); } } /** * Do Not Disturb Manager * Verwaltet den Do Not Disturb-Modus für Flash Messages und Benachrichtigungen */ class DoNotDisturbManager { constructor() { this.isActive = false; this.suppressedMessages = []; this.settings = { allowCritical: true, allowErrorsOnly: false, suppressDuration: 60, // Minuten autoDisable: true }; this.suppressEndTime = null; this.indicator = null; this.counter = 0; this.init(); } /** * Do Not Disturb-System initialisieren */ init() { this.loadSettings(); this.createIndicator(); this.setupEventListeners(); this.checkAutoDisable(); console.log('🔕 Do Not Disturb Manager erfolgreich initialisiert'); } /** * Einstellungen aus localStorage laden */ loadSettings() { try { const saved = localStorage.getItem('dnd-settings'); if (saved) { this.settings = { ...this.settings, ...JSON.parse(saved) }; } const state = localStorage.getItem('dnd-state'); if (state) { const savedState = JSON.parse(state); this.isActive = savedState.isActive || false; this.suppressEndTime = savedState.suppressEndTime ? new Date(savedState.suppressEndTime) : null; this.suppressedMessages = savedState.suppressedMessages || []; this.counter = savedState.counter || 0; } } catch (error) { console.error('Fehler beim Laden der DND-Einstellungen:', error); } } /** * Einstellungen in localStorage speichern */ saveSettings() { try { localStorage.setItem('dnd-settings', JSON.stringify(this.settings)); localStorage.setItem('dnd-state', JSON.stringify({ isActive: this.isActive, suppressEndTime: this.suppressEndTime, suppressedMessages: this.suppressedMessages, counter: this.counter })); } catch (error) { console.error('Fehler beim Speichern der DND-Einstellungen:', error); } } /** * DND-Indikator erstellen */ createIndicator() { this.indicator = document.createElement('div'); this.indicator.className = 'dnd-indicator'; this.indicator.innerHTML = ` Nicht stören `; this.indicator.addEventListener('click', () => this.showSettings()); document.body.appendChild(this.indicator); this.updateIndicator(); } /** * Event-Listener einrichten */ setupEventListeners() { // Original showFlashMessage überschreiben const originalShowFlashMessage = window.showFlashMessage; window.showFlashMessage = (message, type = 'info') => { this.handleFlashMessage(message, type, originalShowFlashMessage); }; // Original showToast überschreiben if (window.showToast) { const originalShowToast = window.showToast; window.showToast = (message, type = 'info', duration) => { this.handleToastMessage(message, type, duration, originalShowToast); }; } // Periodisch Auto-Disable prüfen setInterval(() => this.checkAutoDisable(), 60000); // Jede Minute } /** * Flash Message verarbeiten */ handleFlashMessage(message, type, originalFunction) { if (this.shouldSuppressMessage(type)) { this.addSuppressedMessage(message, type, 'flash'); this.showSuppressedMessage(message, type); } else { originalFunction(message, type); } } /** * Toast Message verarbeiten */ handleToastMessage(message, type, duration, originalFunction) { if (this.shouldSuppressMessage(type)) { this.addSuppressedMessage(message, type, 'toast'); this.showSuppressedMessage(message, type); } else { originalFunction(message, type, duration); } } /** * Prüfen, ob Nachricht unterdrückt werden soll */ shouldSuppressMessage(type) { if (!this.isActive) return false; // Kritische Nachrichten immer anzeigen (falls eingestellt) if (this.settings.allowCritical && (type === 'error' || type === 'critical')) { return false; } // Nur Fehler anzeigen (falls eingestellt) if (this.settings.allowErrorsOnly && type !== 'error') { return true; } return true; } /** * Unterdrückte Nachricht hinzufügen */ addSuppressedMessage(message, type, source) { const suppressedMessage = { id: Date.now(), message, type, source, timestamp: new Date(), read: false }; this.suppressedMessages.unshift(suppressedMessage); this.counter++; // Nur die letzten 50 Nachrichten behalten if (this.suppressedMessages.length > 50) { this.suppressedMessages = this.suppressedMessages.slice(0, 50); } this.updateIndicator(); this.saveSettings(); } /** * Gedämpfte Version der Nachricht anzeigen */ showSuppressedMessage(message, type) { const suppressedFlash = document.createElement('div'); suppressedFlash.className = `flash-message dnd-suppressed ${type}`; suppressedFlash.innerHTML = `
Nachricht unterdrückt
`; suppressedFlash.style.top = '4rem'; document.body.appendChild(suppressedFlash); setTimeout(() => { suppressedFlash.remove(); }, 2000); } /** * DND-Modus aktivieren */ enable(duration = null) { this.isActive = true; if (duration) { this.suppressEndTime = new Date(Date.now() + duration * 60000); } else { this.suppressEndTime = null; } this.updateIndicator(); this.saveSettings(); console.log('🔕 Do Not Disturb aktiviert', duration ? `für ${duration} Minuten` : 'dauerhaft'); } /** * DND-Modus deaktivieren */ disable() { this.isActive = false; this.suppressEndTime = null; this.updateIndicator(); this.saveSettings(); console.log('🔔 Do Not Disturb deaktiviert'); } /** * DND-Modus umschalten */ toggle() { if (this.isActive) { this.disable(); } else { this.showSettings(); } } /** * Auto-Disable prüfen */ checkAutoDisable() { if (this.isActive && this.suppressEndTime && new Date() >= this.suppressEndTime) { this.disable(); if (window.showToast) { window.showToast('Do Not Disturb automatisch deaktiviert', 'info'); } } } /** * Indikator aktualisieren */ updateIndicator() { if (!this.indicator) return; if (this.isActive) { this.indicator.classList.add('active'); // Counter Badge aktualisieren const badge = this.indicator.querySelector('.dnd-counter-badge'); if (this.counter > 0) { badge.textContent = this.counter > 99 ? '99+' : this.counter; badge.classList.remove('hidden'); } else { badge.classList.add('hidden'); } // Zeitanzeige const text = this.indicator.querySelector('.dnd-text'); if (this.suppressEndTime) { const remaining = Math.ceil((this.suppressEndTime - new Date()) / 60000); text.textContent = `Nicht stören (${remaining}min)`; } else { text.textContent = 'Nicht stören'; } } else { this.indicator.classList.remove('active'); } } /** * Einstellungs-Modal anzeigen */ showSettings() { // Prüfen ob Modal bereits offen ist if (document.querySelector('.dnd-modal')) { return; // Modal bereits offen, nicht doppelt öffnen } const modal = document.createElement('div'); modal.className = 'dnd-modal'; modal.innerHTML = `

🔕 Nicht stören

${this.suppressedMessages.length > 0 ? `

Unterdrückte Nachrichten (${this.suppressedMessages.length})

${this.suppressedMessages.slice(0, 5).map(msg => `
${msg.type.toUpperCase()}: ${msg.message}
${msg.timestamp.toLocaleTimeString()}
`).join('')} ${this.suppressedMessages.length > 5 ? `
... und ${this.suppressedMessages.length - 5} weitere
` : ''}
` : ''}
${this.isActive ? ` ` : ''}
`; // Event Listeners mit Event Delegation modal.addEventListener('click', (e) => { e.stopPropagation(); // Schließen bei Klick auf Hintergrund if (e.target === modal) { this.closeModal(modal); return; } // Schließen-Buttons if (e.target.closest('.dnd-close-btn') || e.target.closest('.dnd-close-btn-main')) { e.preventDefault(); this.closeModal(modal); return; } // Schnell-Aktionen if (e.target.classList.contains('dnd-quick-btn')) { e.preventDefault(); const duration = parseInt(e.target.dataset.duration); if (duration === 0) { this.enable(); } else { this.enable(duration); } this.closeModal(modal); return; } // Deaktivieren-Button if (e.target.classList.contains('dnd-disable-btn')) { e.preventDefault(); this.disable(); this.closeModal(modal); return; } // Alle löschen if (e.target.classList.contains('dnd-clear-btn')) { e.preventDefault(); this.clearSuppressedMessages(); this.closeModal(modal); // Modal neu öffnen mit aktualisierten Daten setTimeout(() => this.showSettings(), 100); return; } // Einstellungen-Checkboxen if (e.target.classList.contains('dnd-setting')) { const setting = e.target.dataset.setting; this.settings[setting] = e.target.checked; this.saveSettings(); return; } }); // ESC-Taste zum Schließen const escapeHandler = (e) => { if (e.key === 'Escape') { this.closeModal(modal); document.removeEventListener('keydown', escapeHandler); } }; document.addEventListener('keydown', escapeHandler); // Modal zum DOM hinzufügen document.body.appendChild(modal); } /** * Modal schließen */ closeModal(modal) { if (modal && modal.parentNode) { modal.style.opacity = '0'; modal.style.pointerEvents = 'none'; setTimeout(() => { if (modal.parentNode) { modal.parentNode.removeChild(modal); } }, 200); } } /** * Unterdrückte Nachrichten löschen */ clearSuppressedMessages() { this.suppressedMessages = []; this.counter = 0; this.updateIndicator(); this.saveSettings(); } /** * Unterdrückte Nachrichten abrufen */ getSuppressedMessages() { return [...this.suppressedMessages]; } /** * Status abrufen */ getStatus() { return { isActive: this.isActive, suppressEndTime: this.suppressEndTime, suppressedCount: this.suppressedMessages.length, settings: { ...this.settings } }; } } /** * Navbar Do Not Disturb Integration * Verbindet den DND-Button in der Navbar mit dem DoNotDisturbManager */ class NavbarDNDIntegration { constructor(dndManager) { this.dndManager = dndManager; this.button = document.getElementById('dndToggle'); this.counter = document.getElementById('dndCounter'); this.iconOff = null; this.iconOn = null; this.tooltipOff = null; this.tooltipOn = null; this.init(); } /** * Navbar DND Integration initialisieren */ init() { if (!this.button) { console.log('ℹ️ DND Button nicht gefunden - Navbar Integration deaktiviert'); return; } this.iconOff = this.button.querySelector('.dnd-icon-off'); this.iconOn = this.button.querySelector('.dnd-icon-on'); this.tooltipOff = this.button.querySelector('.dnd-tooltip-off'); this.tooltipOn = this.button.querySelector('.dnd-tooltip-on'); // Event Listener this.button.addEventListener('click', () => this.handleButtonClick()); // Initial state setzen this.updateButton(); // Status-Änderungen überwachen setInterval(() => this.updateButton(), 1000); console.log('🔕 Navbar DND Integration erfolgreich initialisiert'); } /** * Button-Click Handler */ handleButtonClick() { this.dndManager.toggle(); this.updateButton(); } /** * Button-Erscheinungsbild aktualisieren */ updateButton() { if (!this.button) return; const status = this.dndManager.getStatus(); if (status.isActive) { // DND ist aktiv this.button.classList.add('dnd-active'); if (this.iconOff) { this.iconOff.style.opacity = '0'; this.iconOff.style.transform = 'scale(0.75)'; } if (this.iconOn) { this.iconOn.style.opacity = '1'; this.iconOn.style.transform = 'scale(1)'; } if (this.tooltipOff) this.tooltipOff.classList.add('hidden'); if (this.tooltipOn) this.tooltipOn.classList.remove('hidden'); // Counter aktualisieren if (this.counter && status.suppressedCount > 0) { this.counter.textContent = status.suppressedCount > 99 ? '99+' : status.suppressedCount; this.counter.classList.remove('hidden'); } else if (this.counter) { this.counter.classList.add('hidden'); } // Button-Erscheinungsbild this.button.style.background = 'rgba(239, 68, 68, 0.1)'; this.button.style.borderColor = 'rgba(239, 68, 68, 0.3)'; } else { // DND ist inaktiv this.button.classList.remove('dnd-active'); if (this.iconOff) { this.iconOff.style.opacity = '1'; this.iconOff.style.transform = 'scale(1)'; } if (this.iconOn) { this.iconOn.style.opacity = '0'; this.iconOn.style.transform = 'scale(0.75)'; } if (this.tooltipOff) this.tooltipOff.classList.remove('hidden'); if (this.tooltipOn) this.tooltipOn.classList.add('hidden'); if (this.counter) this.counter.classList.add('hidden'); // Button-Erscheinungsbild zurücksetzen this.button.style.background = ''; this.button.style.borderColor = ''; } } } // Initialisierung aller UI-Komponenten document.addEventListener('DOMContentLoaded', function() { // Toast-Manager window.MYP.UI.toast = new ToastManager(); // Modal-Manager window.MYP.UI.modal = new ModalManager(); // Dropdown-Manager window.MYP.UI.dropdown = new DropdownManager(); // Loading-Manager window.MYP.UI.loading = new LoadingManager(); // Dark Mode Manager window.MYP.UI.darkMode = new DarkModeManager(); // Mobile Menu Manager window.MYP.UI.mobileMenu = new MobileMenuManager(); // User Dropdown Manager window.MYP.UI.userDropdown = new UserDropdownManager(); // Connection Status Manager window.MYP.UI.connectionStatus = new ConnectionStatusManager(); // Navbar Scroll Manager für Glassmorphism-Effekte window.MYP.UI.navbarScroll = new NavbarScrollManager(); // Do Not Disturb Manager window.MYP.UI.doNotDisturb = new DoNotDisturbManager(); // Navbar DND Integration window.MYP.UI.navbarDND = new NavbarDNDIntegration(window.MYP.UI.doNotDisturb); // Convenience-Methoden window.showToast = (message, type, duration) => window.MYP.UI.toast.show(message, type, duration); window.showModal = (modalId, options) => window.MYP.UI.modal.open(modalId, options); window.hideModal = (modalId) => window.MYP.UI.modal.close(modalId); window.toggleDarkMode = () => window.MYP.UI.darkMode.setDarkMode(!window.MYP.UI.darkMode.isDarkMode()); window.toggleDoNotDisturb = () => window.MYP.UI.doNotDisturb.toggle(); window.enableDoNotDisturb = (duration) => window.MYP.UI.doNotDisturb.enable(duration); window.disableDoNotDisturb = () => window.MYP.UI.doNotDisturb.disable(); // Event-Listener für data-Attribute document.addEventListener('click', (e) => { // Modal-Trigger if (e.target.matches('[data-modal-open]')) { const modalId = e.target.getAttribute('data-modal-open'); window.MYP.UI.modal.open(modalId); } // Modal-Close if (e.target.matches('[data-modal-close]')) { const modalId = e.target.getAttribute('data-modal-close'); window.MYP.UI.modal.close(modalId); } // Dropdown-Toggle if (e.target.closest('[data-dropdown-toggle]')) { const dropdownId = e.target.closest('[data-dropdown-toggle]').getAttribute('data-dropdown-toggle'); window.MYP.UI.dropdown.toggle(dropdownId); } // Do Not Disturb Toggle if (e.target.matches('[data-dnd-toggle]') || e.target.closest('[data-dnd-toggle]')) { window.MYP.UI.doNotDisturb.toggle(); } }); // Keyboard Shortcuts document.addEventListener('keydown', (e) => { // Ctrl/Cmd + Shift + D für Do Not Disturb Toggle if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === 'd') { e.preventDefault(); window.MYP.UI.doNotDisturb.toggle(); } // Escape für alle Modals schließen if (e.key === 'Escape') { window.MYP.UI.modal.closeTopModal(); } }); console.log('✅ MYP UI Components erfolgreich initialisiert - Erweiterte Benutzeroberfläche mit Glassmorphism und Do Not Disturb bereit'); // Test der glasigen Flash Messages (nur im Development) if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { // Warten auf vollständige Initialisierung, dann Test-Messages anzeigen setTimeout(() => { console.log('🧪 Teste glasige Flash Messages...'); showFlashMessage('Glassmorphism Flash Messages sind aktiv! ✨', 'success', 7000); setTimeout(() => { showFlashMessage('Do Not Disturb System ist bereit. Probieren Sie es im Footer aus! 🔕', 'info', 7000); }, 1000); setTimeout(() => { showFlashMessage('Warnung: Dies ist eine Test-Nachricht für das glasige Design.', 'warning', 7000); }, 2000); }, 2000); } }); // Globale Variablen für erweiterte Flash Messages window.showFlashMessage = showFlashMessage; window.closeFlashMessage = closeFlashMessage; // Globale Variable für Toast-Funktion window.showToast = showToast; // Globale Variable für API-Aufrufe window.apiCall = apiCall; })();