1294 lines
51 KiB
JavaScript
1294 lines
51 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;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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 = `
|
||
<div class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 ${getToastIconClass(type)}">
|
||
${getToastIcon(type)}
|
||
</div>
|
||
<div class="ml-3 text-sm font-normal">${message}</div>
|
||
<button type="button" class="ml-auto -mx-1.5 -my-1.5 rounded-lg p-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 focus:ring-2 focus:ring-gray-300 dark:focus:ring-gray-600 inline-flex h-8 w-8" aria-label="Schließen">
|
||
<span class="sr-only">Schließen</span>
|
||
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||
<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>
|
||
`;
|
||
|
||
// 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 '<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path></svg>';
|
||
case 'error':
|
||
return '<svg aria-hidden="true" class="w-5 h-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>';
|
||
case 'warning':
|
||
return '<svg aria-hidden="true" class="w-5 h-5" 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>';
|
||
case 'info':
|
||
default:
|
||
return '<svg aria-hidden="true" class="w-5 h-5" 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>';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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;
|
||
})();
|