/** * 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; } } /** * Toast-Nachricht anzeigen * @param {string} message - Nachrichtentext * @param {string} type - Nachrichtentyp (success, error, info, warning) */ function showToast(message, type = 'info') { // Prüfen, ob Toast-Container existiert let toastContainer = document.getElementById('toast-container'); // Falls nicht, erstellen wir einen if (!toastContainer) { toastContainer = document.createElement('div'); toastContainer.id = 'toast-container'; toastContainer.className = 'fixed top-4 right-4 z-50 flex flex-col space-y-2'; document.body.appendChild(toastContainer); } // Toast-Element erstellen const toast = document.createElement('div'); toast.className = `flex items-center p-4 mb-4 text-sm rounded-lg shadow-lg transition-all transform translate-x-0 opacity-100 ${getToastTypeClass(type)}`; toast.innerHTML = `
${getToastIcon(type)}
${message}
`; // Toast zum Container hinzufügen toastContainer.appendChild(toast); // Schließen-Button-Event const closeButton = toast.querySelector('button'); closeButton.addEventListener('click', () => { dismissToast(toast); }); // Toast nach 5 Sekunden automatisch ausblenden setTimeout(() => { dismissToast(toast); }, 5000); } /** * Toast ausblenden und nach Animation entfernen * @param {HTMLElement} toast - Toast-Element */ function dismissToast(toast) { toast.classList.replace('translate-x-0', 'translate-x-full'); toast.classList.replace('opacity-100', 'opacity-0'); setTimeout(() => { toast.remove(); }, 300); } /** * CSS-Klassen für Toast-Typ * @param {string} type - Nachrichtentyp * @returns {string} CSS-Klassen */ function getToastTypeClass(type) { switch (type) { case 'success': return 'text-green-800 bg-green-50 dark:bg-green-900/30 dark:text-green-300'; case 'error': return 'text-red-800 bg-red-50 dark:bg-red-900/30 dark:text-red-300'; case 'warning': return 'text-yellow-800 bg-yellow-50 dark:bg-yellow-900/30 dark:text-yellow-300'; case 'info': default: return 'text-blue-800 bg-blue-50 dark:bg-blue-900/30 dark:text-blue-300'; } } /** * CSS-Klassen für Toast-Icon * @param {string} type - Nachrichtentyp * @returns {string} CSS-Klassen */ function getToastIconClass(type) { switch (type) { case 'success': return 'bg-green-100 text-green-500 dark:bg-green-800 dark:text-green-200 rounded-lg'; case 'error': return 'bg-red-100 text-red-500 dark:bg-red-800 dark:text-red-200 rounded-lg'; case 'warning': return 'bg-yellow-100 text-yellow-500 dark:bg-yellow-800 dark:text-yellow-200 rounded-lg'; case 'info': default: return 'bg-blue-100 text-blue-500 dark:bg-blue-800 dark:text-blue-200 rounded-lg'; } } /** * SVG-Icon für Toast-Typ * @param {string} type - Nachrichtentyp * @returns {string} SVG-Markup */ function getToastIcon(type) { switch (type) { case 'success': return ''; case 'error': return ''; case 'warning': return ''; case 'info': default: return ''; } } /** * Flash-Nachricht anzeigen * @param {string} message - Nachrichtentext * @param {string} type - Nachrichtentyp (success, error, info, warning) */ function showFlashMessage(message, type = 'info') { // Toast-Funktion verwenden, wenn verfügbar if (typeof showToast === 'function') { showToast(message, type); } else { // Fallback-Lösung, wenn Toast nicht verfügbar ist alert(message); } } /** * 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(); } } // 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(); // 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()); // 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); } }); console.log('✅ MYP UI Components erfolgreich initialisiert - Benutzeroberfläche bereit'); }); // Globale Variable für Toast-Funktion window.showToast = showToast; // Globale Variable für API-Aufrufe window.apiCall = apiCall; // Globale Variable für Flash-Nachrichten window.showFlashMessage = showFlashMessage; })();