🎉 Fix for JOBS_UNDEFINED and LOG_EXPORT issues, updated documentation 📚 in backend/docs.

This commit is contained in:
2025-06-01 04:04:34 +02:00
parent 45d8d46556
commit 5ee854cbc6
43 changed files with 3053 additions and 118 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -181,7 +181,7 @@ window.refreshStats = async function() {
};
/**
* Jobs-Refresh-Funktion
* Jobs-Refresh-Funktion - VERBESSERT mit umfassenden Null-Checks
*/
window.refreshJobs = async function() {
const refreshButton = document.getElementById('refresh-button');
@@ -194,14 +194,27 @@ window.refreshJobs = async function() {
}
try {
// Jobs-Daten neu laden mit verbesserter Fehlerbehandlung
if (typeof window.jobManager !== 'undefined' && window.jobManager && window.jobManager.loadJobs) {
console.log('🔄 Starte Jobs-Refresh...');
// Mehrstufige Manager-Prüfung mit erweiterten Sicherheitschecks
let refreshSuccess = false;
if (typeof window.jobManager !== 'undefined' &&
window.jobManager &&
typeof window.jobManager.loadJobs === 'function') {
console.log('📝 Verwende window.jobManager.loadJobs()');
await window.jobManager.loadJobs();
} else if (typeof jobManager !== 'undefined' && jobManager && jobManager.loadJobs) {
refreshSuccess = true;
} else if (typeof jobManager !== 'undefined' &&
jobManager &&
typeof jobManager.loadJobs === 'function') {
console.log('📝 Verwende lokalen jobManager.loadJobs()');
await jobManager.loadJobs();
refreshSuccess = true;
} else {
// Fallback: API-Aufruf direkt
// Fallback: Direkter API-Aufruf mit verbesserter Fehlerbehandlung
console.log('📝 JobManager nicht verfügbar - verwende direkten API-Aufruf');
const response = await fetch('/api/jobs', {
headers: {
'Content-Type': 'application/json',
@@ -214,28 +227,128 @@ window.refreshJobs = async function() {
}
const data = await response.json();
console.log('📝 Jobs erfolgreich über API geladen:', data);
console.log('📝 API-Response erhalten:', data);
// Wenn JobsContainer vorhanden ist, versuche die Jobs zu rendern
const jobsContainer = document.querySelector('.jobs-container, #jobs-container, .job-grid');
if (jobsContainer && data.jobs) {
// Einfache Jobs-Darstellung als Fallback
jobsContainer.innerHTML = data.jobs.map(job => `
<div class="job-card p-4 border rounded-lg">
<h3 class="font-semibold">${job.filename || 'Unbekannter Job'}</h3>
<p class="text-sm text-gray-600">Status: ${job.status || 'Unbekannt'}</p>
</div>
`).join('');
// VERBESSERTE Datenvalidierung
if (!data || typeof data !== 'object') {
throw new Error('Ungültige API-Response: Keine Daten erhalten');
}
// Sichere Jobs-Extraktion
let jobs = [];
if (Array.isArray(data.jobs)) {
jobs = data.jobs;
} else if (Array.isArray(data)) {
jobs = data;
} else if (data.success && Array.isArray(data.data)) {
jobs = data.data;
} else {
console.warn('⚠️ Keine Jobs-Array in API-Response gefunden:', data);
jobs = [];
}
console.log(`📝 ${jobs.length} Jobs aus API extrahiert:`, jobs);
// Container-Updates mit Fallback-Rendering
const jobsContainers = [
'.jobs-container',
'#jobs-container',
'.job-grid',
'#jobs-list',
'#jobs-grid'
];
let containerFound = false;
for (const selector of jobsContainers) {
const container = document.querySelector(selector);
if (container) {
containerFound = true;
console.log(`📝 Container gefunden: ${selector}`);
if (jobs.length === 0) {
container.innerHTML = `
<div class="text-center py-12">
<div class="text-gray-400 dark:text-gray-600 text-6xl mb-4">📭</div>
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">Keine Jobs vorhanden</h3>
<p class="text-gray-500 dark:text-gray-400">Es wurden noch keine Druckaufträge erstellt.</p>
</div>
`;
} else {
// Sichere Job-Darstellung mit Null-Checks
const jobCards = jobs.map(job => {
if (!job || typeof job !== 'object') {
console.warn('⚠️ Ungültiges Job-Objekt übersprungen:', job);
return '';
}
return `
<div class="job-card p-4 border rounded-lg bg-white dark:bg-slate-800 shadow-sm hover:shadow-md transition-shadow">
<h3 class="font-semibold text-gray-900 dark:text-white mb-2">
${job.filename || job.title || job.name || 'Unbekannter Job'}
</h3>
<div class="text-sm text-gray-600 dark:text-gray-400 space-y-1">
<p><span class="font-medium">ID:</span> ${job.id || 'N/A'}</p>
<p><span class="font-medium">Status:</span> ${job.status || 'Unbekannt'}</p>
${job.printer_name ? `<p><span class="font-medium">Drucker:</span> ${job.printer_name}</p>` : ''}
${job.created_at ? `<p><span class="font-medium">Erstellt:</span> ${new Date(job.created_at).toLocaleDateString('de-DE')}</p>` : ''}
</div>
</div>
`;
}).filter(card => card !== '').join('');
container.innerHTML = jobCards || `
<div class="text-center py-8">
<p class="text-gray-500 dark:text-gray-400">Keine gültigen Jobs zum Anzeigen</p>
</div>
`;
}
break;
}
}
if (!containerFound) {
console.warn('⚠️ Kein Jobs-Container gefunden. Verfügbare Container:', jobsContainers);
}
refreshSuccess = true;
}
if (refreshSuccess) {
showToast('✅ Druckaufträge erfolgreich aktualisiert', 'success');
}
showToast('✅ Druckaufträge erfolgreich aktualisiert', 'success');
} catch (error) {
console.error('Jobs-Refresh Fehler:', error);
const errorMessage = error.message === 'undefined' ?
'Jobs-Manager nicht verfügbar' :
error.message || 'Unbekannter Fehler';
showToast(`❌ Fehler beim Laden der Jobs: ${errorMessage}`, 'error');
console.error('Jobs-Refresh Fehler:', error);
// Intelligente Fehlermeldung basierend auf dem Fehlertyp
let errorMessage;
if (error.message.includes('undefined')) {
errorMessage = 'Jobs-Daten nicht verfügbar';
} else if (error.message.includes('fetch')) {
errorMessage = 'Netzwerkfehler beim Laden der Jobs';
} else if (error.message.includes('API')) {
errorMessage = 'Server-Fehler beim Laden der Jobs';
} else {
errorMessage = error.message || 'Unbekannter Fehler beim Laden der Jobs';
}
showToast(`❌ Fehler: ${errorMessage}`, 'error');
// Fallback-Container mit Fehleranzeige
const container = document.querySelector('.jobs-container, #jobs-container, .job-grid, #jobs-list');
if (container) {
container.innerHTML = `
<div class="text-center py-12">
<div class="text-red-400 dark:text-red-600 text-6xl mb-4">⚠️</div>
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">Fehler beim Laden</h3>
<p class="text-gray-500 dark:text-gray-400 mb-4">${errorMessage}</p>
<button onclick="refreshJobs()" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
Erneut versuchen
</button>
</div>
`;
}
} finally {
if (refreshButton) {
refreshButton.disabled = false;

View File

@@ -382,6 +382,12 @@
return;
}
// VERBESSERTE SICHERHEITSCHECKS
if (!Array.isArray(this.jobs)) {
console.warn('⚠️ this.jobs ist kein Array:', this.jobs);
this.jobs = [];
}
if (this.jobs.length === 0) {
jobsList.innerHTML = `
<div class="text-center py-12">
@@ -393,10 +399,32 @@
return;
}
const jobsHTML = this.jobs.map(job => this.renderJobCard(job)).join('');
jobsList.innerHTML = jobsHTML;
console.log(`📋 ${this.jobs.length} Jobs gerendert`);
try {
const jobsHTML = this.jobs.map(job => {
// Sicherstellen, dass job ein gültiges Objekt ist
if (!job || typeof job !== 'object') {
console.warn('⚠️ Ungültiges Job-Objekt:', job);
return '';
}
return this.renderJobCard(job);
}).filter(html => html !== '').join('');
jobsList.innerHTML = jobsHTML;
console.log(`📋 ${this.jobs.length} Jobs gerendert`);
} catch (error) {
console.error('❌ Fehler beim Rendern der Jobs:', error);
jobsList.innerHTML = `
<div class="text-center py-12">
<div class="text-red-400 dark:text-red-600 text-6xl mb-4">⚠️</div>
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">Fehler beim Laden</h3>
<p class="text-gray-500 dark:text-gray-400">Es gab einen Fehler beim Darstellen der Jobs.</p>
<button onclick="window.jobManager.loadJobs()" class="mt-4 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
Erneut versuchen
</button>
</div>
`;
}
}
/**

View File

@@ -0,0 +1,307 @@
/**
* Jobs Safety Fix - Mercedes-Benz MYP Platform
* Umfassende Lösung für "jobs undefined" Probleme
* Version: 1.0.0
*/
(function() {
'use strict';
console.log('🛡️ Jobs Safety Fix wird geladen...');
/**
* Globale Jobs-Variable sicher initialisieren
*/
if (typeof window.jobsData === 'undefined') {
window.jobsData = [];
}
if (typeof window.filteredJobs === 'undefined') {
window.filteredJobs = [];
}
/**
* Sichere Jobs-Funktionen
*/
window.safeJobsOperations = {
/**
* Jobs sicher abrufen
*/
getJobs: function() {
if (window.jobManager && Array.isArray(window.jobManager.jobs)) {
return window.jobManager.jobs;
}
if (Array.isArray(window.jobsData)) {
return window.jobsData;
}
return [];
},
/**
* Gefilterte Jobs sicher abrufen
*/
getFilteredJobs: function() {
if (Array.isArray(window.filteredJobs)) {
return window.filteredJobs;
}
return this.getJobs();
},
/**
* Jobs sicher setzen
*/
setJobs: function(jobs) {
if (!Array.isArray(jobs)) {
console.warn('⚠️ Jobs ist kein Array, konvertiere zu leerem Array');
jobs = [];
}
if (window.jobManager) {
window.jobManager.jobs = jobs;
}
window.jobsData = jobs;
console.log(`${jobs.length} Jobs sicher gesetzt`);
},
/**
* Job sicher finden
*/
findJob: function(jobId) {
const jobs = this.getJobs();
return jobs.find(job => job && job.id && job.id.toString() === jobId.toString()) || null;
},
/**
* Jobs sicher filtern
*/
filterJobs: function(filterFn) {
const jobs = this.getJobs();
if (typeof filterFn !== 'function') {
return jobs;
}
try {
return jobs.filter(job => {
if (!job || typeof job !== 'object') {
return false;
}
return filterFn(job);
});
} catch (error) {
console.error('❌ Fehler beim Filtern der Jobs:', error);
return jobs;
}
}
};
/**
* JobManager Sicherheitsprüfungen
*/
function ensureJobManagerSafety() {
if (typeof window.jobManager !== 'undefined' && window.jobManager) {
// Sicherstellen, dass jobs ein Array ist
if (!Array.isArray(window.jobManager.jobs)) {
console.warn('⚠️ JobManager.jobs ist kein Array, korrigiere...');
window.jobManager.jobs = [];
}
// Originale loadJobs Methode wrappen
if (window.jobManager.loadJobs && typeof window.jobManager.loadJobs === 'function') {
const originalLoadJobs = window.jobManager.loadJobs;
window.jobManager.loadJobs = async function(...args) {
try {
await originalLoadJobs.apply(this, args);
// Nach dem Laden sicherstellen, dass jobs ein Array ist
if (!Array.isArray(this.jobs)) {
this.jobs = [];
}
} catch (error) {
console.error('❌ Fehler beim sicheren Laden der Jobs:', error);
this.jobs = [];
throw error;
}
};
}
// Originale renderJobs Methode wrappen
if (window.jobManager.renderJobs && typeof window.jobManager.renderJobs === 'function') {
const originalRenderJobs = window.jobManager.renderJobs;
window.jobManager.renderJobs = function(...args) {
try {
// Sicherstellen, dass jobs ein Array ist vor dem Rendern
if (!Array.isArray(this.jobs)) {
console.warn('⚠️ Jobs ist kein Array vor dem Rendern, korrigiere...');
this.jobs = [];
}
return originalRenderJobs.apply(this, args);
} catch (error) {
console.error('❌ Fehler beim sicheren Rendern der Jobs:', error);
// Fallback-Rendering
const jobsList = document.getElementById('jobs-list');
if (jobsList) {
jobsList.innerHTML = `
<div class="text-center py-12">
<div class="text-red-400 dark:text-red-600 text-6xl mb-4">⚠️</div>
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">Renderingfehler</h3>
<p class="text-gray-500 dark:text-gray-400 mb-4">Jobs konnten nicht angezeigt werden</p>
<button onclick="window.jobManager.loadJobs()" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
Erneut laden
</button>
</div>
`;
}
}
};
}
}
}
/**
* Globale Variablen-Überwachung
*/
function setupGlobalVariableWatching() {
// jobsData überwachen
let _jobsData = [];
Object.defineProperty(window, 'jobsData', {
get: function() {
return _jobsData;
},
set: function(value) {
if (!Array.isArray(value)) {
console.warn('⚠️ Versuche jobsData mit Non-Array zu setzen:', value);
_jobsData = [];
} else {
_jobsData = value;
}
},
enumerable: true,
configurable: true
});
// filteredJobs überwachen
let _filteredJobs = [];
Object.defineProperty(window, 'filteredJobs', {
get: function() {
return _filteredJobs;
},
set: function(value) {
if (!Array.isArray(value)) {
console.warn('⚠️ Versuche filteredJobs mit Non-Array zu setzen:', value);
_filteredJobs = [];
} else {
_filteredJobs = value;
}
},
enumerable: true,
configurable: true
});
}
/**
* API-Response Validator
*/
window.validateJobsResponse = function(data) {
if (!data || typeof data !== 'object') {
console.warn('⚠️ Ungültige API-Response:', data);
return { jobs: [], total: 0 };
}
let jobs = [];
// Verschiedene Response-Formate unterstützen
if (Array.isArray(data.jobs)) {
jobs = data.jobs;
} else if (Array.isArray(data.data)) {
jobs = data.data;
} else if (Array.isArray(data)) {
jobs = data;
} else if (data.success && Array.isArray(data.jobs)) {
jobs = data.jobs;
}
// Jobs validieren
jobs = jobs.filter(job => {
if (!job || typeof job !== 'object') {
console.warn('⚠️ Ungültiges Job-Objekt gefiltert:', job);
return false;
}
if (!job.id) {
console.warn('⚠️ Job ohne ID gefiltert:', job);
return false;
}
return true;
});
console.log(`${jobs.length} Jobs validiert`);
return {
jobs: jobs,
total: jobs.length,
current_page: data.current_page || 1,
total_pages: data.total_pages || 1
};
};
/**
* Fehlerbehandlung für undefined Jobs
*/
window.addEventListener('error', function(e) {
if (e.message && e.message.includes('jobs') && e.message.includes('undefined')) {
console.log('🛡️ Jobs undefined Fehler abgefangen:', e.message);
// Versuche Jobs zu reparieren
window.safeJobsOperations.setJobs([]);
e.preventDefault();
return false;
}
});
/**
* Promise rejection handler für Jobs
*/
window.addEventListener('unhandledrejection', function(e) {
if (e.reason && e.reason.message && e.reason.message.includes('jobs')) {
console.log('🛡️ Jobs Promise rejection abgefangen:', e.reason);
// Versuche Jobs zu reparieren
window.safeJobsOperations.setJobs([]);
e.preventDefault();
}
});
/**
* DOM bereit - Setup starten
*/
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
setupGlobalVariableWatching();
ensureJobManagerSafety();
});
} else {
setupGlobalVariableWatching();
ensureJobManagerSafety();
}
/**
* Periodische Sicherheitsprüfung
*/
setInterval(function() {
ensureJobManagerSafety();
// Prüfe ob globale Jobs-Variablen noch Arrays sind
if (!Array.isArray(window.jobsData)) {
console.warn('⚠️ jobsData ist kein Array mehr, repariere...');
window.jobsData = [];
}
if (!Array.isArray(window.filteredJobs)) {
console.warn('⚠️ filteredJobs ist kein Array mehr, repariere...');
window.filteredJobs = [];
}
}, 10000); // Alle 10 Sekunden
console.log('✅ Jobs Safety Fix erfolgreich geladen');
})();

View File

@@ -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);
}