1865 lines
73 KiB
JavaScript
1865 lines
73 KiB
JavaScript
/**
|
||
* 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 = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>';
|
||
}
|
||
|
||
// 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 = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>';
|
||
}
|
||
|
||
// 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 = `
|
||
<div class="dropdown-header">
|
||
<div class="avatar-large">
|
||
${this.getCurrentUserInitial()}
|
||
</div>
|
||
<div class="ml-3">
|
||
<p class="text-sm font-medium text-slate-900 dark:text-white transition-colors duration-300">
|
||
${this.getCurrentUsername()}
|
||
</p>
|
||
<p class="text-xs text-slate-500 dark:text-slate-400 transition-colors duration-300">
|
||
${this.getCurrentUserEmail()}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="dropdown-divider"></div>
|
||
<a href="/profil" id="profile-link" class="dropdown-item" role="menuitem">
|
||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||
</svg>
|
||
<span>Mein Profil</span>
|
||
</a>
|
||
<a href="/einstellungen" id="settings-link" class="dropdown-item" role="menuitem">
|
||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||
</svg>
|
||
<span>Einstellungen</span>
|
||
</a>
|
||
<div class="dropdown-divider"></div>
|
||
<button type="button" data-action="logout" class="dropdown-item text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/10" role="menuitem">
|
||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path>
|
||
</svg>
|
||
<span>Abmelden</span>
|
||
</button>
|
||
`;
|
||
|
||
// 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': '<svg class="w-5 h-5 text-green-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path></svg>',
|
||
'error': '<svg class="w-5 h-5 text-red-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path></svg>',
|
||
'warning': '<svg class="w-5 h-5 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>',
|
||
'info': '<svg class="w-5 h-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>'
|
||
};
|
||
|
||
toast.className += ' ' + (typeClasses[type] || typeClasses.info);
|
||
|
||
toast.innerHTML = `
|
||
<div class="p-4">
|
||
<div class="flex items-start">
|
||
<div class="flex-shrink-0">
|
||
${iconHTML[type] || iconHTML.info}
|
||
</div>
|
||
<div class="ml-3 w-0 flex-1">
|
||
<p class="text-sm font-medium text-gray-900 dark:text-white">
|
||
${message}
|
||
</p>
|
||
</div>
|
||
<div class="ml-4 flex-shrink-0 flex">
|
||
<button onclick="window.MYP.UI.toast.hide('${id}')"
|
||
class="bg-white dark:bg-slate-800 rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||
<span class="sr-only">Schließen</span>
|
||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
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 = `
|
||
<div class="bg-white dark:bg-slate-800 rounded-lg p-6 flex items-center space-x-3 shadow-lg">
|
||
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-indigo-600"></div>
|
||
<span class="text-slate-900 dark:text-white font-medium">${message}</span>
|
||
</div>
|
||
`;
|
||
|
||
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 = '<div class="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div><span class="text-green-500 dark:text-green-400 font-medium transition-colors duration-300">Online</span>';
|
||
} else {
|
||
this.statusElement.innerHTML = '<div class="w-2 h-2 bg-red-400 rounded-full animate-pulse"></div><span class="text-red-500 dark:text-red-400 font-medium transition-colors duration-300">Offline</span>';
|
||
}
|
||
}
|
||
|
||
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<object>} 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 = `<svg class="w-5 h-5 mr-3" fill="currentColor" viewBox="0 0 20 20">
|
||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
||
</svg>`;
|
||
break;
|
||
case 'error':
|
||
icon = `<svg class="w-5 h-5 mr-3" fill="currentColor" viewBox="0 0 20 20">
|
||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
||
</svg>`;
|
||
break;
|
||
case 'warning':
|
||
icon = `<svg class="w-5 h-5 mr-3" fill="currentColor" viewBox="0 0 20 20">
|
||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
||
</svg>`;
|
||
break;
|
||
case 'info':
|
||
default:
|
||
icon = `<svg class="w-5 h-5 mr-3" fill="currentColor" viewBox="0 0 20 20">
|
||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
|
||
</svg>`;
|
||
}
|
||
|
||
// Inhalt der Flash Message
|
||
flashElement.innerHTML = `
|
||
<div class="flex items-center">
|
||
${icon}
|
||
<div class="flex-1">
|
||
<p class="font-medium text-sm">${message}</p>
|
||
</div>
|
||
<button class="flash-close-btn ml-4 text-current opacity-70 hover:opacity-100 transition-opacity duration-200"
|
||
onclick="closeFlashMessage('${messageId}')">
|
||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
`;
|
||
|
||
// Flash Message zum DOM hinzufügen
|
||
document.body.appendChild(flashElement);
|
||
|
||
// Flash Messages vertikal stapeln
|
||
repositionFlashMessages();
|
||
|
||
// 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)');
|
||
flashMessages.forEach((flash, index) => {
|
||
flash.style.top = `${16 + (index * 80)}px`; // 16px base + 80px pro Message
|
||
flash.style.right = '16px';
|
||
flash.style.zIndex = 50 - index; // Neueste Messages haben höheren z-index
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 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 = `
|
||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zM9 8a1 1 0 012 0v4a1 1 0 11-2 0V8z" clip-rule="evenodd"/>
|
||
</svg>
|
||
<span class="dnd-text">Nicht stören</span>
|
||
<span class="dnd-counter-badge ml-2 px-2 py-1 text-xs rounded-full bg-red-500 text-white hidden">0</span>
|
||
`;
|
||
|
||
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 = `
|
||
<div class="flex items-center">
|
||
<svg class="w-4 h-4 mr-2 opacity-50" fill="currentColor" viewBox="0 0 20 20">
|
||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zM9 8a1 1 0 012 0v4a1 1 0 11-2 0V8z" clip-rule="evenodd"/>
|
||
</svg>
|
||
<span class="opacity-70 text-xs">Nachricht unterdrückt</span>
|
||
</div>
|
||
`;
|
||
|
||
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() {
|
||
const modal = document.createElement('div');
|
||
modal.className = 'dnd-modal';
|
||
modal.innerHTML = `
|
||
<div class="dnd-modal-content">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">
|
||
🔕 Nicht stören
|
||
</h3>
|
||
<button class="dnd-close-btn text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200">
|
||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="space-y-4">
|
||
<!-- Schnell-Aktionen -->
|
||
<div class="grid grid-cols-2 gap-3">
|
||
<button class="dnd-quick-btn btn-primary" data-duration="30">
|
||
30 Min
|
||
</button>
|
||
<button class="dnd-quick-btn btn-primary" data-duration="60">
|
||
1 Stunde
|
||
</button>
|
||
<button class="dnd-quick-btn btn-primary" data-duration="480">
|
||
8 Stunden
|
||
</button>
|
||
<button class="dnd-quick-btn btn-primary" data-duration="0">
|
||
Dauerhaft
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Erweiterte Einstellungen -->
|
||
<div class="pt-4 border-t border-gray-200/30 dark:border-slate-700/30 space-y-3">
|
||
<label class="flex items-center space-x-3">
|
||
<input type="checkbox" class="dnd-setting" data-setting="allowCritical"
|
||
${this.settings.allowCritical ? 'checked' : ''}>
|
||
<span class="text-sm text-slate-700 dark:text-slate-300">
|
||
Kritische Fehler anzeigen
|
||
</span>
|
||
</label>
|
||
|
||
<label class="flex items-center space-x-3">
|
||
<input type="checkbox" class="dnd-setting" data-setting="allowErrorsOnly"
|
||
${this.settings.allowErrorsOnly ? 'checked' : ''}>
|
||
<span class="text-sm text-slate-700 dark:text-slate-300">
|
||
Nur Fehler anzeigen
|
||
</span>
|
||
</label>
|
||
</div>
|
||
|
||
<!-- Unterdrückte Nachrichten -->
|
||
${this.suppressedMessages.length > 0 ? `
|
||
<div class="pt-4 border-t border-gray-200/30 dark:border-slate-700/30">
|
||
<div class="flex items-center justify-between mb-3">
|
||
<h4 class="text-sm font-medium text-slate-900 dark:text-white">
|
||
Unterdrückte Nachrichten (${this.suppressedMessages.length})
|
||
</h4>
|
||
<button class="dnd-clear-btn text-xs text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200">
|
||
Alle löschen
|
||
</button>
|
||
</div>
|
||
<div class="max-h-32 overflow-y-auto space-y-2">
|
||
${this.suppressedMessages.slice(0, 5).map(msg => `
|
||
<div class="text-xs p-2 rounded bg-slate-100/50 dark:bg-slate-800/50">
|
||
<span class="font-medium text-${msg.type === 'error' ? 'red' : msg.type === 'warning' ? 'yellow' : msg.type === 'success' ? 'green' : 'blue'}-600 dark:text-${msg.type === 'error' ? 'red' : msg.type === 'warning' ? 'yellow' : msg.type === 'success' ? 'green' : 'blue'}-400">
|
||
${msg.type.toUpperCase()}:
|
||
</span>
|
||
<span class="text-slate-600 dark:text-slate-400">
|
||
${msg.message}
|
||
</span>
|
||
<div class="text-xs text-slate-400 dark:text-slate-500 mt-1">
|
||
${msg.timestamp.toLocaleTimeString()}
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
${this.suppressedMessages.length > 5 ? `
|
||
<div class="text-xs text-center text-slate-500 dark:text-slate-400 py-2">
|
||
... und ${this.suppressedMessages.length - 5} weitere
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
|
||
<!-- Aktions-Buttons -->
|
||
<div class="flex space-x-3 pt-4">
|
||
${this.isActive ? `
|
||
<button class="dnd-disable-btn btn-secondary flex-1">
|
||
Deaktivieren
|
||
</button>
|
||
` : ''}
|
||
<button class="dnd-close-btn btn-primary flex-1">
|
||
Schließen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// Event Listeners
|
||
modal.addEventListener('click', (e) => {
|
||
if (e.target === modal || e.target.classList.contains('dnd-close-btn')) {
|
||
modal.remove();
|
||
}
|
||
|
||
if (e.target.classList.contains('dnd-quick-btn')) {
|
||
const duration = parseInt(e.target.dataset.duration);
|
||
if (duration === 0) {
|
||
this.enable();
|
||
} else {
|
||
this.enable(duration);
|
||
}
|
||
modal.remove();
|
||
}
|
||
|
||
if (e.target.classList.contains('dnd-disable-btn')) {
|
||
this.disable();
|
||
modal.remove();
|
||
}
|
||
|
||
if (e.target.classList.contains('dnd-clear-btn')) {
|
||
this.clearSuppressedMessages();
|
||
modal.remove();
|
||
this.showSettings(); // Modal neu laden
|
||
}
|
||
|
||
if (e.target.classList.contains('dnd-setting')) {
|
||
const setting = e.target.dataset.setting;
|
||
this.settings[setting] = e.target.checked;
|
||
this.saveSettings();
|
||
}
|
||
});
|
||
|
||
document.body.appendChild(modal);
|
||
}
|
||
|
||
/**
|
||
* 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');
|
||
});
|
||
|
||
// 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;
|
||
})();
|