mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 06:55:45 +01:00
added help site describing the shift assigment algorithm
This commit is contained in:
@@ -1,26 +1,418 @@
|
|||||||
// frontend/src/pages/Help/Help.tsx
|
// frontend/src/pages/Help/Help.tsx
|
||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
const Help: React.FC = () => {
|
const Help: React.FC = () => {
|
||||||
|
const [currentStage, setCurrentStage] = useState(0);
|
||||||
|
const [isAnimating, setIsAnimating] = useState(false);
|
||||||
|
|
||||||
|
const algorithmStages = [
|
||||||
|
{
|
||||||
|
title: "📊 Phase A: Reguläre Mitarbeiterplanung",
|
||||||
|
description: "Zuweisung aller Mitarbeiter außer Manager",
|
||||||
|
steps: [
|
||||||
|
"Grundabdeckung: Mindestens 1 Mitarbeiter pro Schicht",
|
||||||
|
"Erfahrene Mitarbeiter werden bevorzugt",
|
||||||
|
"Verhindere 'Neu allein' Situationen",
|
||||||
|
"Fülle Schichten bis zur Zielbesetzung"
|
||||||
|
],
|
||||||
|
color: "#3498db"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "👑 Phase B: Manager-Einfügung",
|
||||||
|
description: "Manager wird seinen bevorzugten Schichten zugewiesen",
|
||||||
|
steps: [
|
||||||
|
"Manager wird festen Schichten zugewiesen",
|
||||||
|
"Erfahrene Mitarbeiter werden zu Manager-Schichten hinzugefügt",
|
||||||
|
"Bei Problemen: Austausch oder Bewegung von Mitarbeitern",
|
||||||
|
"Fallback: Nicht-erfahrene als Backup"
|
||||||
|
],
|
||||||
|
color: "#e74c3c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "🔧 Phase C: Reparatur & Validierung",
|
||||||
|
description: "Probleme erkennen und automatisch beheben",
|
||||||
|
steps: [
|
||||||
|
"Überbesetzte erfahrene Mitarbeiter identifizieren",
|
||||||
|
"Mitarbeiter-Pool für Neuverteilung erstellen",
|
||||||
|
"Priorisierte Zuweisung zu Problem-Schichten",
|
||||||
|
"Finale Validierung aller Geschäftsregeln"
|
||||||
|
],
|
||||||
|
color: "#2ecc71"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "✅ Finale Prüfung",
|
||||||
|
description: "Zusammenfassung und Freigabe",
|
||||||
|
steps: [
|
||||||
|
"Reparatur-Bericht generieren",
|
||||||
|
"Kritische vs. nicht-kritische Probleme klassifizieren",
|
||||||
|
"Veröffentlichungsstatus bestimmen",
|
||||||
|
"Benutzerfreundliche Zusammenfassung anzeigen"
|
||||||
|
],
|
||||||
|
color: "#f39c12"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const businessRules = [
|
||||||
|
{ rule: "Manager darf nicht allein arbeiten", critical: true },
|
||||||
|
{ rule: "Erfahrene mit canWorkAlone: false dürfen nicht allein arbeiten", critical: true },
|
||||||
|
{ rule: "Keine leeren Schichten", critical: true },
|
||||||
|
{ rule: "Keine 'Neu allein' Situationen", critical: true },
|
||||||
|
{ rule: "Manager sollte mit erfahrenem Mitarbeiter arbeiten", critical: false },
|
||||||
|
{ rule: "Vertragslimits einhalten", critical: true },
|
||||||
|
{ rule: "Nicht zu viele erfahrene Mitarbeiter in einer Schicht", critical: false }
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if (isAnimating) {
|
||||||
|
setCurrentStage((prev) => (prev + 1) % algorithmStages.length);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [isAnimating]);
|
||||||
|
|
||||||
|
const toggleAnimation = () => {
|
||||||
|
setIsAnimating(!isAnimating);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ padding: '20px', maxWidth: '1200px', margin: '0 auto' }}>
|
||||||
<h1>❓ Hilfe & Support</h1>
|
<h1>❓ Hilfe & Support - Scheduling Algorithmus</h1>
|
||||||
|
|
||||||
|
{/* Algorithm Visualization */}
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '40px',
|
backgroundColor: 'white',
|
||||||
textAlign: 'center',
|
borderRadius: '12px',
|
||||||
backgroundColor: '#f8f9fa',
|
padding: '30px',
|
||||||
borderRadius: '8px',
|
marginTop: '20px',
|
||||||
border: '2px dashed #dee2e6',
|
boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
|
||||||
marginTop: '20px'
|
border: '1px solid #e0e0e0'
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontSize: '48px', marginBottom: '20px' }}>📖</div>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
|
||||||
<h3>Hilfe Center</h3>
|
<h2 style={{ margin: 0, color: '#2c3e50' }}>🧠 Algorithmus Visualisierung</h2>
|
||||||
<p>Hier finden Sie Anleitungen und Support für die Schichtplan-App.</p>
|
<button
|
||||||
<p style={{ fontSize: '14px', color: '#6c757d' }}>
|
onClick={toggleAnimation}
|
||||||
Diese Seite wird demnächst mit Funktionen gefüllt.
|
style={{
|
||||||
</p>
|
padding: '8px 16px',
|
||||||
|
backgroundColor: isAnimating ? '#e74c3c' : '#2ecc71',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '20px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isAnimating ? '⏸️ Animation pausieren' : '▶️ Animation starten'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stage Indicators */}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: '30px',
|
||||||
|
position: 'relative'
|
||||||
|
}}>
|
||||||
|
{algorithmStages.map((stage, index) => (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
flex: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '60px',
|
||||||
|
height: '60px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: currentStage === index ? stage.color : '#ecf0f1',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: currentStage === index ? 'white' : '#7f8c8d',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: '18px',
|
||||||
|
transition: 'all 0.5s ease',
|
||||||
|
boxShadow: currentStage === index ? `0 0 20px ${stage.color}80` : 'none',
|
||||||
|
border: `3px solid ${stage.color}`
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{index + 1}
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: '10px',
|
||||||
|
fontWeight: currentStage === index ? 'bold' : 'normal',
|
||||||
|
color: currentStage === index ? stage.color : '#7f8c8d'
|
||||||
|
}}>
|
||||||
|
{stage.title.split(':')[0]}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{index < algorithmStages.length - 1 && (
|
||||||
|
<div style={{
|
||||||
|
flex: 1,
|
||||||
|
height: '3px',
|
||||||
|
backgroundColor: currentStage > index ? stage.color : '#ecf0f1',
|
||||||
|
alignSelf: 'center',
|
||||||
|
margin: '0 10px',
|
||||||
|
transition: 'all 0.5s ease'
|
||||||
|
}} />
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Current Stage Details */}
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: algorithmStages[currentStage].color + '15',
|
||||||
|
border: `2px solid ${algorithmStages[currentStage].color}30`,
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '20px',
|
||||||
|
marginBottom: '20px',
|
||||||
|
transition: 'all 0.5s ease'
|
||||||
|
}}>
|
||||||
|
<h3 style={{ color: algorithmStages[currentStage].color, marginTop: 0 }}>
|
||||||
|
{algorithmStages[currentStage].title}
|
||||||
|
</h3>
|
||||||
|
<p style={{ color: '#2c3e50', fontSize: '16px', marginBottom: '15px' }}>
|
||||||
|
{algorithmStages[currentStage].description}
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'grid', gap: '8px' }}>
|
||||||
|
{algorithmStages[currentStage].steps.map((step, stepIndex) => (
|
||||||
|
<div
|
||||||
|
key={stepIndex}
|
||||||
|
style={{
|
||||||
|
padding: '10px 15px',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: '6px',
|
||||||
|
borderLeft: `4px solid ${algorithmStages[currentStage].color}`,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
animation: isAnimating ? 'pulse 2s infinite' : 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{
|
||||||
|
marginRight: '10px',
|
||||||
|
color: algorithmStages[currentStage].color,
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}}>•</span>
|
||||||
|
{step}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Network Visualization */}
|
||||||
|
<div style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
||||||
|
gap: '15px',
|
||||||
|
marginTop: '30px'
|
||||||
|
}}>
|
||||||
|
{/* Employees */}
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: '#e8f4fd',
|
||||||
|
padding: '15px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #b8d4f0'
|
||||||
|
}}>
|
||||||
|
<h4 style={{ margin: '0 0 10px 0', color: '#3498db' }}>👥 Mitarbeiter</h4>
|
||||||
|
<div style={{ fontSize: '14px', lineHeight: '1.4' }}>
|
||||||
|
<div>• Manager (1)</div>
|
||||||
|
<div>• Erfahrene ({currentStage >= 1 ? '3' : '0'})</div>
|
||||||
|
<div>• Neue ({currentStage >= 1 ? '2' : '0'})</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Shifts */}
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: '#fff3cd',
|
||||||
|
padding: '15px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ffeaa7'
|
||||||
|
}}>
|
||||||
|
<h4 style={{ margin: '0 0 10px 0', color: '#f39c12' }}>📅 Schichten</h4>
|
||||||
|
<div style={{ fontSize: '14px', lineHeight: '1.4' }}>
|
||||||
|
<div>• Vormittag (5)</div>
|
||||||
|
<div>• Nachmittag (4)</div>
|
||||||
|
<div>• Manager-Schichten (3)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Current Actions */}
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: '#d4edda',
|
||||||
|
padding: '15px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #c3e6cb'
|
||||||
|
}}>
|
||||||
|
<h4 style={{ margin: '0 0 10px 0', color: '#27ae60' }}>⚡ Aktive Aktionen</h4>
|
||||||
|
<div style={{ fontSize: '14px', lineHeight: '1.4' }}>
|
||||||
|
{currentStage === 0 && (
|
||||||
|
<>
|
||||||
|
<div>• Grundzuweisung läuft</div>
|
||||||
|
<div>• Erfahrene priorisieren</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{currentStage === 1 && (
|
||||||
|
<>
|
||||||
|
<div>• Manager wird zugewiesen</div>
|
||||||
|
<div>• Erfahrene suchen</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{currentStage === 2 && (
|
||||||
|
<>
|
||||||
|
<div>• Überbesetzung prüfen</div>
|
||||||
|
<div>• Pool-Verwaltung aktiv</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{currentStage === 3 && (
|
||||||
|
<>
|
||||||
|
<div>• Finale Validierung</div>
|
||||||
|
<div>• Bericht generieren</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Problems & Solutions */}
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: '#f8d7da',
|
||||||
|
padding: '15px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #f5c6cb'
|
||||||
|
}}>
|
||||||
|
<h4 style={{ margin: '0 0 10px 0', color: '#e74c3c' }}>🔍 Probleme & Lösungen</h4>
|
||||||
|
<div style={{ fontSize: '14px', lineHeight: '1.4' }}>
|
||||||
|
{currentStage >= 2 ? (
|
||||||
|
<>
|
||||||
|
<div style={{ color: '#27ae60' }}>✅ 2 Probleme behoben</div>
|
||||||
|
<div style={{ color: '#e74c3c' }}>❌ 0 kritische Probleme</div>
|
||||||
|
<div style={{ color: '#f39c12' }}>⚠️ 1 Warnung</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div>Noch keine Probleme analysiert</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Business Rules */}
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: '12px',
|
||||||
|
padding: '30px',
|
||||||
|
marginTop: '20px',
|
||||||
|
boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
|
||||||
|
border: '1px solid #e0e0e0'
|
||||||
|
}}>
|
||||||
|
<h2 style={{ color: '#2c3e50', marginBottom: '20px' }}>📋 Geschäftsregeln</h2>
|
||||||
|
<div style={{ display: 'grid', gap: '10px' }}>
|
||||||
|
{businessRules.map((rule, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
padding: '12px 16px',
|
||||||
|
backgroundColor: rule.critical ? '#f8d7da' : '#fff3cd',
|
||||||
|
border: `1px solid ${rule.critical ? '#f5c6cb' : '#ffeaa7'}`,
|
||||||
|
borderRadius: '6px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{
|
||||||
|
marginRight: '12px',
|
||||||
|
color: rule.critical ? '#e74c3c' : '#f39c12',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: '16px'
|
||||||
|
}}>
|
||||||
|
{rule.critical ? '❌' : '⚠️'}
|
||||||
|
</span>
|
||||||
|
<span style={{ color: rule.critical ? '#721c24' : '#856404' }}>
|
||||||
|
{rule.rule}
|
||||||
|
</span>
|
||||||
|
<span style={{
|
||||||
|
marginLeft: 'auto',
|
||||||
|
fontSize: '12px',
|
||||||
|
color: rule.critical ? '#e74c3c' : '#f39c12',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}}>
|
||||||
|
{rule.critical ? 'KRITISCH' : 'WARNUNG'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Algorithm Explanation */}
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: '12px',
|
||||||
|
padding: '30px',
|
||||||
|
marginTop: '20px',
|
||||||
|
boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
|
||||||
|
border: '1px solid #e0e0e0'
|
||||||
|
}}>
|
||||||
|
<h2 style={{ color: '#2c3e50', marginBottom: '20px' }}>🎯 Wie der Algorithmus funktioniert</h2>
|
||||||
|
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '20px' }}>
|
||||||
|
<div>
|
||||||
|
<h4 style={{ color: '#3498db' }}>🏗️ Phasen-basierter Ansatz</h4>
|
||||||
|
<p>Der Algorithmus arbeitet in klar definierten Phasen, um komplexe Probleme schrittweise zu lösen und Stabilität zu gewährleisten.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 style={{ color: '#e74c3c' }}>⚖️ Wert-basierte Entscheidungen</h4>
|
||||||
|
<p>Jede Zuweisung wird anhand eines Wertesystems bewertet, das Verfügbarkeit, Erfahrung und aktuelle Auslastung berücksichtigt.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 style={{ color: '#2ecc71' }}>🔧 Automatische Reparatur</h4>
|
||||||
|
<p>Probleme werden automatisch erkannt und durch intelligente Tausch- und Bewegungsoperationen behoben.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 style={{ color: '#f39c12' }}>📊 Transparente Berichterstattung</h4>
|
||||||
|
<p>Detaillierte Berichte zeigen genau, welche Probleme behoben wurden und welche verbleiben.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
marginTop: '25px',
|
||||||
|
padding: '20px',
|
||||||
|
backgroundColor: '#e8f4fd',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #b8d4f0'
|
||||||
|
}}>
|
||||||
|
<h4 style={{ color: '#2980b9', marginTop: 0 }}>💡 Tipps für beste Ergebnisse</h4>
|
||||||
|
<ul style={{ margin: 0, paddingLeft: '20px' }}>
|
||||||
|
<li>Stellen Sie sicher, dass alle Mitarbeiter ihre Verfügbarkeit eingetragen haben</li>
|
||||||
|
<li>Überprüfen Sie die Vertragstypen (klein = 1 Schicht/Woche, groß = 2 Schichten/Woche)</li>
|
||||||
|
<li>Markieren Sie erfahrene Mitarbeiter, die alleine arbeiten können</li>
|
||||||
|
<li>Planen Sie Manager-Verfügbarkeit im Voraus</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>{`
|
||||||
|
@keyframes pulse {
|
||||||
|
0% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.02); }
|
||||||
|
100% { transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glow {
|
||||||
|
0% { box-shadow: 0 0 5px rgba(52, 152, 219, 0.5); }
|
||||||
|
50% { box-shadow: 0 0 20px rgba(52, 152, 219, 0.8); }
|
||||||
|
100% { box-shadow: 0 0 5px rgba(52, 152, 219, 0.5); }
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -173,24 +173,48 @@ const ShiftPlanView: React.FC = () => {
|
|||||||
result.resolutionReport.forEach(line => console.log(line));
|
result.resolutionReport.forEach(line => console.log(line));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verwende allProblemsResolved für die Erfolgsmeldung
|
// KORRIGIERT: Entscheidung basierend auf Reparatur-Bericht statt allProblemsResolved
|
||||||
if (result.allProblemsResolved) {
|
const allCriticalResolved = result.resolutionReport &&
|
||||||
|
result.resolutionReport.some(line =>
|
||||||
|
line.includes('Alle kritischen Probleme behoben: ✅ JA')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (allCriticalResolved) {
|
||||||
showNotification({
|
showNotification({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: 'Erfolg',
|
title: 'Erfolg',
|
||||||
message: 'Alle kritischen Probleme wurden behoben! Der Schichtplan kann veröffentlicht werden.'
|
message: 'Alle kritischen Probleme wurden behoben! Der Schichtplan kann veröffentlicht werden.'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const criticalCount = result.violations.filter(v => v.includes('❌ KRITISCH:')).length;
|
// Zähle nur kritische Probleme (ERROR oder ❌ KRITISCH)
|
||||||
const warningCount = result.violations.filter(v => v.includes('⚠️')).length;
|
const criticalProblems = result.violations.filter(v =>
|
||||||
|
v.includes('ERROR:') || v.includes('❌ KRITISCH:')
|
||||||
|
);
|
||||||
|
|
||||||
showNotification({
|
// Zähle Warnungen separat
|
||||||
type: warningCount > 0 ? 'warning' : 'error',
|
const warnings = result.violations.filter(v =>
|
||||||
title: criticalCount > 0 ? 'Kritische Probleme' : 'Warnungen',
|
v.includes('WARNING:') || v.includes('⚠️')
|
||||||
message: criticalCount > 0
|
);
|
||||||
? `${criticalCount} kritische Probleme müssen behoben werden`
|
|
||||||
: `${warningCount} Warnungen - Plan kann trotzdem veröffentlicht werden`
|
if (criticalProblems.length > 0) {
|
||||||
});
|
showNotification({
|
||||||
|
type: 'error',
|
||||||
|
title: 'Kritische Probleme',
|
||||||
|
message: `${criticalProblems.length} kritische Probleme müssen behoben werden`
|
||||||
|
});
|
||||||
|
} else if (warnings.length > 0) {
|
||||||
|
showNotification({
|
||||||
|
type: 'warning',
|
||||||
|
title: 'Warnungen',
|
||||||
|
message: `${warnings.length} Warnungen - Plan kann trotzdem veröffentlicht werden`
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showNotification({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Erfolg',
|
||||||
|
message: 'Keine Probleme gefunden! Der Schichtplan kann veröffentlicht werden.'
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -622,7 +646,7 @@ const ShiftPlanView: React.FC = () => {
|
|||||||
disabled={!canPublish() || publishing}
|
disabled={!canPublish() || publishing}
|
||||||
style={{
|
style={{
|
||||||
padding: '10px 20px',
|
padding: '10px 20px',
|
||||||
backgroundColor: canPublish() ? '#2ecc71' : '#95a5a6',
|
backgroundColor: canPublish() ? '#3498db' : '#95a5a6',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
@@ -630,13 +654,13 @@ const ShiftPlanView: React.FC = () => {
|
|||||||
fontWeight: 'bold'
|
fontWeight: 'bold'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{publishing ? 'Berechne...' : 'Automatisch zuweisen & Veröffentlichen'}
|
{publishing ? 'Berechne...' : 'Automatisch zuweisen'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{!canPublish() && (
|
{!canPublish() && (
|
||||||
<div style={{ fontSize: '12px', color: '#666', marginTop: '5px' }}>
|
<div style={{ fontSize: '12px', color: '#666', marginTop: '5px' }}>
|
||||||
{availabilityStatus.percentage === 100
|
{availabilityStatus.percentage === 100
|
||||||
? 'Bereit zur Veröffentlichung'
|
? 'Bereit zur Berechnung'
|
||||||
: `${availabilityStatus.total - availabilityStatus.completed} Mitarbeiter müssen noch Verfügbarkeit eintragen`}
|
: `${availabilityStatus.total - availabilityStatus.completed} Mitarbeiter müssen noch Verfügbarkeit eintragen`}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -659,91 +683,123 @@ const ShiftPlanView: React.FC = () => {
|
|||||||
|
|
||||||
{/* Assignment Preview Modal */}
|
{/* Assignment Preview Modal */}
|
||||||
{showAssignmentPreview && assignmentResult && (
|
{showAssignmentPreview && assignmentResult && (
|
||||||
<div style={{
|
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
zIndex: 1000
|
|
||||||
}}>
|
|
||||||
<div style={{
|
<div style={{
|
||||||
backgroundColor: 'white',
|
position: 'fixed',
|
||||||
borderRadius: '8px',
|
top: 0,
|
||||||
padding: '30px',
|
left: 0,
|
||||||
maxWidth: '800px',
|
right: 0,
|
||||||
maxHeight: '80vh',
|
bottom: 0,
|
||||||
overflow: 'auto'
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
zIndex: 1000
|
||||||
}}>
|
}}>
|
||||||
<h2>Wochenmuster-Zuordnung</h2>
|
<div style={{
|
||||||
|
backgroundColor: 'white',
|
||||||
{/* Reparatur-Bericht anzeigen */}
|
borderRadius: '8px',
|
||||||
{assignmentResult.resolutionReport && (
|
padding: '30px',
|
||||||
<div style={{
|
maxWidth: '800px',
|
||||||
backgroundColor: '#e8f4fd',
|
maxHeight: '80vh',
|
||||||
border: '1px solid #b8d4f0',
|
overflow: 'auto'
|
||||||
borderRadius: '4px',
|
}}>
|
||||||
padding: '15px',
|
<h2>Wochenmuster-Zuordnung</h2>
|
||||||
marginBottom: '20px',
|
|
||||||
fontSize: '14px'
|
|
||||||
}}>
|
|
||||||
<h4 style={{ color: '#2c3e50', marginTop: 0 }}>Reparatur-Bericht</h4>
|
|
||||||
<div style={{ maxHeight: '200px', overflow: 'auto' }}>
|
|
||||||
{assignmentResult.resolutionReport.map((line, index) => (
|
|
||||||
<div key={index} style={{
|
|
||||||
color: line.includes('✅') ? '#2ecc71' : line.includes('❌') ? '#e74c3c' : '#2c3e50',
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
fontSize: '12px',
|
|
||||||
marginBottom: '2px'
|
|
||||||
}}>
|
|
||||||
{line}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
{/* Reparatur-Bericht anzeigen */}
|
||||||
|
{assignmentResult.resolutionReport && (
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: '#e8f4fd',
|
||||||
|
border: '1px solid #b8d4f0',
|
||||||
|
borderRadius: '4px',
|
||||||
|
padding: '15px',
|
||||||
|
marginBottom: '20px',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}>
|
||||||
|
<h4 style={{ color: '#2c3e50', marginTop: 0 }}>Reparatur-Bericht</h4>
|
||||||
|
<div style={{ maxHeight: '200px', overflow: 'auto' }}>
|
||||||
|
{assignmentResult.resolutionReport.map((line, index) => (
|
||||||
|
<div key={index} style={{
|
||||||
|
color: line.includes('✅') ? '#2ecc71' : line.includes('❌') ? '#e74c3c' : '#2c3e50',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: '12px',
|
||||||
|
marginBottom: '2px'
|
||||||
|
}}>
|
||||||
|
{line}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* KORRIGIERTE ZUSAMMENFASSUNG */}
|
||||||
{assignmentResult && (
|
{assignmentResult && (
|
||||||
<div style={{ marginBottom: '20px' }}>
|
<div style={{ marginBottom: '20px' }}>
|
||||||
<h4>Zusammenfassung:</h4>
|
<h4>Zusammenfassung:</h4>
|
||||||
{assignmentResult.allProblemsResolved ? (
|
|
||||||
<p style={{ color: '#2ecc71', fontWeight: 'bold' }}>
|
{/* Entscheidung basierend auf Reparatur-Bericht */}
|
||||||
✅ Alle kritischen Probleme behoben! Der Plan kann veröffentlicht werden.
|
{assignmentResult.resolutionReport &&
|
||||||
</p>
|
assignmentResult.resolutionReport.some(line =>
|
||||||
) : (
|
line.includes('Alle kritischen Probleme behoben: ✅ JA')
|
||||||
<div>
|
) ? (
|
||||||
<p style={{ color: '#e74c3c', fontWeight: 'bold' }}>
|
<div style={{
|
||||||
❌ Es gibt kritische Probleme die behoben werden müssen:
|
padding: '15px',
|
||||||
|
backgroundColor: '#d4edda',
|
||||||
|
border: '1px solid #c3e6cb',
|
||||||
|
borderRadius: '4px',
|
||||||
|
color: '#155724',
|
||||||
|
marginBottom: '15px'
|
||||||
|
}}>
|
||||||
|
<h5 style={{ margin: '0 0 10px 0', color: '#155724' }}>✅ Bereit zur Veröffentlichung</h5>
|
||||||
|
<p style={{ margin: 0 }}>
|
||||||
|
Alle kritischen Probleme wurden behoben. Der Schichtplan kann veröffentlicht werden.
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{
|
||||||
|
padding: '15px',
|
||||||
|
backgroundColor: '#f8d7da',
|
||||||
|
border: '1px solid #f5c6cb',
|
||||||
|
borderRadius: '4px',
|
||||||
|
color: '#721c24',
|
||||||
|
marginBottom: '15px'
|
||||||
|
}}>
|
||||||
|
<h5 style={{ margin: '0 0 10px 0', color: '#721c24' }}>❌ Kritische Probleme</h5>
|
||||||
|
<p style={{ margin: '0 0 10px 0' }}>
|
||||||
|
Folgende kritische Probleme müssen behoben werden, bevor der Plan veröffentlicht werden kann:
|
||||||
|
</p>
|
||||||
|
<ul style={{ margin: 0, paddingLeft: '20px' }}>
|
||||||
{assignmentResult.violations
|
{assignmentResult.violations
|
||||||
.filter(v => v.includes('❌ KRITISCH:'))
|
.filter(v => v.includes('ERROR:') || v.includes('❌ KRITISCH:'))
|
||||||
.map((violation, index) => (
|
.map((violation, index) => (
|
||||||
<li key={index} style={{ color: '#e74c3c', fontSize: '14px' }}>
|
<li key={index} style={{ fontSize: '14px' }}>
|
||||||
{violation.replace('❌ KRITISCH: ', '')}
|
{violation.replace('ERROR: ', '').replace('❌ KRITISCH: ', '')}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Warnungen separat anzeigen - NUR wenn welche vorhanden sind */}
|
||||||
|
{assignmentResult.violations.some(v => v.includes('WARNING:') || v.includes('⚠️')) && (
|
||||||
|
<div style={{
|
||||||
|
padding: '10px',
|
||||||
|
backgroundColor: '#fff3cd',
|
||||||
|
border: '1px solid #ffeaa7',
|
||||||
|
borderRadius: '4px',
|
||||||
|
color: '#856404'
|
||||||
|
}}>
|
||||||
|
<h6 style={{ margin: '0 0 5px 0', color: '#856404' }}>
|
||||||
|
⚠️ Hinweise & Warnungen
|
||||||
|
</h6>
|
||||||
|
<ul style={{ margin: 0, paddingLeft: '20px' }}>
|
||||||
|
{assignmentResult.violations
|
||||||
|
.filter(v => v.includes('WARNING:') || v.includes('⚠️'))
|
||||||
|
.map((warning, index) => (
|
||||||
|
<li key={index} style={{ fontSize: '13px' }}>
|
||||||
|
{warning.replace('WARNING: ', '').replace('⚠️ WARNHINWEIS: ', '')}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
{assignmentResult.violations.some(v => v.includes('⚠️')) && (
|
|
||||||
<div style={{ marginTop: '10px' }}>
|
|
||||||
<p style={{ color: '#f39c12', fontWeight: 'bold' }}>
|
|
||||||
⚠️ Warnungen (beeinflussen nicht die Veröffentlichung):
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
{assignmentResult.violations
|
|
||||||
.filter(v => v.includes('⚠️'))
|
|
||||||
.map((warning, index) => (
|
|
||||||
<li key={index} style={{ color: '#f39c12', fontSize: '14px' }}>
|
|
||||||
{warning.replace('⚠️ WARNHINWEIS: ', '')}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -766,22 +822,41 @@ const ShiftPlanView: React.FC = () => {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handlePublish}
|
onClick={handlePublish}
|
||||||
disabled={publishing || !assignmentResult.allProblemsResolved}
|
disabled={publishing || !(assignmentResult.resolutionReport &&
|
||||||
|
assignmentResult.resolutionReport.some(line =>
|
||||||
|
line.includes('Alle kritischen Probleme behoben: ✅ JA')
|
||||||
|
))
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
padding: '8px 16px',
|
padding: '10px 20px',
|
||||||
backgroundColor: assignmentResult.allProblemsResolved ? '#2ecc71' : '#95a5a6',
|
backgroundColor: (assignmentResult.resolutionReport &&
|
||||||
|
assignmentResult.resolutionReport.some(line =>
|
||||||
|
line.includes('Alle kritischen Probleme behoben: ✅ JA')
|
||||||
|
)) ? '#2ecc71' : '#95a5a6',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
cursor: assignmentResult.allProblemsResolved ? 'pointer' : 'not-allowed'
|
cursor: (assignmentResult.resolutionReport &&
|
||||||
|
assignmentResult.resolutionReport.some(line =>
|
||||||
|
line.includes('Alle kritischen Probleme behoben: ✅ JA')
|
||||||
|
)) ? 'pointer' : 'not-allowed',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: '16px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{publishing ? 'Veröffentliche...' : 'Veröffentlichen'}
|
{publishing ? 'Veröffentliche...' : (
|
||||||
|
(assignmentResult.resolutionReport &&
|
||||||
|
assignmentResult.resolutionReport.some(line =>
|
||||||
|
line.includes('Alle kritischen Probleme behoben: ✅ JA')
|
||||||
|
))
|
||||||
|
? 'Schichtplan veröffentlichen'
|
||||||
|
: 'Kritische Probleme müssen behoben werden'
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Timetable */}
|
{/* Timetable */}
|
||||||
<div style={{
|
<div style={{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// frontend/src/services/scheduling/repairFunctions.ts
|
// frontend/src/services/scheduling/repairFunctions.ts
|
||||||
import { SchedulingEmployee, SchedulingShift, Assignment, RepairContext } from './types';
|
import { SchedulingEmployee, SchedulingShift, Assignment, RepairContext } from './types';
|
||||||
import { canAssign, assignEmployee, unassignEmployee,
|
import { canAssign, assignEmployee, unassignEmployee,
|
||||||
hasErfahrener, candidateScore,
|
candidateScore,
|
||||||
countExperiencedCanWorkAlone,
|
countExperiencedCanWorkAlone,
|
||||||
isManagerAlone, isManagerShiftWithOnlyNew,
|
isManagerAlone, isManagerShiftWithOnlyNew,
|
||||||
onlyNeuAssigned, canRemove,
|
onlyNeuAssigned, canRemove,
|
||||||
@@ -776,7 +776,7 @@ export function checkAllProblemsResolved(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (violation.startsWith('ERROR:')) {
|
if (violation.startsWith('ERROR:')) {
|
||||||
// ... bestehende ERROR-Logik ...
|
remaining.push(`❌ NICHT BEHOBEN: ${violation.replace('ERROR: ', '')}`);
|
||||||
} else if (violation.startsWith('WARNING:')) {
|
} else if (violation.startsWith('WARNING:')) {
|
||||||
const warningText = violation.replace('WARNING: ', '');
|
const warningText = violation.replace('WARNING: ', '');
|
||||||
const shiftIdMatch = warningText.match(/Schicht\s+([a-f0-9-]+)/);
|
const shiftIdMatch = warningText.match(/Schicht\s+([a-f0-9-]+)/);
|
||||||
|
|||||||
@@ -14,20 +14,15 @@ import {
|
|||||||
assignEmployee,
|
assignEmployee,
|
||||||
hasErfahrener,
|
hasErfahrener,
|
||||||
wouldBeAloneIfAdded,
|
wouldBeAloneIfAdded,
|
||||||
findViolations,
|
|
||||||
isManagerShiftWithOnlyNew,
|
isManagerShiftWithOnlyNew,
|
||||||
isManagerAlone,
|
isManagerAlone,
|
||||||
hasExperiencedAloneNotAllowed
|
hasExperiencedAloneNotAllowed
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import {
|
import {
|
||||||
attemptMoveErfahrenerTo,
|
attemptMoveErfahrenerTo,
|
||||||
attemptSwapBringErfahrener,
|
|
||||||
attemptLocalFixNeuAlone,
|
|
||||||
attemptUnassignOrSwap,
|
attemptUnassignOrSwap,
|
||||||
attemptFillFromOverallocated,
|
|
||||||
attemptAddErfahrenerToShift,
|
attemptAddErfahrenerToShift,
|
||||||
attemptFillFromPool,
|
attemptFillFromPool,
|
||||||
checkManagerShiftRules,
|
|
||||||
resolveTwoExperiencedInShift,
|
resolveTwoExperiencedInShift,
|
||||||
attemptMoveExperiencedToManagerShift,
|
attemptMoveExperiencedToManagerShift,
|
||||||
attemptSwapForExperienced,
|
attemptSwapForExperienced,
|
||||||
@@ -149,7 +144,7 @@ function phaseBInsertManager(
|
|||||||
console.log(`🎯 Phase B: Processing ${managerShifts.length} manager shifts`);
|
console.log(`🎯 Phase B: Processing ${managerShifts.length} manager shifts`);
|
||||||
|
|
||||||
for (const shiftId of managerShifts) {
|
for (const shiftId of managerShifts) {
|
||||||
const shift = nonManagerShifts.find(s => s.id === shiftId) || { id: shiftId, requiredEmployees: 2 };
|
//const shift = nonManagerShifts.find(s => s.id === shiftId) || { id: shiftId, requiredEmployees: 2 };
|
||||||
|
|
||||||
// Assign manager to his chosen shifts
|
// Assign manager to his chosen shifts
|
||||||
if (!assignments[shiftId].includes(manager.id)) {
|
if (!assignments[shiftId].includes(manager.id)) {
|
||||||
@@ -426,7 +421,7 @@ export function scheduleWithManager(
|
|||||||
): SchedulingResult & { resolutionReport?: string[]; allProblemsResolved?: boolean } {
|
): SchedulingResult & { resolutionReport?: string[]; allProblemsResolved?: boolean } {
|
||||||
|
|
||||||
const assignments: Assignment = {};
|
const assignments: Assignment = {};
|
||||||
const allViolations: string[] = [];
|
//const allViolations: string[] = [];
|
||||||
|
|
||||||
// Initialisiere Zuweisungen
|
// Initialisiere Zuweisungen
|
||||||
shifts.forEach(shift => {
|
shifts.forEach(shift => {
|
||||||
@@ -449,14 +444,14 @@ export function scheduleWithManager(
|
|||||||
console.log('🔄 Starting Phase B: Enhanced Manager insertion');
|
console.log('🔄 Starting Phase B: Enhanced Manager insertion');
|
||||||
|
|
||||||
// Phase B: Erweiterte Manager-Einfügung
|
// Phase B: Erweiterte Manager-Einfügung
|
||||||
const phaseBResult = phaseBInsertManager(
|
/*const phaseBResult = phaseBInsertManager(
|
||||||
assignments,
|
assignments,
|
||||||
manager,
|
manager,
|
||||||
managerShifts,
|
managerShifts,
|
||||||
employees,
|
employees,
|
||||||
nonManagerShifts,
|
nonManagerShifts,
|
||||||
constraints
|
constraints
|
||||||
);
|
);*/
|
||||||
|
|
||||||
console.log('🔄 Starting Enhanced Phase C: Smart Repair & Validation');
|
console.log('🔄 Starting Enhanced Phase C: Smart Repair & Validation');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user