diff --git a/backend/.claude/settings.local.json b/backend/.claude/settings.local.json index 616dc09f..97e55bbc 100644 --- a/backend/.claude/settings.local.json +++ b/backend/.claude/settings.local.json @@ -6,7 +6,8 @@ "Bash(for file in animations-optimized.css glassmorphism.css components.css professional-theme.css)", "Bash(do echo \"=== $file ===\")", "Bash(echo)", - "Bash(done)" + "Bash(done)", + "Bash(npm run build:css:*)" ], "deny": [] } diff --git a/backend/static/css/core-utilities.css b/backend/static/css/core-utilities.css new file mode 100644 index 00000000..f1b25a03 --- /dev/null +++ b/backend/static/css/core-utilities.css @@ -0,0 +1,102 @@ +/* Core Utilities CSS - Notification System Styles */ + +/* ===== NOTIFICATION CONTAINER ===== */ +#myp-notifications { + pointer-events: none; + max-width: 400px; +} + +#myp-notifications > * { + pointer-events: auto; +} + +/* ===== BASE NOTIFICATION STYLES ===== */ +.notification { + min-width: 300px; + max-width: 100%; + word-wrap: break-word; + font-size: 0.875rem; + line-height: 1.25rem; +} + +/* Glassmorphism effect for notifications */ +.notification.glass-navbar { + background: rgba(255, 255, 255, 0.85); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.3); +} + +.dark .notification.glass-navbar { + background: rgba(15, 23, 42, 0.85); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +/* ===== NOTIFICATION TYPES ===== */ +.notification-success { + border-left: 4px solid #10b981; +} + +.notification-error { + border-left: 4px solid #ef4444; +} + +.notification-warning { + border-left: 4px solid #f59e0b; +} + +.notification-info { + border-left: 4px solid #3b82f6; +} + +/* ===== NOTIFICATION ANIMATIONS ===== */ +.notification { + transition: transform 0.3s ease-out, opacity 0.3s ease-out; +} + +.notification.translate-x-full { + transform: translateX(100%); + opacity: 0; +} + +/* ===== CLOSE BUTTON ===== */ +.notification button { + font-size: 1.5rem; + line-height: 1; + background: none; + border: none; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.2s; +} + +.notification button:hover { + opacity: 1; +} + +/* ===== RESPONSIVE DESIGN ===== */ +@media (max-width: 640px) { + #myp-notifications { + left: 1rem; + right: 1rem; + max-width: none; + } + + .notification { + min-width: auto; + } +} + +/* ===== ACCESSIBILITY ===== */ +@media (prefers-reduced-motion: reduce) { + .notification { + transition: none; + } +} + +/* ===== PRINT STYLES ===== */ +@media print { + #myp-notifications { + display: none; + } +} \ No newline at end of file diff --git a/backend/static/js/core-utilities-optimized.js b/backend/static/js/core-utilities-optimized.js new file mode 100644 index 00000000..fbe263fa --- /dev/null +++ b/backend/static/js/core-utilities-optimized.js @@ -0,0 +1,468 @@ +/** + * MYP Platform Core Utilities - Optimized Version + * Consolidates common functionality from multiple files + * Reduces redundancy and improves performance + */ + +(function(window) { + 'use strict'; + + // Single instance pattern for core utilities + const MYPCore = { + // Version for cache busting + version: '1.0.0', + + // CSRF Token Management (replaces 22+ different implementations) + csrf: { + _token: null, + + getToken() { + if (!this._token) { + // Try multiple sources in order of preference + this._token = + document.querySelector('meta[name="csrf-token"]')?.content || + document.querySelector('input[name="csrf_token"]')?.value || + this._getFromCookie('csrf_token') || + ''; + } + return this._token; + }, + + _getFromCookie(name) { + const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); + return match ? match[2] : null; + }, + + clearCache() { + this._token = null; + } + }, + + // DOM Utilities with caching + dom: { + _cache: new Map(), + + // Cached querySelector + get(selector, parent = document) { + const key = `${parent === document ? 'doc' : 'el'}_${selector}`; + if (!this._cache.has(key)) { + this._cache.set(key, parent.querySelector(selector)); + } + return this._cache.get(key); + }, + + // Cached querySelectorAll + getAll(selector, parent = document) { + return parent.querySelectorAll(selector); + }, + + // Efficient HTML escaping + escapeHtml(text) { + const div = this._escapeDiv || (this._escapeDiv = document.createElement('div')); + div.textContent = text; + return div.innerHTML; + }, + + // Clear cache when DOM changes + clearCache() { + this._cache.clear(); + }, + + // Efficient element creation + create(tag, attrs = {}, children = []) { + const el = document.createElement(tag); + Object.entries(attrs).forEach(([key, value]) => { + if (key === 'class') { + el.className = value; + } else if (key === 'style' && typeof value === 'object') { + Object.assign(el.style, value); + } else if (key.startsWith('data-')) { + el.dataset[key.slice(5)] = value; + } else { + el.setAttribute(key, value); + } + }); + children.forEach(child => { + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + el.appendChild(child); + } + }); + return el; + } + }, + + // Unified Notification System (replaces 3 separate systems) + notify: { + _container: null, + _queue: [], + _activeToasts: new Map(), + _toastId: 0, + + show(message, type = 'info', duration = 5000, options = {}) { + // Check for DND mode + if (window.dndManager?.isEnabled && !options.force) { + console.log(`[DND] Suppressed ${type} notification:`, message); + return null; + } + + const id = `toast-${++this._toastId}`; + const toast = this._createToast(id, message, type, duration, options); + + this._ensureContainer(); + this._container.appendChild(toast); + + // Animate in + requestAnimationFrame(() => { + toast.classList.add('show'); + }); + + // Auto remove + if (duration > 0 && !options.persistent) { + const timeout = setTimeout(() => this.close(id), duration); + this._activeToasts.set(id, { element: toast, timeout }); + } + + return id; + }, + + _createToast(id, message, type, duration, options) { + const toast = MYPCore.dom.create('div', { + id, + class: `myp-toast myp-toast-${type}`, + role: 'alert', + 'aria-live': 'polite' + }); + + const icon = this._getIcon(type); + const content = MYPCore.dom.create('div', { class: 'toast-content' }, [ + MYPCore.dom.create('span', { class: 'toast-icon' }, [icon]), + MYPCore.dom.create('span', { class: 'toast-message' }, [message]) + ]); + + const closeBtn = MYPCore.dom.create('button', { + class: 'toast-close', + 'aria-label': 'Close notification', + onclick: () => this.close(id) + }, ['×']); + + toast.appendChild(content); + toast.appendChild(closeBtn); + + if (duration > 0 && !options.persistent) { + const progress = MYPCore.dom.create('div', { class: 'toast-progress' }); + const bar = MYPCore.dom.create('div', { + class: 'toast-progress-bar', + style: { animationDuration: `${duration}ms` } + }); + progress.appendChild(bar); + toast.appendChild(progress); + } + + return toast; + }, + + _getIcon(type) { + const icons = { + success: '✓', + error: '✗', + warning: '⚠', + info: 'ℹ' + }; + return icons[type] || icons.info; + }, + + _ensureContainer() { + if (!this._container) { + this._container = MYPCore.dom.get('#myp-toast-container') || + MYPCore.dom.create('div', { id: 'myp-toast-container', class: 'myp-toast-container' }); + + if (!this._container.parentNode) { + document.body.appendChild(this._container); + } + } + }, + + close(id) { + const toast = this._activeToasts.get(id); + if (toast) { + clearTimeout(toast.timeout); + toast.element.classList.remove('show'); + setTimeout(() => { + toast.element.remove(); + this._activeToasts.delete(id); + }, 300); + } + }, + + closeAll() { + this._activeToasts.forEach((_, id) => this.close(id)); + } + }, + + // API Request Utilities + api: { + _cache: new Map(), + _pendingRequests: new Map(), + + async request(url, options = {}) { + const config = { + ...options, + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': MYPCore.csrf.getToken(), + ...options.headers + } + }; + + // Check cache for GET requests + if (options.method === 'GET' || !options.method) { + const cacheKey = this._getCacheKey(url, options); + const cached = this._cache.get(cacheKey); + + if (cached && !options.noCache) { + const age = Date.now() - cached.timestamp; + const maxAge = options.cacheTime || 300000; // 5 minutes default + + if (age < maxAge) { + return cached.data; + } + } + + // Prevent duplicate requests + if (this._pendingRequests.has(cacheKey)) { + return this._pendingRequests.get(cacheKey); + } + } + + // Make request + const requestPromise = fetch(url, config) + .then(async response => { + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + + // Cache successful GET requests + if (config.method === 'GET' || !config.method) { + const cacheKey = this._getCacheKey(url, options); + this._cache.set(cacheKey, { + data, + timestamp: Date.now() + }); + this._pendingRequests.delete(cacheKey); + } + + return data; + }) + .catch(error => { + if (config.method === 'GET' || !config.method) { + this._pendingRequests.delete(this._getCacheKey(url, options)); + } + throw error; + }); + + if (config.method === 'GET' || !config.method) { + this._pendingRequests.set(this._getCacheKey(url, options), requestPromise); + } + + return requestPromise; + }, + + _getCacheKey(url, options) { + return `${url}_${JSON.stringify(options.params || {})}`; + }, + + clearCache(pattern) { + if (pattern) { + for (const [key] of this._cache) { + if (key.includes(pattern)) { + this._cache.delete(key); + } + } + } else { + this._cache.clear(); + } + } + }, + + // Time/Date Utilities + time: { + formatAgo(timestamp) { + const date = timestamp instanceof Date ? timestamp : new Date(timestamp); + const now = new Date(); + const seconds = Math.floor((now - date) / 1000); + + const intervals = [ + { label: 'Jahr', seconds: 31536000, plural: 'Jahre' }, + { label: 'Monat', seconds: 2592000, plural: 'Monate' }, + { label: 'Woche', seconds: 604800, plural: 'Wochen' }, + { label: 'Tag', seconds: 86400, plural: 'Tage' }, + { label: 'Stunde', seconds: 3600, plural: 'Stunden' }, + { label: 'Minute', seconds: 60, plural: 'Minuten' } + ]; + + for (const interval of intervals) { + const count = Math.floor(seconds / interval.seconds); + if (count >= 1) { + const label = count === 1 ? interval.label : interval.plural; + return `vor ${count} ${label}`; + } + } + + return 'Gerade eben'; + }, + + formatDuration(seconds) { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const secs = seconds % 60; + + const parts = []; + if (hours > 0) parts.push(`${hours}h`); + if (minutes > 0) parts.push(`${minutes}m`); + if (secs > 0 || parts.length === 0) parts.push(`${secs}s`); + + return parts.join(' '); + } + }, + + // Performance Utilities + perf: { + debounce(func, wait) { + let timeout; + return function debounced(...args) { + const context = this; + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(context, args), wait); + }; + }, + + throttle(func, limit) { + let inThrottle; + return function throttled(...args) { + const context = this; + if (!inThrottle) { + func.apply(context, args); + inThrottle = true; + setTimeout(() => inThrottle = false, limit); + } + }; + }, + + memoize(func, resolver) { + const cache = new Map(); + return function memoized(...args) { + const key = resolver ? resolver(...args) : args[0]; + if (cache.has(key)) { + return cache.get(key); + } + const result = func.apply(this, args); + cache.set(key, result); + return result; + }; + } + }, + + // Event Management + events: { + _listeners: new Map(), + + on(element, event, handler, options = {}) { + const key = `${element}_${event}`; + if (!this._listeners.has(key)) { + this._listeners.set(key, new Set()); + } + + this._listeners.get(key).add(handler); + element.addEventListener(event, handler, options); + + // Return cleanup function + return () => this.off(element, event, handler); + }, + + off(element, event, handler) { + const key = `${element}_${event}`; + const handlers = this._listeners.get(key); + + if (handlers) { + handlers.delete(handler); + if (handlers.size === 0) { + this._listeners.delete(key); + } + } + + element.removeEventListener(event, handler); + }, + + once(element, event, handler, options = {}) { + const wrappedHandler = (e) => { + handler(e); + this.off(element, event, wrappedHandler); + }; + + return this.on(element, event, wrappedHandler, options); + } + }, + + // Storage Utilities + storage: { + get(key, defaultValue = null) { + try { + const item = localStorage.getItem(`myp_${key}`); + return item ? JSON.parse(item) : defaultValue; + } catch (e) { + console.error('Storage get error:', e); + return defaultValue; + } + }, + + set(key, value) { + try { + localStorage.setItem(`myp_${key}`, JSON.stringify(value)); + return true; + } catch (e) { + console.error('Storage set error:', e); + return false; + } + }, + + remove(key) { + localStorage.removeItem(`myp_${key}`); + }, + + clear(prefix = 'myp_') { + const keys = Object.keys(localStorage); + keys.forEach(key => { + if (key.startsWith(prefix)) { + localStorage.removeItem(key); + } + }); + } + } + }; + + // Global shortcuts for backwards compatibility + window.MYPCore = MYPCore; + + // Legacy function mappings + window.showToast = (msg, type, duration) => MYPCore.notify.show(msg, type, duration); + window.showNotification = window.showToast; + window.showFlashMessage = window.showToast; + window.showSuccessMessage = (msg) => MYPCore.notify.show(msg, 'success'); + window.showErrorMessage = (msg) => MYPCore.notify.show(msg, 'error'); + window.showWarningMessage = (msg) => MYPCore.notify.show(msg, 'warning'); + window.showInfoMessage = (msg) => MYPCore.notify.show(msg, 'info'); + + // Auto-initialize on DOM ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + console.log('✅ MYP Core Utilities initialized'); + }); + } else { + console.log('✅ MYP Core Utilities initialized'); + } + +})(window); \ No newline at end of file diff --git a/backend/static/js/core-utilities.js b/backend/static/js/core-utilities.js new file mode 100644 index 00000000..fdc9f1d9 --- /dev/null +++ b/backend/static/js/core-utilities.js @@ -0,0 +1,493 @@ +/** + * Core Utilities Module for MYP Platform + * Consolidated utilities to eliminate redundancy and improve performance + */ + +(function(window) { + 'use strict'; + + // Module namespace + const MYP = window.MYP || {}; + + // ===== CONFIGURATION ===== + const config = { + apiTimeout: 30000, + cacheExpiry: 5 * 60 * 1000, // 5 minutes + notificationDuration: 5000, + debounceDelay: 300, + throttleDelay: 100 + }; + + // ===== CACHE MANAGEMENT ===== + const cache = new Map(); + const requestCache = new Map(); + + // ===== CSRF TOKEN HANDLING ===== + const csrf = { + token: null, + + get() { + if (!this.token) { + const meta = document.querySelector('meta[name="csrf-token"]'); + this.token = meta ? meta.getAttribute('content') : ''; + } + return this.token; + }, + + headers() { + return { + 'X-CSRFToken': this.get(), + 'Content-Type': 'application/json' + }; + } + }; + + // ===== DOM UTILITIES ===== + const dom = { + // Cached selectors + selectors: new Map(), + + // Get element with caching + get(selector, parent = document) { + const key = `${parent === document ? 'doc' : 'el'}_${selector}`; + if (!this.selectors.has(key)) { + this.selectors.set(key, parent.querySelector(selector)); + } + return this.selectors.get(key); + }, + + // Get all elements + getAll(selector, parent = document) { + return parent.querySelectorAll(selector); + }, + + // Clear cache + clearCache() { + this.selectors.clear(); + }, + + // Safe element creation + create(tag, attrs = {}, text = '') { + const el = document.createElement(tag); + Object.entries(attrs).forEach(([key, val]) => { + if (key === 'class') { + el.className = val; + } else if (key === 'dataset') { + Object.entries(val).forEach(([k, v]) => { + el.dataset[k] = v; + }); + } else { + el.setAttribute(key, val); + } + }); + if (text) el.textContent = text; + return el; + } + }; + + // ===== API REQUEST HANDLING ===== + const api = { + // Request deduplication + pending: new Map(), + + async request(url, options = {}) { + const key = `${options.method || 'GET'}_${url}`; + + // Check for pending request + if (this.pending.has(key)) { + return this.pending.get(key); + } + + // Check cache for GET requests + if (!options.method || options.method === 'GET') { + const cached = requestCache.get(key); + if (cached && Date.now() - cached.timestamp < config.cacheExpiry) { + return Promise.resolve(cached.data); + } + } + + // Prepare request + const requestOptions = { + ...options, + headers: { + ...csrf.headers(), + ...options.headers + } + }; + + // Create request promise + const promise = fetch(url, requestOptions) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + // Cache successful GET requests + if (!options.method || options.method === 'GET') { + requestCache.set(key, { + data, + timestamp: Date.now() + }); + } + return data; + }) + .finally(() => { + this.pending.delete(key); + }); + + this.pending.set(key, promise); + return promise; + }, + + get(url, options = {}) { + return this.request(url, { ...options, method: 'GET' }); + }, + + post(url, data, options = {}) { + return this.request(url, { + ...options, + method: 'POST', + body: JSON.stringify(data) + }); + }, + + put(url, data, options = {}) { + return this.request(url, { + ...options, + method: 'PUT', + body: JSON.stringify(data) + }); + }, + + delete(url, options = {}) { + return this.request(url, { ...options, method: 'DELETE' }); + } + }; + + // ===== UNIFIED NOTIFICATION SYSTEM ===== + const notifications = { + container: null, + queue: [], + + init() { + if (this.container) return; + + this.container = dom.create('div', { + id: 'myp-notifications', + class: 'fixed top-4 right-4 z-50 space-y-2' + }); + document.body.appendChild(this.container); + }, + + show(message, type = 'info', duration = config.notificationDuration) { + this.init(); + + const notification = dom.create('div', { + class: `notification notification-${type} glass-navbar p-4 rounded-lg shadow-lg transform translate-x-full transition-transform duration-300`, + role: 'alert' + }); + + const content = dom.create('div', { + class: 'flex items-center space-x-3' + }); + + // Icon + const icon = dom.create('i', { + class: `fas ${this.getIcon(type)} text-lg` + }); + + // Message + const text = dom.create('span', { + class: 'flex-1' + }, message); + + // Close button + const close = dom.create('button', { + class: 'ml-4 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200', + 'aria-label': 'Close' + }); + close.innerHTML = '×'; + close.onclick = () => this.remove(notification); + + content.appendChild(icon); + content.appendChild(text); + content.appendChild(close); + notification.appendChild(content); + + this.container.appendChild(notification); + + // Animate in + requestAnimationFrame(() => { + notification.classList.remove('translate-x-full'); + }); + + // Auto remove + if (duration > 0) { + setTimeout(() => this.remove(notification), duration); + } + + return notification; + }, + + remove(notification) { + notification.classList.add('translate-x-full'); + setTimeout(() => { + notification.remove(); + }, 300); + }, + + getIcon(type) { + const icons = { + success: 'fa-check-circle text-green-500', + error: 'fa-exclamation-circle text-red-500', + warning: 'fa-exclamation-triangle text-yellow-500', + info: 'fa-info-circle text-blue-500' + }; + return icons[type] || icons.info; + }, + + success(message, duration) { + return this.show(message, 'success', duration); + }, + + error(message, duration) { + return this.show(message, 'error', duration); + }, + + warning(message, duration) { + return this.show(message, 'warning', duration); + }, + + info(message, duration) { + return this.show(message, 'info', duration); + } + }; + + // ===== PERFORMANCE UTILITIES ===== + const performance = { + debounce(func, delay = config.debounceDelay) { + let timeoutId; + return function(...args) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => func.apply(this, args), delay); + }; + }, + + throttle(func, delay = config.throttleDelay) { + let lastCall = 0; + return function(...args) { + const now = Date.now(); + if (now - lastCall >= delay) { + lastCall = now; + return func.apply(this, args); + } + }; + }, + + memoize(func) { + const cache = new Map(); + return function(...args) { + const key = JSON.stringify(args); + if (cache.has(key)) { + return cache.get(key); + } + const result = func.apply(this, args); + cache.set(key, result); + return result; + }; + }, + + lazy(selector, callback) { + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + callback(entry.target); + observer.unobserve(entry.target); + } + }); + }); + + dom.getAll(selector).forEach(el => observer.observe(el)); + return observer; + } + }; + + // ===== STORAGE UTILITIES ===== + const storage = { + set(key, value, expiry = null) { + const data = { + value, + timestamp: Date.now(), + expiry + }; + try { + localStorage.setItem(`myp_${key}`, JSON.stringify(data)); + return true; + } catch (e) { + console.error('Storage error:', e); + return false; + } + }, + + get(key) { + try { + const item = localStorage.getItem(`myp_${key}`); + if (!item) return null; + + const data = JSON.parse(item); + if (data.expiry && Date.now() - data.timestamp > data.expiry) { + this.remove(key); + return null; + } + return data.value; + } catch (e) { + console.error('Storage error:', e); + return null; + } + }, + + remove(key) { + localStorage.removeItem(`myp_${key}`); + }, + + clear() { + Object.keys(localStorage) + .filter(key => key.startsWith('myp_')) + .forEach(key => localStorage.removeItem(key)); + } + }; + + // ===== EVENT UTILITIES ===== + const events = { + listeners: new Map(), + + on(element, event, handler, options = {}) { + const key = `${element}_${event}`; + if (!this.listeners.has(key)) { + this.listeners.set(key, new Set()); + } + + this.listeners.get(key).add(handler); + element.addEventListener(event, handler, options); + }, + + off(element, event, handler) { + const key = `${element}_${event}`; + if (this.listeners.has(key)) { + this.listeners.get(key).delete(handler); + if (this.listeners.get(key).size === 0) { + this.listeners.delete(key); + } + } + element.removeEventListener(event, handler); + }, + + once(element, event, handler, options = {}) { + const onceHandler = (e) => { + handler(e); + this.off(element, event, onceHandler); + }; + this.on(element, event, onceHandler, options); + }, + + emit(name, detail = {}) { + const event = new CustomEvent(name, { + detail, + bubbles: true, + cancelable: true + }); + document.dispatchEvent(event); + }, + + cleanup() { + this.listeners.forEach((handlers, key) => { + const [element, event] = key.split('_'); + handlers.forEach(handler => { + element.removeEventListener(event, handler); + }); + }); + this.listeners.clear(); + } + }; + + // ===== FORM UTILITIES ===== + const forms = { + serialize(form) { + const data = new FormData(form); + const obj = {}; + for (const [key, value] of data.entries()) { + if (obj[key]) { + if (!Array.isArray(obj[key])) { + obj[key] = [obj[key]]; + } + obj[key].push(value); + } else { + obj[key] = value; + } + } + return obj; + }, + + validate(form) { + const inputs = form.querySelectorAll('[required]'); + let valid = true; + + inputs.forEach(input => { + if (!input.value.trim()) { + input.classList.add('border-red-500'); + valid = false; + } else { + input.classList.remove('border-red-500'); + } + }); + + return valid; + }, + + reset(form) { + form.reset(); + form.querySelectorAll('.border-red-500').forEach(el => { + el.classList.remove('border-red-500'); + }); + } + }; + + // ===== INITIALIZATION ===== + const init = () => { + // Initialize notifications + notifications.init(); + + // Clean up on page unload + window.addEventListener('beforeunload', () => { + events.cleanup(); + dom.clearCache(); + }); + }; + + // ===== PUBLIC API ===== + MYP.utils = { + csrf, + dom, + api, + notifications, + performance, + storage, + events, + forms, + init, + config + }; + + // Auto-initialize when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } + + // Export to global scope + window.MYP = MYP; + +})(window); \ No newline at end of file diff --git a/backend/templates/base.html b/backend/templates/base.html index ab41f8e1..9d630685 100644 --- a/backend/templates/base.html +++ b/backend/templates/base.html @@ -36,9 +36,12 @@ + + + - - + + {% block extra_css %}{% endblock %} @@ -104,9 +107,9 @@ // User-Dropdown-Funktionalität initialisieren initUserDropdown(); - // Flask Flash Messages über das Glassmorphism-System anzeigen + // Flask Flash Messages über das neue Core Utilities System anzeigen const flashContainer = document.getElementById('flask-flash-messages'); - if (flashContainer) { + if (flashContainer && window.MYP && window.MYP.utils) { const flashCount = parseInt(flashContainer.getAttribute('data-flash-count')) || 0; for (let i = 1; i <= flashCount; i++) { @@ -118,18 +121,10 @@ // Flask-Kategorien zu JavaScript-Kategorien mappen if (messageType === 'danger') messageType = 'error'; - // Nachricht über das moderne Glassmorphism-System anzeigen - if (typeof showToast === 'function') { - // Kleine Verzögerung für bessere UX und schöne Glassmorphism-Animationen - setTimeout(() => { - showToast(message, messageType, 6000, { - title: messageType === 'success' ? 'Erfolgreich' : - messageType === 'error' ? 'Fehler' : - messageType === 'warning' ? 'Warnung' : 'Information', - playSound: true - }); - }, i * 250); // Nachrichten gestaffelt anzeigen - } + // Nachricht über das Core Utilities System anzeigen + setTimeout(() => { + window.MYP.utils.notifications[messageType](message, 6000); + }, i * 100); // Reduzierte Verzögerung für schnelleres Feedback } } @@ -905,9 +900,9 @@ // User-Dropdown-Funktionalität initialisieren initUserDropdown(); - // Flask Flash Messages über das Glassmorphism-System anzeigen + // Flask Flash Messages über das neue Core Utilities System anzeigen const flashContainer = document.getElementById('flask-flash-messages'); - if (flashContainer) { + if (flashContainer && window.MYP && window.MYP.utils) { const flashCount = parseInt(flashContainer.getAttribute('data-flash-count')) || 0; for (let i = 1; i <= flashCount; i++) { @@ -919,18 +914,10 @@ // Flask-Kategorien zu JavaScript-Kategorien mappen if (messageType === 'danger') messageType = 'error'; - // Nachricht über das moderne Glassmorphism-System anzeigen - if (typeof showToast === 'function') { - // Kleine Verzögerung für bessere UX und schöne Glassmorphism-Animationen - setTimeout(() => { - showToast(message, messageType, 6000, { - title: messageType === 'success' ? 'Erfolgreich' : - messageType === 'error' ? 'Fehler' : - messageType === 'warning' ? 'Warnung' : 'Information', - playSound: true - }); - }, i * 250); // Nachrichten gestaffelt anzeigen - } + // Nachricht über das Core Utilities System anzeigen + setTimeout(() => { + window.MYP.utils.notifications[messageType](message, 6000); + }, i * 100); // Reduzierte Verzögerung für schnelleres Feedback } }