📝 "🐛 Refactor backend files, improve documentation, and update UI components (#123)"
This commit is contained in:
@@ -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;
|
||||
})();
|
Reference in New Issue
Block a user