/** * MYP Platform - Globale Refresh-Funktionen * Sammelt alle Refresh-Funktionen für verschiedene Seiten und Komponenten */ /** * Utility-Funktionen für robustes DOM-Element-Handling */ /** * Sicheres Aktualisieren von Element-Inhalten * @param {string} elementId - Die ID des Elements * @param {string|number} value - Der zu setzende Wert * @param {Object} options - Optionale Parameter * @returns {boolean} - true wenn erfolgreich, false wenn Element nicht gefunden */ function safeUpdateElement(elementId, value, options = {}) { const { fallbackValue = '-', logWarning = true, attribute = 'textContent', transform = null } = options; const element = document.getElementById(elementId); if (!element) { if (logWarning) { console.warn(`🔍 Element mit ID '${elementId}' nicht gefunden`); } return false; } try { const finalValue = value !== undefined && value !== null ? value : fallbackValue; const displayValue = transform ? transform(finalValue) : finalValue; element[attribute] = displayValue; return true; } catch (error) { console.error(`❌ Fehler beim Aktualisieren von Element '${elementId}':`, error); return false; } } /** * Sicheres Aktualisieren mehrerer Elemente * @param {Object} updates - Objekt mit elementId: value Paaren * @param {Object} options - Optionale Parameter */ function safeBatchUpdate(updates, options = {}) { const results = {}; Object.entries(updates).forEach(([elementId, value]) => { results[elementId] = safeUpdateElement(elementId, value, options); }); const successful = Object.values(results).filter(Boolean).length; const total = Object.keys(updates).length; console.log(`📊 Batch-Update: ${successful}/${total} Elemente erfolgreich aktualisiert`); return results; } /** * Prüfen ob Element existiert * @param {string} elementId - Die ID des Elements * @returns {boolean} - true wenn Element existiert */ function elementExists(elementId) { return document.getElementById(elementId) !== null; } /** * Dashboard-Refresh-Funktion */ window.refreshDashboard = async function() { const refreshButton = document.getElementById('refreshDashboard'); if (refreshButton) { // Button-State ändern refreshButton.disabled = true; const icon = refreshButton.querySelector('svg'); if (icon) { icon.classList.add('animate-spin'); } } try { // Dashboard-Statistiken aktualisieren const response = await fetch('/api/dashboard/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCSRFToken() } }); const data = await response.json(); if (data.success) { // Statistiken im DOM aktualisieren updateDashboardStats(data.stats); // Benachrichtigung anzeigen showToast('✅ Dashboard erfolgreich aktualisiert', 'success'); // Seite neu laden für vollständige Aktualisierung setTimeout(() => { window.location.reload(); }, 1000); } else { showToast('❌ Fehler beim Aktualisieren des Dashboards', 'error'); } } catch (error) { console.error('Dashboard-Refresh Fehler:', error); showToast('❌ Netzwerkfehler beim Dashboard-Refresh', 'error'); } finally { // Button-State zurücksetzen if (refreshButton) { refreshButton.disabled = false; const icon = refreshButton.querySelector('svg'); if (icon) { icon.classList.remove('animate-spin'); } } } }; /** * Statistiken-Refresh-Funktion */ window.refreshStats = async function() { const refreshButton = document.querySelector('button[onclick="refreshStats()"]'); if (refreshButton) { refreshButton.disabled = true; const icon = refreshButton.querySelector('svg'); if (icon) { icon.classList.add('animate-spin'); } } try { // Basis-Statistiken laden if (typeof loadBasicStats === 'function') { await loadBasicStats(); } else { // Fallback: API-Aufruf für Statistiken const response = await fetch('/api/stats'); const data = await response.json(); if (response.ok) { // Statistiken im DOM aktualisieren updateStatsCounter('total-jobs-count', data.total_jobs); updateStatsCounter('completed-jobs-count', data.completed_jobs); updateStatsCounter('online-printers-count', data.online_printers); updateStatsCounter('success-rate-percent', data.success_rate + '%'); updateStatsCounter('active-jobs-count', data.active_jobs); updateStatsCounter('failed-jobs-count', data.failed_jobs); updateStatsCounter('total-users-count', data.total_users); } else { throw new Error(data.error || 'Fehler beim Laden der Statistiken'); } } // Charts aktualisieren if (window.refreshAllCharts) { window.refreshAllCharts(); } showToast('✅ Statistiken erfolgreich aktualisiert', 'success'); } catch (error) { console.error('Stats-Refresh Fehler:', error); showToast('❌ Fehler beim Aktualisieren der Statistiken', 'error'); } finally { if (refreshButton) { refreshButton.disabled = false; const icon = refreshButton.querySelector('svg'); if (icon) { icon.classList.remove('animate-spin'); } } } }; /** * Jobs-Refresh-Funktion - VERBESSERT mit umfassenden Null-Checks */ window.refreshJobs = async function() { const refreshButton = document.getElementById('refresh-button'); if (refreshButton) { refreshButton.disabled = true; const icon = refreshButton.querySelector('svg'); if (icon) { icon.classList.add('animate-spin'); } } try { 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(); 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: 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', 'X-CSRFToken': getCSRFToken() } }); if (!response.ok) { throw new Error(`API-Fehler: ${response.status} ${response.statusText}`); } const data = await response.json(); console.log('📝 API-Response erhalten:', data); // 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 = `
📭

Keine Jobs vorhanden

Es wurden noch keine Druckaufträge erstellt.

`; } 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 `

${job.filename || job.title || job.name || 'Unbekannter Job'}

ID: ${job.id || 'N/A'}

Status: ${job.status || 'Unbekannt'}

${job.printer_name ? `

Drucker: ${job.printer_name}

` : ''} ${job.created_at ? `

Erstellt: ${new Date(job.created_at).toLocaleDateString('de-DE')}

` : ''}
`; }).filter(card => card !== '').join(''); container.innerHTML = jobCards || `

Keine gültigen Jobs zum Anzeigen

`; } break; } } if (!containerFound) { console.warn('⚠️ Kein Jobs-Container gefunden. Verfügbare Container:', jobsContainers); } refreshSuccess = true; } if (refreshSuccess) { showToast('✅ Druckaufträge erfolgreich aktualisiert', 'success'); } } catch (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 = `
⚠️

Fehler beim Laden

${errorMessage}

`; } } finally { if (refreshButton) { refreshButton.disabled = false; const icon = refreshButton.querySelector('svg'); if (icon) { icon.classList.remove('animate-spin'); } } } }; /** * Calendar-Refresh-Funktion */ window.refreshCalendar = async function() { const refreshButton = document.getElementById('refresh-button'); if (refreshButton) { refreshButton.disabled = true; const icon = refreshButton.querySelector('svg'); if (icon) { icon.classList.add('animate-spin'); } } try { // FullCalendar neu laden falls verfügbar if (typeof calendar !== 'undefined' && calendar.refetchEvents) { calendar.refetchEvents(); showToast('✅ Kalender erfolgreich aktualisiert', 'success'); } else { // Fallback: Seite neu laden window.location.reload(); } } catch (error) { console.error('Calendar-Refresh Fehler:', error); showToast('❌ Fehler beim Aktualisieren des Kalenders', 'error'); } finally { if (refreshButton) { refreshButton.disabled = false; const icon = refreshButton.querySelector('svg'); if (icon) { icon.classList.remove('animate-spin'); } } } }; /** * Drucker-Refresh-Funktion */ window.refreshPrinters = async function() { const refreshButton = document.getElementById('refresh-button'); if (refreshButton) { refreshButton.disabled = true; const icon = refreshButton.querySelector('svg'); if (icon) { icon.classList.add('animate-spin'); } } try { // Drucker-Manager verwenden falls verfügbar if (typeof printerManager !== 'undefined' && printerManager.loadPrinters) { await printerManager.loadPrinters(); } else { // Fallback: API-Aufruf für Drucker-Update const response = await fetch('/api/printers/status/live', { headers: { 'X-CSRFToken': getCSRFToken() } }); if (response.ok) { // Seite neu laden für vollständige Aktualisierung window.location.reload(); } else { throw new Error('Drucker-Status konnte nicht abgerufen werden'); } } showToast('✅ Drucker erfolgreich aktualisiert', 'success'); } catch (error) { console.error('Printer-Refresh Fehler:', error); showToast('❌ Fehler beim Aktualisieren der Drucker', 'error'); } finally { if (refreshButton) { refreshButton.disabled = false; const icon = refreshButton.querySelector('svg'); if (icon) { icon.classList.remove('animate-spin'); } } } }; /** * Dashboard-Statistiken im DOM aktualisieren */ function updateDashboardStats(stats) { // Aktive Jobs const activeJobsEl = document.querySelector('[data-stat="active-jobs"]'); if (activeJobsEl) { activeJobsEl.textContent = stats.active_jobs || 0; } // Verfügbare Drucker const availablePrintersEl = document.querySelector('[data-stat="available-printers"]'); if (availablePrintersEl) { availablePrintersEl.textContent = stats.available_printers || 0; } // Gesamte Jobs const totalJobsEl = document.querySelector('[data-stat="total-jobs"]'); if (totalJobsEl) { totalJobsEl.textContent = stats.total_jobs || 0; } // Erfolgsrate const successRateEl = document.querySelector('[data-stat="success-rate"]'); if (successRateEl) { successRateEl.textContent = (stats.success_rate || 0) + '%'; } console.log('📊 Dashboard-Statistiken aktualisiert:', stats); } /** * Statistiken-Counter im DOM aktualisieren */ function updateStatsCounter(elementId, value, animate = true) { const element = document.getElementById(elementId); if (!element) { console.warn(`Element mit ID '${elementId}' nicht gefunden`); return; } // Sichere Wert-Validierung hinzufügen if (value === null || value === undefined) { console.warn(`Ungültiger Wert für Element '${elementId}':`, value); value = 0; // Fallback-Wert } if (animate) { // Animierte Zählung const currentValue = parseInt(element.textContent.replace(/[^\d]/g, '')) || 0; const targetValue = parseInt(value.toString().replace(/[^\d]/g, '')) || 0; if (currentValue !== targetValue) { // Sichere String-Konvertierung const finalTextValue = value !== null && value !== undefined ? value.toString() : '0'; animateCounter(element, currentValue, targetValue, finalTextValue); } } else { // Sichere Zuweisung ohne Animation element.textContent = value !== null && value !== undefined ? value.toString() : '0'; } } /** * Animierte Counter-Funktion */ function animateCounter(element, start, end, finalText) { // Sichere Parameter-Validierung if (!element) { console.warn('animateCounter: Kein gültiges Element übergeben'); return; } // Sichere finalText-Validierung mit optimiertem Logging if (typeof finalText !== 'string') { // Nur bei problematischen Werten warnen (null, undefined, objects) if (finalText === null || finalText === undefined || (typeof finalText === 'object' && finalText !== null)) { console.warn('animateCounter: Problematischer finalText-Wert:', finalText); } // Normale Numbers stille konvertieren finalText = finalText !== null && finalText !== undefined ? String(finalText) : '0'; } // Sichere start/end-Validierung start = parseInt(start) || 0; end = parseInt(end) || 0; const duration = 1000; // 1 Sekunde const startTime = performance.now(); function updateCounter(currentTime) { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); // Easing-Funktion (ease-out) const easeOut = 1 - Math.pow(1 - progress, 3); const currentValue = Math.round(start + (end - start) * easeOut); // Sichere includes-Prüfung try { if (typeof finalText === 'string' && finalText.includes('%')) { element.textContent = currentValue + '%'; } else { element.textContent = currentValue; } } catch (error) { console.warn('animateCounter: Fehler bei finalText.includes:', error, 'finalText:', finalText); element.textContent = currentValue; } if (progress < 1) { requestAnimationFrame(updateCounter); } else { // Sichere Zuweisung des finalen Wertes try { element.textContent = finalText; } catch (error) { console.warn('animateCounter: Fehler bei finaler Zuweisung:', error); element.textContent = String(end); } } } requestAnimationFrame(updateCounter); } /** * CSRF-Token abrufen */ function getCSRFToken() { const token = document.querySelector('meta[name="csrf-token"]'); return token ? token.getAttribute('content') : ''; } /** * Toast-Benachrichtigung anzeigen */ function showToast(message, type = 'info') { // Prüfen ob der OptimizationManager verfügbar ist und dessen Toast-Funktion verwenden if (typeof optimizationManager !== 'undefined' && optimizationManager.showToast) { optimizationManager.showToast(message, type); return; } // Fallback: Einfache Console-Ausgabe const emoji = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' }; console.log(`${emoji[type] || 'ℹ️'} ${message}`); // Versuche eine Alert-Benachrichtigung zu erstellen try { const toast = document.createElement('div'); toast.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg transition-all duration-300 transform translate-x-full`; const colors = { success: 'bg-green-500 text-white', error: 'bg-red-500 text-white', warning: 'bg-yellow-500 text-black', info: 'bg-blue-500 text-white' }; toast.className += ` ${colors[type]}`; toast.textContent = message; document.body.appendChild(toast); // Animation einblenden setTimeout(() => { toast.classList.remove('translate-x-full'); }, 100); // Nach 3 Sekunden automatisch entfernen setTimeout(() => { toast.classList.add('translate-x-full'); setTimeout(() => { toast.remove(); }, 300); }, 3000); } catch (error) { console.warn('Toast-Erstellung fehlgeschlagen:', error); } } /** * Universelle Refresh-Funktion basierend auf aktueller Seite */ window.universalRefresh = function() { const currentPath = window.location.pathname; if (currentPath.includes('/dashboard')) { refreshDashboard(); } else if (currentPath.includes('/jobs')) { refreshJobs(); } else if (currentPath.includes('/calendar') || currentPath.includes('/schichtplan')) { refreshCalendar(); } else if (currentPath.includes('/printers') || currentPath.includes('/drucker')) { refreshPrinters(); } else { // Fallback: Seite neu laden window.location.reload(); } }; /** * Auto-Refresh-Funktionalität */ class AutoRefreshManager { constructor() { this.isEnabled = false; this.interval = null; this.intervalTime = 30000; // 30 Sekunden } start() { if (this.isEnabled) return; this.isEnabled = true; this.interval = setInterval(() => { // Nur refresh wenn Seite sichtbar ist if (!document.hidden) { universalRefresh(); } }, this.intervalTime); console.log('🔄 Auto-Refresh aktiviert (alle 30 Sekunden)'); } stop() { if (!this.isEnabled) return; this.isEnabled = false; if (this.interval) { clearInterval(this.interval); this.interval = null; } console.log('⏸️ Auto-Refresh deaktiviert'); } toggle() { if (this.isEnabled) { this.stop(); } else { this.start(); } } } // Globaler Auto-Refresh-Manager window.autoRefreshManager = new AutoRefreshManager(); /** * Keyboard-Shortcuts */ document.addEventListener('keydown', function(e) { // F5 oder Ctrl+R abfangen und eigene Refresh-Funktion verwenden if (e.key === 'F5' || (e.ctrlKey && e.key === 'r')) { e.preventDefault(); universalRefresh(); } // Ctrl+Shift+R für Auto-Refresh-Toggle if (e.ctrlKey && e.shiftKey && e.key === 'R') { e.preventDefault(); autoRefreshManager.toggle(); showToast( autoRefreshManager.isEnabled ? '🔄 Auto-Refresh aktiviert' : '⏸️ Auto-Refresh deaktiviert', 'info' ); } }); /** * Visibility API für Auto-Refresh bei Tab-Wechsel */ document.addEventListener('visibilitychange', function() { if (!document.hidden && autoRefreshManager.isEnabled) { // Verzögertes Refresh wenn Tab wieder sichtbar wird setTimeout(universalRefresh, 1000); } }); console.log('🔄 Globale Refresh-Funktionen geladen');