Projektarbeit-MYP/backend/static/js/global-refresh-functions.js

736 lines
25 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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 = `
<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');
}
} 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 = `
<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;
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');