From 3cab66efc86b7208e202740714ddc16f3d67fa31 Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Mon, 2 Jun 2025 14:26:33 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9A=20Improved=20backend=20structure?= =?UTF-8?q?=20&=20logs,=20added=20conflict=20manager=20=F0=9F=8C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/__pycache__/models.cpython-313.pyc | Bin 71816 -> 84652 bytes .../__pycache__/calendar.cpython-313.pyc | Bin 39875 -> 60720 bytes backend/logs/app/app.log | 1 + backend/logs/scheduler/scheduler.log | 1 + backend/logs/windows_fixes/windows_fixes.log | 4 + backend/setup.sh | 12 +- backend/static/js/conflict-manager.js | 741 ++++++++++++++++++ backend/templates/calendar.html | 16 + .../conflict_manager.cpython-313.pyc | Bin 0 -> 26656 bytes backend/utils/conflict_manager.py | 3 +- 10 files changed, 773 insertions(+), 5 deletions(-) create mode 100644 backend/static/js/conflict-manager.js create mode 100644 backend/utils/__pycache__/conflict_manager.cpython-313.pyc diff --git a/backend/__pycache__/models.cpython-313.pyc b/backend/__pycache__/models.cpython-313.pyc index 1e98837a0144940a861688c3155088d7dcf76ef2..a7e16c713d240e8c3a262a2858f716c2504efba9 100644 GIT binary patch delta 20468 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 diff --git a/backend/blueprints/__pycache__/calendar.cpython-313.pyc b/backend/blueprints/__pycache__/calendar.cpython-313.pyc index 11a6dc11f55c46114c5f491bcbd676e59c17f896..7cf8c8606b50ae0a200727868ea73af1bb0d04a9 100644 GIT binary patch delta 20119 zcmcJ13v^T0mG-@oY{|AP$(CfvvMgOmen@`E*v3zu#^49ofPE1d3^Br%Z5bnzE1M*4 zQl;t3lbKARxA_B2voLL@n5Hw3&}m4T{vks;IL$QCMNlbB=8CtPI1@wdmslw_YrnZcge547WA+=6(?G}L7B<^JaaOjB%`s3D zG-un-^mp@pQgx}E&8?wXMT=;^f>j!*hFF2*Cs&%xsY=V4cbA-H$(;FVIn{T`S)R

TIc*P5u6y&F}Q`V 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 diff --git a/backend/logs/app/app.log b/backend/logs/app/app.log index 0e50bae6..5f6c5e14 100644 --- a/backend/logs/app/app.log +++ b/backend/logs/app/app.log @@ -2765,3 +2765,4 @@ WHERE jobs.status = ?) AS anon_1] 2025-06-02 10:56:52 - [app] app - [INFO] INFO - Dashboard-Refresh angefordert von User 1 2025-06-02 10:56:52 - [app] app - [INFO] INFO - Dashboard-Refresh erfolgreich: {'active_jobs': 0, 'available_printers': 2, 'total_jobs': 16, 'pending_jobs': 0, 'success_rate': 0.0, 'completed_jobs': 0, 'failed_jobs': 0, 'cancelled_jobs': None, 'total_users': 1, 'online_printers': 0, 'offline_printers': 2} 2025-06-02 10:56:52 - [app] app - [INFO] INFO - Dashboard-Refresh erfolgreich: {'active_jobs': 0, 'available_printers': None, 'total_jobs': 16, 'pending_jobs': 0, 'success_rate': 0.0, 'completed_jobs': 0, 'failed_jobs': 0, 'cancelled_jobs': 0, 'total_users': 1, 'online_printers': 0, 'offline_printers': 2} +2025-06-02 14:25:35 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db diff --git a/backend/logs/scheduler/scheduler.log b/backend/logs/scheduler/scheduler.log index a1f4e925..71ccb375 100644 --- a/backend/logs/scheduler/scheduler.log +++ b/backend/logs/scheduler/scheduler.log @@ -27181,3 +27181,4 @@ 2025-06-02 10:57:11 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) 2025-06-02 10:57:11 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 14 nicht einschalten 2025-06-02 10:57:11 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 15: test +2025-06-02 14:25:36 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True diff --git a/backend/logs/windows_fixes/windows_fixes.log b/backend/logs/windows_fixes/windows_fixes.log index 8954ea6d..ee665b56 100644 --- a/backend/logs/windows_fixes/windows_fixes.log +++ b/backend/logs/windows_fixes/windows_fixes.log @@ -451,3 +451,7 @@ 2025-06-02 10:02:51 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen) 2025-06-02 10:02:51 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet 2025-06-02 10:02:51 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet +2025-06-02 14:25:35 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an... +2025-06-02 14:25:35 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen) +2025-06-02 14:25:35 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet +2025-06-02 14:25:35 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet diff --git a/backend/setup.sh b/backend/setup.sh index 0b08e800..b8cb2bae 100644 --- a/backend/setup.sh +++ b/backend/setup.sh @@ -117,17 +117,23 @@ check_system_resources() { local ram_mb=$(free -m | awk '/^Mem:/{print $2}') progress "Verfügbarer RAM: ${ram_mb}MB" - if [ "$ram_mb" -lt 512 ]; then + if [ -n "$ram_mb" ] && [ "$ram_mb" -eq "$ram_mb" ] 2>/dev/null && [ "$ram_mb" -lt 512 ]; then warning "⚠️ Wenig RAM verfügbar (${ram_mb}MB) - Installation könnte langsam sein" - else + elif [ -n "$ram_mb" ] && [ "$ram_mb" -eq "$ram_mb" ] 2>/dev/null; then success "✅ Ausreichend RAM verfügbar (${ram_mb}MB)" + else + warning "⚠️ RAM-Größe konnte nicht ermittelt werden" fi # Festplattenplatz prüfen local disk_free_gb=$(df / | awk 'NR==2{printf "%.1f", $4/1024/1024}') progress "Verfügbarer Festplattenplatz: ${disk_free_gb}GB" - if [ "$(echo "$disk_free_gb < 2.0" | bc 2>/dev/null || echo "0")" -eq 1 ]; then + # Konvertiere zu Ganzzahl für Vergleich (GB * 10 für eine Dezimalstelle) + local disk_free_int=$(echo "$disk_free_gb * 10" | bc 2>/dev/null | cut -d. -f1) + local min_required_int=20 # 2.0 GB * 10 + + if [ -z "$disk_free_int" ] || [ "$disk_free_int" -lt "$min_required_int" ]; then warning "⚠️ Wenig Festplattenplatz verfügbar (${disk_free_gb}GB)" else success "✅ Ausreichend Festplattenplatz verfügbar (${disk_free_gb}GB)" diff --git a/backend/static/js/conflict-manager.js b/backend/static/js/conflict-manager.js new file mode 100644 index 00000000..11c835a6 --- /dev/null +++ b/backend/static/js/conflict-manager.js @@ -0,0 +1,741 @@ +/** + * 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; + } + }); + } +}); \ No newline at end of file diff --git a/backend/templates/calendar.html b/backend/templates/calendar.html index 0a21abf8..99923808 100644 --- a/backend/templates/calendar.html +++ b/backend/templates/calendar.html @@ -818,6 +818,11 @@ Produktionseinheit +