794 lines
32 KiB
JavaScript
794 lines
32 KiB
JavaScript
/**
|
|
* MYP Platform - Erweiterte Optimierungs- und Batch-Funktionen
|
|
* Implementiert Auto-Optimierung und Batch-Planung für 3D-Druckaufträge
|
|
*/
|
|
|
|
class OptimizationManager {
|
|
constructor() {
|
|
this.isAutoOptimizationEnabled = false;
|
|
this.isBatchModeEnabled = false;
|
|
this.selectedJobs = new Set();
|
|
this.optimizationSettings = {
|
|
algorithm: 'round_robin', // 'round_robin', 'load_balance', 'priority_based'
|
|
considerDistance: true,
|
|
minimizeChangeover: true,
|
|
maxBatchSize: 10,
|
|
timeWindow: 24 // Stunden
|
|
};
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.setupEventListeners();
|
|
this.loadSavedSettings();
|
|
this.updateUI();
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Keyboard shortcuts
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.ctrlKey && e.altKey && e.key === 'O') {
|
|
this.toggleAutoOptimization();
|
|
e.preventDefault();
|
|
}
|
|
if (e.ctrlKey && e.altKey && e.key === 'B') {
|
|
this.toggleBatchMode();
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Auto-Optimierung ein-/ausschalten
|
|
* Diese Funktion optimiert automatisch die Druckreihenfolge basierend auf verschiedenen Faktoren
|
|
*/
|
|
toggleAutoOptimization() {
|
|
this.isAutoOptimizationEnabled = !this.isAutoOptimizationEnabled;
|
|
|
|
const button = document.getElementById('auto-opt-toggle');
|
|
if (button) {
|
|
this.updateAutoOptimizationButton(button);
|
|
}
|
|
|
|
// Settings speichern
|
|
localStorage.setItem('myp-auto-optimization', this.isAutoOptimizationEnabled);
|
|
|
|
// Notification anzeigen
|
|
this.showOptimizationNotification(
|
|
this.isAutoOptimizationEnabled ? 'aktiviert' : 'deaktiviert',
|
|
'auto-optimization'
|
|
);
|
|
|
|
// Wenn aktiviert, sofortige Optimierung durchführen
|
|
if (this.isAutoOptimizationEnabled) {
|
|
this.performAutoOptimization();
|
|
}
|
|
|
|
// UI aktualisieren
|
|
this.updateUI();
|
|
}
|
|
|
|
updateAutoOptimizationButton(button) {
|
|
const span = button.querySelector('span');
|
|
const icon = button.querySelector('svg');
|
|
|
|
if (this.isAutoOptimizationEnabled) {
|
|
button.classList.remove('btn-secondary');
|
|
button.classList.add('btn-primary');
|
|
span.textContent = 'Auto-Optimierung AN';
|
|
|
|
// Button-Animation
|
|
button.style.transform = 'scale(1.05)';
|
|
setTimeout(() => {
|
|
button.style.transform = '';
|
|
}, 200);
|
|
|
|
// Icon-Animation
|
|
icon.style.animation = 'spin 1s ease-in-out';
|
|
setTimeout(() => {
|
|
icon.style.animation = '';
|
|
}, 1000);
|
|
} else {
|
|
button.classList.remove('btn-primary');
|
|
button.classList.add('btn-secondary');
|
|
span.textContent = 'Auto-Optimierung';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Batch-Modus ein-/ausschalten
|
|
* Ermöglicht die Auswahl mehrerer Jobs für Batch-Operationen
|
|
*/
|
|
toggleBatchMode() {
|
|
this.isBatchModeEnabled = !this.isBatchModeEnabled;
|
|
|
|
const button = document.getElementById('batch-toggle');
|
|
if (button) {
|
|
this.updateBatchModeButton(button);
|
|
}
|
|
|
|
// Batch-Funktionalität aktivieren/deaktivieren
|
|
this.toggleBatchSelection();
|
|
|
|
// Settings speichern
|
|
localStorage.setItem('myp-batch-mode', this.isBatchModeEnabled);
|
|
|
|
// Notification anzeigen
|
|
this.showOptimizationNotification(
|
|
this.isBatchModeEnabled ? 'aktiviert' : 'deaktiviert',
|
|
'batch-mode'
|
|
);
|
|
|
|
// UI aktualisieren
|
|
this.updateUI();
|
|
}
|
|
|
|
updateBatchModeButton(button) {
|
|
const span = button.querySelector('span');
|
|
|
|
if (this.isBatchModeEnabled) {
|
|
button.classList.remove('btn-secondary');
|
|
button.classList.add('btn-warning');
|
|
span.textContent = `Batch-Modus (${this.selectedJobs.size})`;
|
|
|
|
// Button-Animation
|
|
button.style.transform = 'scale(1.05)';
|
|
setTimeout(() => {
|
|
button.style.transform = '';
|
|
}, 200);
|
|
} else {
|
|
button.classList.remove('btn-warning');
|
|
button.classList.add('btn-secondary');
|
|
span.textContent = 'Mehrfachauswahl';
|
|
|
|
// Auswahl zurücksetzen
|
|
this.selectedJobs.clear();
|
|
}
|
|
}
|
|
|
|
toggleBatchSelection() {
|
|
const jobCards = document.querySelectorAll('.job-card, [data-job-id]');
|
|
|
|
jobCards.forEach(card => {
|
|
if (this.isBatchModeEnabled) {
|
|
this.enableBatchSelection(card);
|
|
} else {
|
|
this.disableBatchSelection(card);
|
|
}
|
|
});
|
|
}
|
|
|
|
enableBatchSelection(card) {
|
|
// Checkbox hinzufügen
|
|
let checkbox = card.querySelector('.batch-checkbox');
|
|
if (!checkbox) {
|
|
checkbox = document.createElement('input');
|
|
checkbox.type = 'checkbox';
|
|
checkbox.className = 'batch-checkbox absolute top-3 left-3 w-5 h-5 rounded border-2 border-gray-300 text-blue-600 focus:ring-blue-500';
|
|
checkbox.style.zIndex = '10';
|
|
|
|
// Event Listener für Checkbox
|
|
checkbox.addEventListener('change', (e) => {
|
|
const jobId = card.dataset.jobId;
|
|
if (e.target.checked) {
|
|
this.selectedJobs.add(jobId);
|
|
card.classList.add('selected-for-batch');
|
|
} else {
|
|
this.selectedJobs.delete(jobId);
|
|
card.classList.remove('selected-for-batch');
|
|
}
|
|
this.updateBatchCounter();
|
|
});
|
|
|
|
// Checkbox in die Karte einfügen
|
|
card.style.position = 'relative';
|
|
card.appendChild(checkbox);
|
|
}
|
|
|
|
checkbox.style.display = 'block';
|
|
card.classList.add('batch-selectable');
|
|
}
|
|
|
|
disableBatchSelection(card) {
|
|
const checkbox = card.querySelector('.batch-checkbox');
|
|
if (checkbox) {
|
|
checkbox.style.display = 'none';
|
|
}
|
|
card.classList.remove('batch-selectable', 'selected-for-batch');
|
|
}
|
|
|
|
updateBatchCounter() {
|
|
const button = document.getElementById('batch-toggle');
|
|
if (button && this.isBatchModeEnabled) {
|
|
const span = button.querySelector('span');
|
|
span.textContent = `Batch-Modus (${this.selectedJobs.size})`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Automatische Optimierung durchführen
|
|
*/
|
|
async performAutoOptimization() {
|
|
try {
|
|
// Ladeanimation anzeigen
|
|
this.showOptimizationLoading();
|
|
|
|
const response = await fetch('/api/optimization/auto-optimize', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': this.getCSRFToken()
|
|
},
|
|
body: JSON.stringify({
|
|
settings: this.optimizationSettings,
|
|
enabled: this.isAutoOptimizationEnabled
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
// Ladeanimation ausblenden
|
|
this.hideOptimizationLoading();
|
|
|
|
if (data.success) {
|
|
// Belohnendes animiertes Modal anzeigen
|
|
this.showRewardModal(data);
|
|
this.refreshCurrentView();
|
|
} else {
|
|
this.showErrorMessage(`Optimierung fehlgeschlagen: ${data.error}`);
|
|
}
|
|
} catch (error) {
|
|
this.hideOptimizationLoading();
|
|
console.error('Auto-Optimierung Fehler:', error);
|
|
this.showErrorMessage('Netzwerkfehler bei der Auto-Optimierung');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Belohnendes animiertes Modal für erfolgreiche Optimierung
|
|
*/
|
|
showRewardModal(data) {
|
|
// Existing Modal entfernen falls vorhanden
|
|
const existingModal = document.getElementById('optimization-reward-modal');
|
|
if (existingModal) {
|
|
existingModal.remove();
|
|
}
|
|
|
|
const modal = document.createElement('div');
|
|
modal.id = 'optimization-reward-modal';
|
|
modal.className = 'fixed inset-0 bg-black/70 backdrop-blur-md z-50 flex items-center justify-center p-4 animate-fade-in';
|
|
|
|
// Erfolgs-Emojis basierend auf Anzahl der optimierten Jobs
|
|
const optimizedCount = data.optimized_jobs || 0;
|
|
let celebration = '🎉';
|
|
let message = 'Optimierung erfolgreich!';
|
|
|
|
if (optimizedCount === 0) {
|
|
celebration = '✅';
|
|
message = 'System bereits optimal!';
|
|
} else if (optimizedCount <= 3) {
|
|
celebration = '🚀';
|
|
message = 'Kleine Verbesserungen durchgeführt!';
|
|
} else if (optimizedCount <= 10) {
|
|
celebration = '⚡';
|
|
message = 'Deutliche Optimierung erreicht!';
|
|
} else {
|
|
celebration = '💎';
|
|
message = 'Exzellente Optimierung!';
|
|
}
|
|
|
|
modal.innerHTML = `
|
|
<div class="relative bg-white dark:bg-slate-800 rounded-2xl shadow-2xl max-w-md w-full p-8 transform animate-bounce-in">
|
|
<!-- Konfetti Animation -->
|
|
<div class="absolute inset-0 pointer-events-none overflow-hidden rounded-2xl">
|
|
<div class="confetti-container">
|
|
${this.generateConfetti()}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Schließen Button -->
|
|
<button onclick="this.closest('#optimization-reward-modal').remove()"
|
|
class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 transition-colors z-10">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Haupt-Content -->
|
|
<div class="text-center relative z-10">
|
|
<!-- Animiertes Erfolgs-Icon -->
|
|
<div class="relative mb-6">
|
|
<div class="text-8xl animate-pulse-scale mx-auto">${celebration}</div>
|
|
<div class="absolute -top-2 -right-2 text-4xl animate-float">✨</div>
|
|
<div class="absolute -bottom-2 -left-2 text-3xl animate-float-delay">⭐</div>
|
|
</div>
|
|
|
|
<!-- Erfolgs-Nachricht -->
|
|
<h2 class="text-2xl font-bold text-slate-800 dark:text-white mb-2 animate-slide-up">
|
|
${message}
|
|
</h2>
|
|
|
|
<!-- Statistiken -->
|
|
<div class="bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20
|
|
rounded-xl p-6 mb-6 animate-slide-up-delay">
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div class="text-center">
|
|
<div class="text-3xl font-bold text-green-600 dark:text-green-400 animate-count-up">
|
|
${optimizedCount}
|
|
</div>
|
|
<div class="text-sm text-green-700 dark:text-green-300 font-medium">
|
|
Jobs optimiert
|
|
</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="text-lg font-semibold text-slate-700 dark:text-slate-300 capitalize">
|
|
${data.algorithm?.replace('_', ' ') || 'Standard'}
|
|
</div>
|
|
<div class="text-sm text-slate-600 dark:text-slate-400">
|
|
Algorithmus
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Belohnungs-Badge -->
|
|
<div class="inline-flex items-center gap-2 bg-gradient-to-r from-blue-500 to-purple-600
|
|
text-white px-6 py-3 rounded-full text-sm font-semibold mb-6 animate-glow">
|
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
|
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
|
|
</svg>
|
|
Effizienz-Boost erreicht!
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="flex gap-3">
|
|
<button onclick="this.closest('#optimization-reward-modal').remove()"
|
|
class="flex-1 bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300
|
|
py-3 px-6 rounded-xl font-semibold transition-all hover:bg-slate-200
|
|
dark:hover:bg-slate-600 animate-slide-up-delay-2">
|
|
Schließen
|
|
</button>
|
|
<button onclick="optimizationManager.showOptimizationSettings();
|
|
this.closest('#optimization-reward-modal').remove();"
|
|
class="flex-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white
|
|
py-3 px-6 rounded-xl font-semibold transition-all hover:from-blue-600
|
|
hover:to-blue-700 animate-slide-up-delay-3">
|
|
Einstellungen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Modal zum DOM hinzufügen
|
|
document.body.appendChild(modal);
|
|
|
|
// Sound-Effekt (optional)
|
|
this.playSuccessSound();
|
|
|
|
// Auto-Close nach 20 Sekunden (verlängert für bessere Animation-Wirkung)
|
|
setTimeout(() => {
|
|
if (modal && modal.parentNode) {
|
|
modal.style.opacity = '0';
|
|
modal.style.transform = 'scale(0.95)';
|
|
setTimeout(() => modal.remove(), 300);
|
|
}
|
|
}, 20000);
|
|
}
|
|
|
|
/**
|
|
* Konfetti-Animation generieren
|
|
*/
|
|
generateConfetti() {
|
|
const colors = ['#FFD700', '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7'];
|
|
let confetti = '';
|
|
|
|
for (let i = 0; i < 50; i++) {
|
|
const color = colors[Math.floor(Math.random() * colors.length)];
|
|
const delay = Math.random() * 5;
|
|
const duration = 4 + Math.random() * 3;
|
|
const left = Math.random() * 100;
|
|
|
|
confetti += `
|
|
<div class="confetti-piece" style="
|
|
background-color: ${color};
|
|
left: ${left}%;
|
|
animation-delay: ${delay}s;
|
|
animation-duration: ${duration}s;
|
|
"></div>
|
|
`;
|
|
}
|
|
|
|
return confetti;
|
|
}
|
|
|
|
/**
|
|
* Ladeanimation für Optimierung anzeigen
|
|
*/
|
|
showOptimizationLoading() {
|
|
const loader = document.createElement('div');
|
|
loader.id = 'optimization-loader';
|
|
loader.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm z-40 flex items-center justify-center';
|
|
loader.innerHTML = `
|
|
<div class="bg-white dark:bg-slate-800 rounded-2xl p-8 text-center max-w-sm mx-4 shadow-2xl">
|
|
<div class="relative mb-6">
|
|
<div class="w-16 h-16 mx-auto">
|
|
<svg class="animate-spin text-blue-500" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="absolute -top-1 -right-1 text-2xl animate-bounce">⚡</div>
|
|
</div>
|
|
<h3 class="text-xl font-semibold text-slate-800 dark:text-white mb-2">
|
|
Optimierung läuft...
|
|
</h3>
|
|
<p class="text-slate-600 dark:text-slate-400">
|
|
Jobs werden intelligent verteilt
|
|
</p>
|
|
</div>
|
|
`;
|
|
document.body.appendChild(loader);
|
|
}
|
|
|
|
/**
|
|
* Ladeanimation ausblenden
|
|
*/
|
|
hideOptimizationLoading() {
|
|
const loader = document.getElementById('optimization-loader');
|
|
if (loader) {
|
|
loader.style.opacity = '0';
|
|
setTimeout(() => loader.remove(), 200);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Erfolgs-Sound abspielen (optional)
|
|
*/
|
|
playSuccessSound() {
|
|
try {
|
|
// Erstelle einen kurzen, angenehmen Ton
|
|
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
const oscillator = audioContext.createOscillator();
|
|
const gainNode = audioContext.createGain();
|
|
|
|
oscillator.connect(gainNode);
|
|
gainNode.connect(audioContext.destination);
|
|
|
|
oscillator.frequency.setValueAtTime(523.25, audioContext.currentTime); // C5
|
|
oscillator.frequency.setValueAtTime(659.25, audioContext.currentTime + 0.1); // E5
|
|
oscillator.frequency.setValueAtTime(783.99, audioContext.currentTime + 0.2); // G5
|
|
|
|
gainNode.gain.setValueAtTime(0.1, audioContext.currentTime);
|
|
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
|
|
|
|
oscillator.start(audioContext.currentTime);
|
|
oscillator.stop(audioContext.currentTime + 0.5);
|
|
} catch (e) {
|
|
// Sound nicht verfügbar, ignorieren
|
|
console.log('Audio-API nicht verfügbar');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Batch-Operationen durchführen
|
|
*/
|
|
async performBatchOperation(operation) {
|
|
if (this.selectedJobs.size === 0) {
|
|
this.showWarningMessage('Keine Jobs für Batch-Operation ausgewählt');
|
|
return;
|
|
}
|
|
|
|
const jobIds = Array.from(this.selectedJobs);
|
|
|
|
try {
|
|
const response = await fetch('/api/jobs/batch-operation', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': this.getCSRFToken()
|
|
},
|
|
body: JSON.stringify({
|
|
job_ids: jobIds,
|
|
operation: operation
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
this.showSuccessMessage(`Batch-Operation "${operation}" erfolgreich auf ${jobIds.length} Jobs angewendet`);
|
|
this.selectedJobs.clear();
|
|
this.updateBatchCounter();
|
|
this.refreshCurrentView();
|
|
} else {
|
|
this.showErrorMessage(`Batch-Operation fehlgeschlagen: ${data.error}`);
|
|
}
|
|
} catch (error) {
|
|
console.error('Batch-Operation Fehler:', error);
|
|
this.showErrorMessage('Netzwerkfehler bei der Batch-Operation');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Optimierungs-Einstellungen konfigurieren
|
|
*/
|
|
showOptimizationSettings() {
|
|
this.createOptimizationModal();
|
|
}
|
|
|
|
createOptimizationModal() {
|
|
const modal = document.createElement('div');
|
|
modal.id = 'optimization-settings-modal';
|
|
modal.className = 'fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4';
|
|
|
|
modal.innerHTML = `
|
|
<div class="dashboard-card max-w-2xl w-full p-8 transform transition-all duration-300">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<div>
|
|
<h3 class="text-xl font-bold text-slate-900 dark:text-white mb-2">
|
|
Optimierungs-Einstellungen
|
|
</h3>
|
|
<p class="text-slate-500 dark:text-slate-400">
|
|
Konfigurieren Sie die automatische Optimierung für maximale Effizienz
|
|
</p>
|
|
</div>
|
|
<button onclick="this.closest('#optimization-settings-modal').remove()"
|
|
class="p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors">
|
|
<svg class="w-6 h-6 text-slate-500 dark:text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="space-y-6">
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
|
Optimierungs-Algorithmus
|
|
</label>
|
|
<select id="optimization-algorithm" class="block w-full px-3 py-2 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-white">
|
|
<option value="round_robin">Round Robin - Gleichmäßige Verteilung</option>
|
|
<option value="load_balance">Load Balancing - Auslastungsoptimierung</option>
|
|
<option value="priority_based">Prioritätsbasiert - Wichtige Jobs zuerst</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="flex items-center">
|
|
<input type="checkbox" id="consider-distance" class="mr-2">
|
|
<span class="text-sm text-slate-700 dark:text-slate-300">
|
|
Druckerentfernung berücksichtigen
|
|
</span>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label class="flex items-center">
|
|
<input type="checkbox" id="minimize-changeover" class="mr-2">
|
|
<span class="text-sm text-slate-700 dark:text-slate-300">
|
|
Rüstzeiten minimieren
|
|
</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
|
Max. Batch-Größe
|
|
</label>
|
|
<input type="number" id="max-batch-size" min="1" max="50"
|
|
class="block w-full px-3 py-2 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-white">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
|
Planungshorizont (Stunden)
|
|
</label>
|
|
<input type="number" id="time-window" min="1" max="168"
|
|
class="block w-full px-3 py-2 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-white">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-end gap-3 pt-6 border-t border-gray-200 dark:border-slate-600 mt-6">
|
|
<button onclick="this.closest('#optimization-settings-modal').remove()"
|
|
class="btn-secondary">
|
|
Abbrechen
|
|
</button>
|
|
<button onclick="optimizationManager.saveOptimizationSettings()"
|
|
class="btn-primary">
|
|
Einstellungen speichern
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(modal);
|
|
this.loadOptimizationSettingsInModal();
|
|
}
|
|
|
|
loadOptimizationSettingsInModal() {
|
|
document.getElementById('optimization-algorithm').value = this.optimizationSettings.algorithm;
|
|
document.getElementById('consider-distance').checked = this.optimizationSettings.considerDistance;
|
|
document.getElementById('minimize-changeover').checked = this.optimizationSettings.minimizeChangeover;
|
|
document.getElementById('max-batch-size').value = this.optimizationSettings.maxBatchSize;
|
|
document.getElementById('time-window').value = this.optimizationSettings.timeWindow;
|
|
}
|
|
|
|
saveOptimizationSettings() {
|
|
this.optimizationSettings.algorithm = document.getElementById('optimization-algorithm').value;
|
|
this.optimizationSettings.considerDistance = document.getElementById('consider-distance').checked;
|
|
this.optimizationSettings.minimizeChangeover = document.getElementById('minimize-changeover').checked;
|
|
this.optimizationSettings.maxBatchSize = parseInt(document.getElementById('max-batch-size').value);
|
|
this.optimizationSettings.timeWindow = parseInt(document.getElementById('time-window').value);
|
|
|
|
localStorage.setItem('myp-optimization-settings', JSON.stringify(this.optimizationSettings));
|
|
|
|
document.getElementById('optimization-settings-modal').remove();
|
|
this.showSuccessMessage('Optimierungs-Einstellungen gespeichert');
|
|
|
|
// Wenn Auto-Optimierung aktiv ist, neue Optimierung durchführen
|
|
if (this.isAutoOptimizationEnabled) {
|
|
this.performAutoOptimization();
|
|
}
|
|
}
|
|
|
|
loadSavedSettings() {
|
|
// Auto-Optimierung Status laden
|
|
const savedAutoOpt = localStorage.getItem('myp-auto-optimization');
|
|
if (savedAutoOpt !== null) {
|
|
this.isAutoOptimizationEnabled = savedAutoOpt === 'true';
|
|
}
|
|
|
|
// Batch-Modus Status laden
|
|
const savedBatchMode = localStorage.getItem('myp-batch-mode');
|
|
if (savedBatchMode !== null) {
|
|
this.isBatchModeEnabled = savedBatchMode === 'true';
|
|
}
|
|
|
|
// Optimierungs-Einstellungen laden
|
|
const savedSettings = localStorage.getItem('myp-optimization-settings');
|
|
if (savedSettings) {
|
|
try {
|
|
this.optimizationSettings = { ...this.optimizationSettings, ...JSON.parse(savedSettings) };
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der Optimierungs-Einstellungen:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
updateUI() {
|
|
// Buttons aktualisieren
|
|
const autoOptButton = document.getElementById('auto-opt-toggle');
|
|
if (autoOptButton) {
|
|
this.updateAutoOptimizationButton(autoOptButton);
|
|
}
|
|
|
|
const batchButton = document.getElementById('batch-toggle');
|
|
if (batchButton) {
|
|
this.updateBatchModeButton(batchButton);
|
|
}
|
|
|
|
// Batch-Auswahl aktualisieren
|
|
if (this.isBatchModeEnabled) {
|
|
this.toggleBatchSelection();
|
|
}
|
|
}
|
|
|
|
// Utility-Funktionen
|
|
getCSRFToken() {
|
|
const token = document.querySelector('meta[name="csrf-token"]');
|
|
return token ? token.getAttribute('content') : '';
|
|
}
|
|
|
|
refreshCurrentView() {
|
|
// Je nach aktueller Seite entsprechende Refresh-Funktion aufrufen
|
|
if (typeof refreshJobs === 'function') {
|
|
refreshJobs();
|
|
} else if (typeof refreshCalendar === 'function') {
|
|
refreshCalendar();
|
|
} else if (typeof refreshDashboard === 'function') {
|
|
refreshDashboard();
|
|
}
|
|
}
|
|
|
|
showOptimizationNotification(status, type) {
|
|
const messages = {
|
|
'auto-optimization': {
|
|
'aktiviert': '🚀 Auto-Optimierung aktiviert - Jobs werden automatisch optimiert',
|
|
'deaktiviert': '⏸️ Auto-Optimierung deaktiviert'
|
|
},
|
|
'batch-mode': {
|
|
'aktiviert': '📦 Batch-Modus aktiviert - Wählen Sie Jobs für Batch-Operationen aus',
|
|
'deaktiviert': '✅ Batch-Modus deaktiviert'
|
|
}
|
|
};
|
|
|
|
const message = messages[type]?.[status] || `${type} ${status}`;
|
|
this.showSuccessMessage(message);
|
|
}
|
|
|
|
showSuccessMessage(message) {
|
|
this.showToast(message, 'success');
|
|
}
|
|
|
|
showErrorMessage(message) {
|
|
this.showToast(message, 'error');
|
|
}
|
|
|
|
showWarningMessage(message) {
|
|
this.showToast(message, 'warning');
|
|
}
|
|
|
|
showToast(message, type = 'info') {
|
|
// Einfache Toast-Benachrichtigung
|
|
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 5 Sekunden automatisch entfernen
|
|
setTimeout(() => {
|
|
toast.classList.add('translate-x-full');
|
|
setTimeout(() => {
|
|
toast.remove();
|
|
}, 300);
|
|
}, 5000);
|
|
}
|
|
}
|
|
|
|
// Globale Funktionen für Template-Integration
|
|
let optimizationManager;
|
|
|
|
// Auto-Optimierung umschalten
|
|
window.toggleAutoOptimization = function() {
|
|
if (!optimizationManager) {
|
|
optimizationManager = new OptimizationManager();
|
|
}
|
|
optimizationManager.toggleAutoOptimization();
|
|
};
|
|
|
|
// Batch-Modus umschalten
|
|
window.toggleBatchMode = function() {
|
|
if (!optimizationManager) {
|
|
optimizationManager = new OptimizationManager();
|
|
}
|
|
optimizationManager.toggleBatchMode();
|
|
};
|
|
|
|
// Batch-Planung Modal öffnen
|
|
window.openBatchPlanningModal = function() {
|
|
if (!optimizationManager) {
|
|
optimizationManager = new OptimizationManager();
|
|
}
|
|
optimizationManager.showOptimizationSettings();
|
|
};
|
|
|
|
// Optimierungs-Einstellungen anzeigen
|
|
window.showOptimizationSettings = function() {
|
|
if (!optimizationManager) {
|
|
optimizationManager = new OptimizationManager();
|
|
}
|
|
optimizationManager.showOptimizationSettings();
|
|
};
|
|
|
|
// Initialisierung beim Laden der Seite
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
optimizationManager = new OptimizationManager();
|
|
console.log('🎯 Optimierungs-Manager initialisiert');
|
|
});
|