/** * Erweiterte Druckerkonflikt-Management-Engine - Frontend * MYP Platform * * Behandelt Konflikte zwischen Druckerreservierungen mit * intelligenten Lösungsvorschlägen und Benutzerführung. */ class ConflictManager { constructor() { this.lastConflictCheck = null; this.currentRecommendation = null; this.autoCheckEnabled = true; this.checkTimeout = null; this.debounceDelay = 500; // ms this.init(); } init() { this.createConflictModal(); this.createAvailabilityPanel(); this.createSmartRecommendationWidget(); this.attachEventListeners(); console.log('🔧 ConflictManager initialisiert'); } // =================== MODAL CREATION =================== createConflictModal() { const modalHTML = ` `; document.body.insertAdjacentHTML('beforeend', modalHTML); } createAvailabilityPanel() { const panelHTML = ` `; // Panel nach dem Kalender-Container einfügen const calendarContainer = document.querySelector('.container'); if (calendarContainer) { calendarContainer.insertAdjacentHTML('afterbegin', panelHTML); } } createSmartRecommendationWidget() { const widgetHTML = ` `; // Widget nach dem Kalender-Container einfügen const calendarContainer = document.querySelector('.container'); if (calendarContainer) { calendarContainer.insertAdjacentHTML('afterbegin', widgetHTML); } } // =================== EVENT LISTENERS =================== attachEventListeners() { // Konflikt-Modal Events document.getElementById('closeConflictModal')?.addEventListener('click', () => { this.hideConflictModal(); }); document.getElementById('applyAutoFix')?.addEventListener('click', () => { this.applyAutoFix(); }); document.getElementById('ignoreConflicts')?.addEventListener('click', () => { this.ignoreConflicts(); }); // Smart-Empfehlung Events document.getElementById('acceptRecommendation')?.addEventListener('click', () => { this.acceptRecommendation(); }); document.getElementById('dismissRecommendation')?.addEventListener('click', () => { this.dismissRecommendation(); }); // Formular-Validierung Events this.attachFormValidation(); // Verfügbarkeits-Button (falls vorhanden) document.getElementById('refreshAvailability')?.addEventListener('click', () => { this.refreshAvailability(); }); } attachFormValidation() { const formFields = ['eventStart', 'eventEnd', 'eventPrinter', 'eventPriority']; formFields.forEach(fieldId => { const field = document.getElementById(fieldId); if (field) { field.addEventListener('change', () => { this.scheduleValidation(); }); field.addEventListener('input', () => { this.scheduleValidation(); }); } }); } // =================== API CALLS =================== async checkConflicts(eventData) { try { const response = await fetch('/api/calendar/check-conflicts', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(eventData) }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const result = await response.json(); this.lastConflictCheck = result; return result; } catch (error) { console.error('❌ Fehler bei Konfliktprüfung:', error); this.showError('Konfliktprüfung fehlgeschlagen'); return null; } } async resolveConflictsAndCreate(eventData) { try { const response = await fetch('/api/calendar/resolve-conflicts', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({...eventData, auto_resolve: true}) }); if (!response.ok) { const errorData = await response.json(); if (response.status === 409) { this.showConflictModal(errorData); return null; } else { throw new Error(errorData.error || `HTTP ${response.status}`); } } return await response.json(); } catch (error) { console.error('❌ Fehler bei Konfliktlösung:', error); this.showError('Automatische Konfliktlösung fehlgeschlagen'); return null; } } async loadPrinterAvailability(startTime, endTime) { try { const params = new URLSearchParams({ start: startTime, end: endTime }); const response = await fetch(`/api/calendar/printer-availability?${params}`); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); this.displayAvailability(data); return data; } catch (error) { console.error('❌ Fehler beim Laden der Verfügbarkeit:', error); return null; } } async getSmartRecommendation(eventData) { try { const response = await fetch('/api/calendar/smart-recommendation', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(eventData) }); if (!response.ok) return null; const recommendation = await response.json(); if (recommendation.success) { this.displayRecommendation(recommendation.recommendation); this.currentRecommendation = recommendation.recommendation; return recommendation.recommendation; } return null; } catch (error) { console.error('❌ Fehler bei Smart-Empfehlung:', error); return null; } } // =================== UI DISPLAY METHODS =================== showConflictModal(conflictData) { const modal = document.getElementById('conflictNotificationModal'); if (!modal) return; this.updateConflictSummary(conflictData); this.updateConflictDetails(conflictData); this.updateConflictRecommendations(conflictData); modal.classList.remove('hidden'); } hideConflictModal() { const modal = document.getElementById('conflictNotificationModal'); if (modal) { modal.classList.add('hidden'); } } updateConflictSummary(conflictData) { const title = document.getElementById('conflictTitle'); const description = document.getElementById('conflictDescription'); const icon = document.getElementById('conflictIcon'); const summary = document.getElementById('conflictSummary'); if (!title || !description || !icon || !summary) return; if (conflictData.severity_score >= 3) { icon.textContent = '🚨'; title.textContent = 'Kritische Konflikte'; summary.className = summary.className.replace(/amber/g, 'red'); } else { icon.textContent = '⚠️'; title.textContent = 'Konflikte erkannt'; } description.textContent = `${conflictData.conflict_count} Konflikt(e) gefunden. ${conflictData.can_proceed ? 'Automatische Lösung möglich.' : 'Manuelle Anpassung erforderlich.'}`; } updateConflictDetails(conflictData) { const details = document.getElementById('conflictDetails'); if (!details) return; details.innerHTML = ''; if (conflictData.conflicts) { conflictData.conflicts.forEach(conflict => { const conflictEl = this.createConflictElement(conflict); details.appendChild(conflictEl); }); } } updateConflictRecommendations(conflictData) { const recommendations = document.getElementById('smartRecommendations'); if (!recommendations) return; recommendations.innerHTML = ''; if (conflictData.recommendations) { conflictData.recommendations.forEach(rec => { const recEl = this.createRecommendationElement(rec); recommendations.appendChild(recEl); }); } // Auto-Fix Button aktivieren/deaktivieren const autoFixBtn = document.getElementById('applyAutoFix'); if (autoFixBtn) { autoFixBtn.disabled = !conflictData.can_proceed; } } createConflictElement(conflict) { const div = document.createElement('div'); div.className = 'p-3 border border-slate-200 dark:border-slate-600 rounded-lg'; const severityColors = { 'kritisch': 'red', 'hoch': 'orange', 'mittel': 'amber', 'niedrig': 'blue', 'information': 'slate' }; const color = severityColors[conflict.severity] || 'slate'; div.innerHTML = `

${conflict.description}

${conflict.estimated_impact}

${conflict.conflicting_jobs?.length > 0 ? `
Betroffene Jobs: ${conflict.conflicting_jobs.map(job => job.name).join(', ')}
` : ''}
`; return div; } createRecommendationElement(recommendation) { const div = document.createElement('div'); div.className = 'p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg'; let content = `
💡

${recommendation.message}

`; if (recommendation.suggestions) { content += '
'; recommendation.suggestions.forEach(suggestion => { content += `
${suggestion.description || suggestion.printer_name} ${suggestion.confidence ? `${Math.round(suggestion.confidence * 100)}%` : ''}
`; }); content += '
'; } content += `
`; div.innerHTML = content; return div; } displayAvailability(data) { const panel = document.getElementById('printerAvailabilityPanel'); if (!panel) return; const summary = data.summary; // Zusammenfassung aktualisieren this.updateElement('totalPrinters', summary.total_printers); this.updateElement('availablePrinters', summary.available_printers); this.updateElement('optimalPrinters', summary.optimal_printers); this.updateElement('availabilityRate', `${summary.availability_rate}%`); this.updateElement('availabilityTimestamp', `Aktualisiert: ${new Date().toLocaleTimeString()}`); // Drucker-Details aktualisieren const detailsList = document.getElementById('printerDetailsList'); if (detailsList) { detailsList.innerHTML = ''; data.printers.forEach(printer => { const printerEl = document.createElement('div'); printerEl.className = 'flex items-center justify-between p-2 bg-slate-50 dark:bg-slate-700 rounded'; printerEl.innerHTML = `
${printer.availability_icon}
${printer.printer_name}
${printer.location || 'Kein Standort'}
${printer.status_description}
${printer.recent_jobs_24h} Jobs (24h)
`; detailsList.appendChild(printerEl); }); } panel.classList.remove('hidden'); } displayRecommendation(recommendation) { const widget = document.getElementById('smartRecommendationWidget'); if (!widget) return; const text = document.getElementById('recommendationText'); const details = document.getElementById('recommendationDetails'); if (text) { text.textContent = `Drucker: ${recommendation.printer_name}`; } if (details) { details.innerHTML = `
📍 Standort: ${recommendation.location}
📊 Verfügbarkeit: ${recommendation.availability}
⚡ Auslastung: ${recommendation.utilization}
🎯 Eignung: ${recommendation.suitability}
💡 ${recommendation.reason}
`; } widget.classList.remove('hidden'); } // =================== FORM VALIDATION =================== scheduleValidation() { if (this.checkTimeout) { clearTimeout(this.checkTimeout); } this.checkTimeout = setTimeout(() => { this.validateFormRealtime(); }, this.debounceDelay); } async validateFormRealtime() { if (!this.autoCheckEnabled) return; const formData = this.getFormData(); if (!formData.start_time || !formData.end_time) return; // Formular-Hash für Debouncing const formHash = JSON.stringify(formData); if (this.lastFormHash === formHash) return; this.lastFormHash = formHash; try { // Konflikte prüfen const conflicts = await this.checkConflicts(formData); if (conflicts) { this.showInlineConflictWarning(conflicts); } // Smart-Empfehlung anzeigen (wenn kein Drucker ausgewählt) if (!formData.printer_id) { await this.getSmartRecommendation(formData); } } catch (error) { console.error('❌ Fehler bei Echzeit-Validierung:', error); } } showInlineConflictWarning(conflictData) { // Inline-Warnung anzeigen (falls im Template vorhanden) const warning = document.getElementById('conflictWarning'); if (!warning) return; if (conflictData.has_conflicts) { // Warnung anzeigen warning.classList.remove('hidden'); // Nachrichten aktualisieren const messages = document.getElementById('conflictMessages'); if (messages) { messages.innerHTML = ''; conflictData.conflicts.forEach(conflict => { const msgEl = document.createElement('div'); msgEl.textContent = `${conflict.severity}: ${conflict.description}`; messages.appendChild(msgEl); }); } } else { warning.classList.add('hidden'); } } // =================== USER ACTIONS =================== async applyAutoFix() { if (!this.lastConflictCheck) return; const formData = this.getFormData(); const result = await this.resolveConflictsAndCreate(formData); if (result && result.success) { this.hideConflictModal(); this.showSuccess('Konflikte automatisch gelöst und Auftrag erstellt! ✅'); // Kalender aktualisieren if (window.calendar) { window.calendar.refetchEvents(); } // Modal schließen if (window.closeEventModal) { window.closeEventModal(); } } } ignoreConflicts() { this.hideConflictModal(); this.showWarning('Konflikte werden ignoriert. Bitte manuell prüfen.'); } acceptRecommendation() { if (this.currentRecommendation) { const printerSelect = document.getElementById('eventPrinter'); if (printerSelect) { printerSelect.value = this.currentRecommendation.printer_id; this.dismissRecommendation(); this.showSuccess('Empfehlung angenommen! 🎯'); } } } dismissRecommendation() { const widget = document.getElementById('smartRecommendationWidget'); if (widget) { widget.classList.add('hidden'); } this.currentRecommendation = null; } async refreshAvailability() { const now = new Date(); const endTime = new Date(now.getTime() + 24 * 60 * 60 * 1000); // +24h await this.loadPrinterAvailability( now.toISOString(), endTime.toISOString() ); } // =================== HELPER METHODS =================== getFormData() { return { title: this.getFieldValue('eventTitle'), description: this.getFieldValue('eventDescription'), printer_id: this.getFieldValue('eventPrinter') || null, start_time: this.getFieldValue('eventStart'), end_time: this.getFieldValue('eventEnd'), priority: this.getFieldValue('eventPriority') || 'normal' }; } getFieldValue(fieldId) { const field = document.getElementById(fieldId); return field ? field.value : ''; } updateElement(elementId, content) { const element = document.getElementById(elementId); if (element) { element.textContent = content; } } // =================== NOTIFICATIONS =================== showSuccess(message) { this.showNotification(message, 'success'); } showError(message) { this.showNotification(message, 'error'); } showWarning(message) { this.showNotification(message, 'warning'); } showNotification(message, type = 'info') { const colors = { success: 'from-green-500 to-green-600', error: 'from-red-500 to-red-600', warning: 'from-amber-500 to-amber-600', info: 'from-blue-500 to-blue-600' }; const icons = { success: '', error: '', warning: '', info: '' }; const toast = document.createElement('div'); toast.className = `fixed top-4 right-4 bg-gradient-to-r ${colors[type]} text-white px-6 py-4 rounded-lg shadow-xl z-50 transform transition-all duration-300 translate-x-full`; toast.innerHTML = `
${icons[type]}
${message}
`; document.body.appendChild(toast); setTimeout(() => toast.classList.remove('translate-x-full'), 100); setTimeout(() => { toast.classList.add('translate-x-full'); setTimeout(() => toast.remove(), 300); }, 4000); } } // Globale Instanz für einfache Nutzung window.conflictManager = new ConflictManager(); // Integration mit bestehenden Kalender-Funktionen document.addEventListener('DOMContentLoaded', function() { console.log('✅ ConflictManager erfolgreich geladen'); // Integration mit bestehendem Formular const existingForm = document.getElementById('eventForm'); if (existingForm) { // Existing form handler erweitern existingForm.addEventListener('submit', async function(e) { const formData = window.conflictManager.getFormData(); // Konflikte vor Submit prüfen const conflicts = await window.conflictManager.checkConflicts(formData); if (conflicts && conflicts.has_conflicts && !conflicts.can_proceed) { e.preventDefault(); window.conflictManager.showConflictModal(conflicts); return false; } }); } });