/** * MYP Platform Job Manager * Verwaltung und Steuerung von 3D-Druckaufträgen * Version: 1.0.0 */ (function() { 'use strict'; /** * Job Manager Klasse für 3D-Druckaufträge */ class JobManager { constructor() { this.jobs = []; this.currentPage = 1; this.totalPages = 1; this.isLoading = false; this.refreshInterval = null; this.autoRefreshEnabled = false; console.log('🔧 JobManager wird initialisiert...'); } /** * JobManager initialisieren */ async init() { try { console.log('🚀 JobManager-Initialisierung gestartet'); // Event-Listener einrichten this.setupEventListeners(); // Formular-Handler einrichten this.setupFormHandlers(); // Anfängliche Jobs laden await this.loadJobs(); // Auto-Refresh starten falls aktiviert if (this.autoRefreshEnabled) { this.startAutoRefresh(); } console.log('✅ JobManager erfolgreich initialisiert'); } catch (error) { console.error('❌ Fehler bei JobManager-Initialisierung:', error); this.showToast('Fehler beim Initialisieren des Job-Managers', 'error'); } } /** * Event-Listener für Job-Aktionen einrichten */ setupEventListeners() { console.log('📡 Event-Listener werden eingerichtet...'); // Job-Aktionen über data-Attribute document.addEventListener('click', (e) => { const target = e.target.closest('[data-job-action]'); if (!target) return; const action = target.getAttribute('data-job-action'); const jobId = target.getAttribute('data-job-id'); if (!jobId) { console.warn('⚠️ Job-ID fehlt für Aktion:', action); return; } switch (action) { case 'start': this.startJob(jobId); break; case 'pause': this.pauseJob(jobId); break; case 'resume': this.resumeJob(jobId); break; case 'stop': this.stopJob(jobId); break; case 'delete': this.deleteJob(jobId); break; case 'details': this.openJobDetails(jobId); break; default: console.warn('⚠️ Unbekannte Job-Aktion:', action); } }); // Refresh-Button const refreshBtn = document.getElementById('refresh-jobs'); if (refreshBtn) { refreshBtn.addEventListener('click', () => this.loadJobs()); } // Auto-Refresh Toggle const autoRefreshToggle = document.getElementById('auto-refresh-toggle'); if (autoRefreshToggle) { autoRefreshToggle.addEventListener('change', (e) => { this.autoRefreshEnabled = e.target.checked; if (this.autoRefreshEnabled) { this.startAutoRefresh(); } else { this.stopAutoRefresh(); } }); } console.log('✅ Event-Listener erfolgreich eingerichtet'); } /** * Formular-Handler für Job-Erstellung einrichten */ setupFormHandlers() { console.log('📝 Formular-Handler werden eingerichtet...'); const newJobForm = document.getElementById('new-job-form'); if (newJobForm) { newJobForm.addEventListener('submit', async (e) => { e.preventDefault(); await this.createNewJob(new FormData(newJobForm)); }); } const editJobForm = document.getElementById('edit-job-form'); if (editJobForm) { editJobForm.addEventListener('submit', async (e) => { e.preventDefault(); const jobId = editJobForm.getAttribute('data-job-id'); await this.updateJob(jobId, new FormData(editJobForm)); }); } console.log('✅ Formular-Handler erfolgreich eingerichtet'); } /** * Jobs von Server laden */ async loadJobs(page = 1) { if (this.isLoading) { console.log('⚠️ Jobs werden bereits geladen...'); return; } this.isLoading = true; this.showLoadingState(true); try { console.log(`📥 Lade Jobs (Seite ${page})...`); const response = await fetch(`/api/jobs?page=${page}`, { headers: { 'X-CSRFToken': this.getCSRFToken(), 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); // VERBESSERTE NULL-CHECKS für jobs-Daten if (data && typeof data === 'object') { // Sicherstellen, dass jobs immer ein Array ist this.jobs = Array.isArray(data.jobs) ? data.jobs : []; this.currentPage = Number(data.current_page) || 1; this.totalPages = Number(data.total_pages) || 1; console.log(`✅ ${this.jobs.length} Jobs erfolgreich geladen`, this.jobs); } else { console.warn('⚠️ Unerwartete API-Response-Struktur:', data); this.jobs = []; this.currentPage = 1; this.totalPages = 1; } // Sichere Rendering-Aufrufe this.renderJobs(); this.updatePagination(); console.log(`✅ ${this.jobs.length} Jobs erfolgreich geladen und gerendert`); } catch (error) { console.error('❌ Fehler beim Laden der Jobs:', error); this.showToast('Fehler beim Laden der Jobs', 'error'); // Fallback: Leere Jobs-Liste anzeigen this.jobs = []; this.currentPage = 1; this.totalPages = 1; this.renderJobs(); } finally { this.isLoading = false; this.showLoadingState(false); } } /** * Job starten */ async startJob(jobId) { try { console.log(`▶️ Starte Job ${jobId}...`); const response = await fetch(`/api/jobs/${jobId}/start`, { method: 'POST', headers: { 'X-CSRFToken': this.getCSRFToken(), 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } this.showToast('Job erfolgreich gestartet', 'success'); await this.loadJobs(); } catch (error) { console.error('❌ Fehler beim Starten des Jobs:', error); this.showToast('Fehler beim Starten des Jobs', 'error'); } } /** * Job pausieren */ async pauseJob(jobId) { try { console.log(`⏸️ Pausiere Job ${jobId}...`); const response = await fetch(`/api/jobs/${jobId}/pause`, { method: 'POST', headers: { 'X-CSRFToken': this.getCSRFToken(), 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } this.showToast('Job erfolgreich pausiert', 'success'); await this.loadJobs(); } catch (error) { console.error('❌ Fehler beim Pausieren des Jobs:', error); this.showToast('Fehler beim Pausieren des Jobs', 'error'); } } /** * Job fortsetzen */ async resumeJob(jobId) { try { console.log(`▶️ Setze Job ${jobId} fort...`); const response = await fetch(`/api/jobs/${jobId}/resume`, { method: 'POST', headers: { 'X-CSRFToken': this.getCSRFToken(), 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } this.showToast('Job erfolgreich fortgesetzt', 'success'); await this.loadJobs(); } catch (error) { console.error('❌ Fehler beim Fortsetzen des Jobs:', error); this.showToast('Fehler beim Fortsetzen des Jobs', 'error'); } } /** * Job stoppen */ async stopJob(jobId) { if (!confirm('Möchten Sie diesen Job wirklich stoppen?')) { return; } try { console.log(`⏹️ Stoppe Job ${jobId}...`); const response = await fetch(`/api/jobs/${jobId}/stop`, { method: 'POST', headers: { 'X-CSRFToken': this.getCSRFToken(), 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } this.showToast('Job erfolgreich gestoppt', 'success'); await this.loadJobs(); } catch (error) { console.error('❌ Fehler beim Stoppen des Jobs:', error); this.showToast('Fehler beim Stoppen des Jobs', 'error'); } } /** * Job löschen */ async deleteJob(jobId) { if (!confirm('Möchten Sie diesen Job wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.')) { return; } try { console.log(`🗑️ Lösche Job ${jobId}...`); const response = await fetch(`/api/jobs/${jobId}`, { method: 'DELETE', headers: { 'X-CSRFToken': this.getCSRFToken(), 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } this.showToast('Job erfolgreich gelöscht', 'success'); await this.loadJobs(); } catch (error) { console.error('❌ Fehler beim Löschen des Jobs:', error); this.showToast('Fehler beim Löschen des Jobs', 'error'); } } /** * Job-Details öffnen */ openJobDetails(jobId) { console.log(`📄 Öffne Details für Job ${jobId}...`); // Modal für Job-Details öffnen oder zu Detail-Seite navigieren const detailsUrl = `/jobs/${jobId}`; // Prüfen ob Modal verfügbar ist const detailsModal = document.getElementById(`job-details-${jobId}`); if (detailsModal && typeof window.MYP !== 'undefined' && window.MYP.UI && window.MYP.UI.modal) { window.MYP.UI.modal.open(`job-details-${jobId}`); } else { // Fallback: Zur Detail-Seite navigieren window.location.href = detailsUrl; } } /** * Jobs in der UI rendern */ renderJobs() { const jobsList = document.getElementById('jobs-list'); if (!jobsList) { console.warn('⚠️ Jobs-Liste Element nicht gefunden'); 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 = `
📭

Keine Jobs vorhanden

Es wurden noch keine Druckaufträge erstellt.

`; return; } 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 = `
⚠️

Fehler beim Laden

Es gab einen Fehler beim Darstellen der Jobs.

`; } } /** * Einzelne Job-Karte rendern */ renderJobCard(job) { const statusClass = this.getJobStatusClass(job.status); const statusText = this.getJobStatusText(job.status); return `

${job.name || 'Unbenannter Job'}

ID: ${job.id} Drucker: ${job.printer_name || 'Unbekannt'} Erstellt: ${new Date(job.created_at).toLocaleDateString('de-DE')}
${statusText} ${job.progress ? `${job.progress}%` : ''}
${this.renderJobActions(job)}
`; } /** * Job-Aktionen rendern */ renderJobActions(job) { const actions = []; // Details-Button immer verfügbar actions.push(` `); // Status-abhängige Aktionen switch (job.status) { case 'pending': case 'ready': actions.push(` `); break; case 'running': case 'printing': actions.push(` `); actions.push(` `); break; case 'paused': actions.push(` `); actions.push(` `); break; case 'completed': case 'failed': case 'cancelled': actions.push(` `); break; } return actions.join(''); } /** * CSS-Klasse für Job-Status */ getJobStatusClass(status) { const statusClasses = { 'pending': 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300', 'ready': 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300', 'running': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300', 'printing': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300', 'paused': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300', 'completed': 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900 dark:text-emerald-300', 'failed': 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300', 'cancelled': 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' }; return statusClasses[status] || statusClasses['pending']; } /** * Anzeigetext für Job-Status */ getJobStatusText(status) { const statusTexts = { 'pending': 'Wartend', 'ready': 'Bereit', 'running': 'Läuft', 'printing': 'Druckt', 'paused': 'Pausiert', 'completed': 'Abgeschlossen', 'failed': 'Fehlgeschlagen', 'cancelled': 'Abgebrochen' }; return statusTexts[status] || 'Unbekannt'; } /** * Loading-Zustand anzeigen/verstecken */ showLoadingState(show) { const loadingEl = document.getElementById('jobs-loading'); const jobsList = document.getElementById('jobs-list'); if (loadingEl) { loadingEl.style.display = show ? 'block' : 'none'; } if (jobsList) { jobsList.style.opacity = show ? '0.5' : '1'; jobsList.style.pointerEvents = show ? 'none' : 'auto'; } } /** * CSRF-Token abrufen */ getCSRFToken() { const token = document.querySelector('meta[name="csrf-token"]'); return token ? token.getAttribute('content') : ''; } /** * Toast-Nachricht anzeigen */ showToast(message, type = 'info') { if (typeof window.showToast === 'function') { window.showToast(message, type); } else { console.log(`${type.toUpperCase()}: ${message}`); } } /** * Auto-Refresh starten */ startAutoRefresh() { this.stopAutoRefresh(); // Vorherigen Refresh stoppen this.refreshInterval = setInterval(() => { if (!this.isLoading) { this.loadJobs(this.currentPage); } }, 30000); // Alle 30 Sekunden console.log('🔄 Auto-Refresh gestartet (30s Intervall)'); } /** * Auto-Refresh stoppen */ stopAutoRefresh() { if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = null; console.log('⏹️ Auto-Refresh gestoppt'); } } /** * Paginierung aktualisieren */ updatePagination() { const paginationEl = document.getElementById('jobs-pagination'); if (!paginationEl) return; if (this.totalPages <= 1) { paginationEl.style.display = 'none'; return; } paginationEl.style.display = 'flex'; let paginationHTML = ''; // Vorherige Seite if (this.currentPage > 1) { paginationHTML += ` `; } // Seitenzahlen for (let i = 1; i <= this.totalPages; i++) { const isActive = i === this.currentPage; paginationHTML += ` `; } // Nächste Seite if (this.currentPage < this.totalPages) { paginationHTML += ` `; } paginationEl.innerHTML = paginationHTML; } /** * Neuen Job erstellen */ async createNewJob(formData) { try { console.log('📝 Erstelle neuen Job...'); const response = await fetch('/api/jobs', { method: 'POST', headers: { 'X-CSRFToken': this.getCSRFToken() }, body: formData }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const result = await response.json(); this.showToast('Job erfolgreich erstellt', 'success'); // Jobs neu laden await this.loadJobs(); // Formular zurücksetzen const form = document.getElementById('new-job-form'); if (form) { form.reset(); } return result; } catch (error) { console.error('❌ Fehler beim Erstellen des Jobs:', error); this.showToast('Fehler beim Erstellen des Jobs', 'error'); throw error; } } /** * Job aktualisieren */ async updateJob(jobId, formData) { try { console.log(`📝 Aktualisiere Job ${jobId}...`); const response = await fetch(`/api/jobs/${jobId}`, { method: 'PUT', headers: { 'X-CSRFToken': this.getCSRFToken() }, body: formData }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const result = await response.json(); this.showToast('Job erfolgreich aktualisiert', 'success'); // Jobs neu laden await this.loadJobs(); return result; } catch (error) { console.error('❌ Fehler beim Aktualisieren des Jobs:', error); this.showToast('Fehler beim Aktualisieren des Jobs', 'error'); throw error; } } } // JobManager global verfügbar machen window.JobManager = JobManager; // JobManager-Instanz erstellen wenn DOM bereit ist document.addEventListener('DOMContentLoaded', function() { if (typeof window.jobManager === 'undefined') { window.jobManager = new JobManager(); // Nur initialisieren wenn wir uns auf einer Jobs-Seite befinden if (document.getElementById('jobs-list') || document.querySelector('[data-job-action]')) { window.jobManager.init(); } } }); console.log('✅ JobManager-Modul geladen'); })();