📝 "🐛 Refactor backend files, improve documentation, and update UI components (#123)"

This commit is contained in:
2025-06-01 00:59:13 +02:00
parent 070f4a6165
commit 4dd3c4b1b1
13 changed files with 11286 additions and 142 deletions

View File

@@ -951,136 +951,103 @@
}
/**
* Toast-Nachricht anzeigen
* 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 showToast(message, type = 'info') {
// Prüfen, ob Toast-Container existiert
let toastContainer = document.getElementById('toast-container');
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);
// 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);
// 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>`;
}
// 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)}
// 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>
<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);
// Flash Message zum DOM hinzufügen
document.body.appendChild(flashElement);
// Schließen-Button-Event
const closeButton = toast.querySelector('button');
closeButton.addEventListener('click', () => {
dismissToast(toast);
// 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
});
// 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);
}
}
/**
@@ -1224,6 +1191,581 @@
}
}
/**
* 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
@@ -1253,11 +1795,20 @@
// 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) => {
@@ -1278,17 +1829,37 @@
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();
}
});
console.log('✅ MYP UI Components erfolgreich initialisiert - Benutzeroberfläche bereit');
// 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;
// Globale Variable für Flash-Nachrichten
window.showFlashMessage = showFlashMessage;
})();