🎉 Fix for JOBS_UNDEFINED and LOG_EXPORT issues, updated documentation 📚 in backend/docs.
This commit is contained in:
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Benachrichtigungssystem für die MYP 3D-Druck Platform
|
||||
* Verwaltet die Anzeige und Interaktion mit Benachrichtigungen
|
||||
* Modernes Benachrichtigungssystem für die MYP 3D-Druck Platform
|
||||
* Verwaltet einheitliche Glassmorphism-Benachrichtigungen aller Art
|
||||
*/
|
||||
|
||||
class NotificationManager {
|
||||
class ModernNotificationManager {
|
||||
constructor() {
|
||||
this.notificationToggle = document.getElementById('notificationToggle');
|
||||
this.notificationDropdown = document.getElementById('notificationDropdown');
|
||||
@ -13,11 +13,14 @@ class NotificationManager {
|
||||
|
||||
this.isOpen = false;
|
||||
this.notifications = [];
|
||||
this.activeToasts = new Map();
|
||||
this.toastCounter = 0;
|
||||
|
||||
// CSRF-Token aus Meta-Tag holen
|
||||
this.csrfToken = this.getCSRFToken();
|
||||
|
||||
this.init();
|
||||
this.setupGlobalNotificationSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -307,9 +310,358 @@ class NotificationManager {
|
||||
console.error('Fehler beim Markieren aller als gelesen:', error);
|
||||
}
|
||||
}
|
||||
|
||||
setupGlobalNotificationSystem() {
|
||||
// Globale Funktionen für einheitliche Benachrichtigungen
|
||||
window.showFlashMessage = this.showGlassToast.bind(this);
|
||||
window.showToast = this.showGlassToast.bind(this);
|
||||
window.showNotification = this.showGlassToast.bind(this);
|
||||
window.showSuccessMessage = (message) => this.showGlassToast(message, 'success');
|
||||
window.showErrorMessage = (message) => this.showGlassToast(message, 'error');
|
||||
window.showWarningMessage = (message) => this.showGlassToast(message, 'warning');
|
||||
window.showInfoMessage = (message) => this.showGlassToast(message, 'info');
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt eine moderne Glassmorphism-Toast-Benachrichtigung
|
||||
*/
|
||||
showGlassToast(message, type = 'info', duration = 5000, options = {}) {
|
||||
// DND-Check
|
||||
if (window.dndManager && window.dndManager.isEnabled) {
|
||||
window.dndManager.suppressNotification(message, type);
|
||||
return;
|
||||
}
|
||||
|
||||
const toastId = `toast-${++this.toastCounter}`;
|
||||
|
||||
// Toast-Element erstellen
|
||||
const toast = document.createElement('div');
|
||||
toast.id = toastId;
|
||||
toast.className = `glass-toast notification notification-${type} show`;
|
||||
|
||||
// Icon basierend auf Typ
|
||||
const iconMap = {
|
||||
success: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||
</svg>`,
|
||||
error: `<svg class="w-5 h-5" 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>`,
|
||||
warning: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"/>
|
||||
</svg>`,
|
||||
info: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>`
|
||||
};
|
||||
|
||||
toast.innerHTML = `
|
||||
<div class="flex items-center">
|
||||
<div class="notification-icon">
|
||||
${iconMap[type] || iconMap.info}
|
||||
</div>
|
||||
<div class="notification-content">
|
||||
${options.title ? `<div class="notification-title">${options.title}</div>` : ''}
|
||||
<div class="notification-message">${message}</div>
|
||||
</div>
|
||||
<button class="notification-close" onclick="modernNotificationManager.closeToast('${toastId}')">
|
||||
<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>
|
||||
`;
|
||||
|
||||
// Container für Toasts erstellen falls nicht vorhanden
|
||||
let container = document.getElementById('toast-container');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.id = 'toast-container';
|
||||
container.className = 'notifications-container';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
// Position berechnen basierend auf existierenden Toasts
|
||||
const existingToasts = container.children.length;
|
||||
toast.style.top = `${1 + existingToasts * 5}rem`;
|
||||
|
||||
container.appendChild(toast);
|
||||
this.activeToasts.set(toastId, toast);
|
||||
|
||||
// Hover-Effekt pausiert Auto-Close
|
||||
let isPaused = false;
|
||||
let timeoutId;
|
||||
|
||||
const startTimer = () => {
|
||||
if (!isPaused && duration > 0) {
|
||||
timeoutId = setTimeout(() => {
|
||||
this.closeToast(toastId);
|
||||
}, duration);
|
||||
}
|
||||
};
|
||||
|
||||
toast.addEventListener('mouseenter', () => {
|
||||
isPaused = true;
|
||||
clearTimeout(timeoutId);
|
||||
toast.style.transform = 'translateY(-2px) scale(1.02)';
|
||||
});
|
||||
|
||||
toast.addEventListener('mouseleave', () => {
|
||||
isPaused = false;
|
||||
toast.style.transform = 'translateY(0) scale(1)';
|
||||
startTimer();
|
||||
});
|
||||
|
||||
// Initial timer starten
|
||||
setTimeout(startTimer, 100);
|
||||
|
||||
// Sound-Effekt (optional)
|
||||
if (options.playSound !== false) {
|
||||
this.playNotificationSound(type);
|
||||
}
|
||||
|
||||
return toastId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schließt eine spezifische Toast-Benachrichtigung
|
||||
*/
|
||||
closeToast(toastId) {
|
||||
const toast = this.activeToasts.get(toastId);
|
||||
if (!toast) return;
|
||||
|
||||
toast.classList.add('hiding');
|
||||
|
||||
setTimeout(() => {
|
||||
if (toast.parentNode) {
|
||||
toast.parentNode.removeChild(toast);
|
||||
}
|
||||
this.activeToasts.delete(toastId);
|
||||
this.repositionToasts();
|
||||
}, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Repositioniert alle aktiven Toasts
|
||||
*/
|
||||
repositionToasts() {
|
||||
let index = 0;
|
||||
this.activeToasts.forEach((toast) => {
|
||||
if (toast.parentNode) {
|
||||
toast.style.top = `${1 + index * 5}rem`;
|
||||
index++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Spielt einen Benachrichtigungston ab
|
||||
*/
|
||||
playNotificationSound(type) {
|
||||
try {
|
||||
// Nur wenn AudioContext verfügbar ist
|
||||
if (typeof AudioContext !== 'undefined' || typeof webkitAudioContext !== 'undefined') {
|
||||
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
const oscillator = audioContext.createOscillator();
|
||||
const gainNode = audioContext.createGain();
|
||||
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
|
||||
// Verschiedene Töne für verschiedene Typen
|
||||
const frequencies = {
|
||||
success: [523.25, 659.25, 783.99], // C-E-G Akkord
|
||||
error: [440, 415.3], // A-Ab
|
||||
warning: [493.88, 523.25], // B-C
|
||||
info: [523.25] // C
|
||||
};
|
||||
|
||||
const freq = frequencies[type] || frequencies.info;
|
||||
|
||||
freq.forEach((f, i) => {
|
||||
setTimeout(() => {
|
||||
const osc = audioContext.createOscillator();
|
||||
const gain = audioContext.createGain();
|
||||
|
||||
osc.connect(gain);
|
||||
gain.connect(audioContext.destination);
|
||||
|
||||
osc.frequency.setValueAtTime(f, audioContext.currentTime);
|
||||
gain.gain.setValueAtTime(0.1, audioContext.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
|
||||
|
||||
osc.start(audioContext.currentTime);
|
||||
osc.stop(audioContext.currentTime + 0.1);
|
||||
}, i * 100);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// Silent fail - Audio nicht verfügbar
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt Browser-Benachrichtigung (falls berechtigt)
|
||||
*/
|
||||
showBrowserNotification(title, message, options = {}) {
|
||||
if ('Notification' in window) {
|
||||
if (Notification.permission === 'granted') {
|
||||
const notification = new Notification(title, {
|
||||
body: message,
|
||||
icon: options.icon || '/static/icons/static/icons/notification-icon.png',
|
||||
badge: options.badge || '/static/icons/static/icons/badge-icon.png',
|
||||
tag: options.tag || 'myp-notification',
|
||||
requireInteraction: options.requireInteraction || false,
|
||||
...options
|
||||
});
|
||||
|
||||
notification.onclick = options.onClick || (() => {
|
||||
window.focus();
|
||||
notification.close();
|
||||
});
|
||||
|
||||
return notification;
|
||||
} else if (Notification.permission === 'default') {
|
||||
Notification.requestPermission().then(permission => {
|
||||
if (permission === 'granted') {
|
||||
this.showBrowserNotification(title, message, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt eine Alert-Benachrichtigung mit Glassmorphism
|
||||
*/
|
||||
showAlert(message, type = 'info', options = {}) {
|
||||
const alertId = `alert-${Date.now()}`;
|
||||
const alert = document.createElement('div');
|
||||
alert.id = alertId;
|
||||
alert.className = `alert alert-${type}`;
|
||||
|
||||
alert.innerHTML = `
|
||||
<div class="flex items-start">
|
||||
<div class="notification-icon mr-3">
|
||||
${this.getIconForType(type)}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
${options.title ? `<h4 class="font-semibold mb-2">${options.title}</h4>` : ''}
|
||||
<p>${message}</p>
|
||||
</div>
|
||||
${options.dismissible !== false ? `
|
||||
<button class="ml-3 p-1 rounded-lg opacity-70 hover:opacity-100 transition-opacity" onclick="document.getElementById('${alertId}').remove()">
|
||||
<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>
|
||||
`;
|
||||
|
||||
// Alert in Container einfügen
|
||||
const container = options.container || document.querySelector('.flash-messages') || document.body;
|
||||
container.appendChild(alert);
|
||||
|
||||
// Auto-dismiss
|
||||
if (options.autoDismiss !== false) {
|
||||
setTimeout(() => {
|
||||
if (alert.parentNode) {
|
||||
alert.style.opacity = '0';
|
||||
alert.style.transform = 'translateY(-20px)';
|
||||
setTimeout(() => alert.remove(), 300);
|
||||
}
|
||||
}, options.duration || 7000);
|
||||
}
|
||||
|
||||
return alertId;
|
||||
}
|
||||
|
||||
getIconForType(type) {
|
||||
const icons = {
|
||||
success: `<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||
</svg>`,
|
||||
error: `<svg class="w-5 h-5 text-red-600" 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>`,
|
||||
warning: `<svg class="w-5 h-5 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"/>
|
||||
</svg>`,
|
||||
info: `<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>`
|
||||
};
|
||||
return icons[type] || icons.info;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisierung nach DOM-Load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new NotificationManager();
|
||||
});
|
||||
// Legacy NotificationManager für Rückwärtskompatibilität
|
||||
class NotificationManager extends ModernNotificationManager {
|
||||
constructor() {
|
||||
super();
|
||||
console.warn('NotificationManager ist deprecated. Verwenden Sie ModernNotificationManager.');
|
||||
}
|
||||
}
|
||||
|
||||
// Globale Instanz erstellen
|
||||
const modernNotificationManager = new ModernNotificationManager();
|
||||
|
||||
// Für Rückwärtskompatibilität
|
||||
if (typeof window !== 'undefined') {
|
||||
window.notificationManager = modernNotificationManager;
|
||||
window.modernNotificationManager = modernNotificationManager;
|
||||
}
|
||||
|
||||
// CSS für glassmorphe Toasts hinzufügen
|
||||
const toastStyles = `
|
||||
<style>
|
||||
.glass-toast {
|
||||
position: relative;
|
||||
transform: translateX(0) translateY(0) scale(1);
|
||||
animation: toast-slide-in 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.glass-toast:hover {
|
||||
transform: translateY(-2px) scale(1.02) !important;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
@keyframes toast-slide-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(100%) translateY(-20px) scale(0.9);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
transform: translateX(20px) translateY(-10px) scale(1.05);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0) translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.notification-item.unread {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(59, 130, 246, 0.08) 0%,
|
||||
rgba(147, 197, 253, 0.05) 100%);
|
||||
border-left: 3px solid rgb(59, 130, 246);
|
||||
}
|
||||
|
||||
.dark .notification-item.unread {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(59, 130, 246, 0.15) 0%,
|
||||
rgba(147, 197, 253, 0.08) 100%);
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
// Styles zur Seite hinzufügen
|
||||
if (typeof document !== 'undefined' && !document.getElementById('toast-styles')) {
|
||||
const styleElement = document.createElement('div');
|
||||
styleElement.id = 'toast-styles';
|
||||
styleElement.innerHTML = toastStyles;
|
||||
document.head.appendChild(styleElement);
|
||||
}
|
Reference in New Issue
Block a user