diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index db3cc92..98510cc 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -5,6 +5,7 @@ import { AuthProvider, useAuth } from './contexts/AuthContext';
import { NotificationProvider } from './contexts/NotificationContext';
import NotificationContainer from './components/Notification/NotificationContainer';
import Layout from './components/Layout/Layout';
+import { DesignSystemProvider, GlobalStyles } from './design/DesignSystem';
import Login from './pages/Auth/Login';
import Dashboard from './pages/Dashboard/Dashboard';
import ShiftPlanList from './pages/ShiftPlans/ShiftPlanList';
@@ -132,14 +133,17 @@ const AppContent: React.FC = () => {
function App() {
return (
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/frontend/src/components/Layout/Footer.tsx b/frontend/src/components/Layout/Footer.tsx
index 043d17b..e988d81 100644
--- a/frontend/src/components/Layout/Footer.tsx
+++ b/frontend/src/components/Layout/Footer.tsx
@@ -1,46 +1,51 @@
-// frontend/src/components/Layout/Footer.tsx
+// frontend/src/components/Layout/Footer.tsx - ELEGANT WHITE DESIGN
import React from 'react';
const Footer: React.FC = () => {
const styles = {
footer: {
- background: '#2c3e50',
+ background: 'linear-gradient(135deg, #1a1325 0%, #24163a 100%)',
color: 'white',
marginTop: 'auto',
+ borderTop: '1px solid rgba(251, 250, 246, 0.1)',
},
footerContent: {
maxWidth: '1200px',
margin: '0 auto',
- padding: '2rem 20px',
+ padding: '3rem 2rem 2rem',
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
- gap: '2rem',
+ gap: '3rem',
},
footerSection: {
display: 'flex',
flexDirection: 'column' as const,
},
footerSectionH3: {
- marginBottom: '1rem',
- color: '#ecf0f1',
- fontSize: '1.2rem',
+ marginBottom: '1.5rem',
+ color: '#FBFAF6',
+ fontSize: '1.1rem',
+ fontWeight: 600,
},
footerSectionH4: {
marginBottom: '1rem',
- color: '#ecf0f1',
- fontSize: '1.1rem',
+ color: '#FBFAF6',
+ fontSize: '1rem',
+ fontWeight: 600,
},
footerLink: {
- color: '#bdc3c7',
+ color: 'rgba(251, 250, 246, 0.7)',
textDecoration: 'none',
- marginBottom: '0.5rem',
- transition: 'color 0.3s ease',
+ marginBottom: '0.75rem',
+ transition: 'all 0.2s ease',
+ fontSize: '0.9rem',
},
footerBottom: {
- borderTop: '1px solid #34495e',
- padding: '1rem 20px',
+ borderTop: '1px solid rgba(251, 250, 246, 0.1)',
+ padding: '1.5rem 2rem',
textAlign: 'center' as const,
- color: '#95a5a6',
+ color: 'rgba(251, 250, 246, 0.6)',
+ fontSize: '0.9rem',
},
};
@@ -49,9 +54,9 @@ const Footer: React.FC = () => {
Schichtenplaner
-
+
Professionelle Schichtplanung für Ihr Team.
- Effiziente Personalplanung für optimale Abläufe.
+ Effiziente Personalplanung für optimale Abläube.
@@ -61,10 +66,12 @@ const Footer: React.FC = () => {
href="/help"
style={styles.footerLink}
onMouseEnter={(e) => {
- e.currentTarget.style.color = '#3498db';
+ e.currentTarget.style.color = '#FBFAF6';
+ e.currentTarget.style.transform = 'translateX(4px)';
}}
onMouseLeave={(e) => {
- e.currentTarget.style.color = '#bdc3c7';
+ e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
+ e.currentTarget.style.transform = 'translateX(0)';
}}
>
Hilfe & Anleitungen
@@ -73,10 +80,12 @@ const Footer: React.FC = () => {
href="/contact"
style={styles.footerLink}
onMouseEnter={(e) => {
- e.currentTarget.style.color = '#3498db';
+ e.currentTarget.style.color = '#FBFAF6';
+ e.currentTarget.style.transform = 'translateX(4px)';
}}
onMouseLeave={(e) => {
- e.currentTarget.style.color = '#bdc3c7';
+ e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
+ e.currentTarget.style.transform = 'translateX(0)';
}}
>
Kontakt & Support
@@ -85,10 +94,12 @@ const Footer: React.FC = () => {
href="/faq"
style={styles.footerLink}
onMouseEnter={(e) => {
- e.currentTarget.style.color = '#3498db';
+ e.currentTarget.style.color = '#FBFAF6';
+ e.currentTarget.style.transform = 'translateX(4px)';
}}
onMouseLeave={(e) => {
- e.currentTarget.style.color = '#bdc3c7';
+ e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
+ e.currentTarget.style.transform = 'translateX(0)';
}}
>
Häufige Fragen
@@ -101,10 +112,12 @@ const Footer: React.FC = () => {
href="/privacy"
style={styles.footerLink}
onMouseEnter={(e) => {
- e.currentTarget.style.color = '#3498db';
+ e.currentTarget.style.color = '#FBFAF6';
+ e.currentTarget.style.transform = 'translateX(4px)';
}}
onMouseLeave={(e) => {
- e.currentTarget.style.color = '#bdc3c7';
+ e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
+ e.currentTarget.style.transform = 'translateX(0)';
}}
>
Datenschutzerklärung
@@ -113,10 +126,12 @@ const Footer: React.FC = () => {
href="/imprint"
style={styles.footerLink}
onMouseEnter={(e) => {
- e.currentTarget.style.color = '#3498db';
+ e.currentTarget.style.color = '#FBFAF6';
+ e.currentTarget.style.transform = 'translateX(4px)';
}}
onMouseLeave={(e) => {
- e.currentTarget.style.color = '#bdc3c7';
+ e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
+ e.currentTarget.style.transform = 'translateX(0)';
}}
>
Impressum
@@ -125,10 +140,12 @@ const Footer: React.FC = () => {
href="/terms"
style={styles.footerLink}
onMouseEnter={(e) => {
- e.currentTarget.style.color = '#3498db';
+ e.currentTarget.style.color = '#FBFAF6';
+ e.currentTarget.style.transform = 'translateX(4px)';
}}
onMouseLeave={(e) => {
- e.currentTarget.style.color = '#bdc3c7';
+ e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
+ e.currentTarget.style.transform = 'translateX(0)';
}}
>
Allgemeine Geschäftsbedingungen
@@ -141,10 +158,12 @@ const Footer: React.FC = () => {
href="/about"
style={styles.footerLink}
onMouseEnter={(e) => {
- e.currentTarget.style.color = '#3498db';
+ e.currentTarget.style.color = '#FBFAF6';
+ e.currentTarget.style.transform = 'translateX(4px)';
}}
onMouseLeave={(e) => {
- e.currentTarget.style.color = '#bdc3c7';
+ e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
+ e.currentTarget.style.transform = 'translateX(0)';
}}
>
Über uns
@@ -153,10 +172,12 @@ const Footer: React.FC = () => {
href="/features"
style={styles.footerLink}
onMouseEnter={(e) => {
- e.currentTarget.style.color = '#3498db';
+ e.currentTarget.style.color = '#FBFAF6';
+ e.currentTarget.style.transform = 'translateX(4px)';
}}
onMouseLeave={(e) => {
- e.currentTarget.style.color = '#bdc3c7';
+ e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
+ e.currentTarget.style.transform = 'translateX(0)';
}}
>
Funktionen
@@ -165,10 +186,12 @@ const Footer: React.FC = () => {
href="/pricing"
style={styles.footerLink}
onMouseEnter={(e) => {
- e.currentTarget.style.color = '#3498db';
+ e.currentTarget.style.color = '#FBFAF6';
+ e.currentTarget.style.transform = 'translateX(4px)';
}}
onMouseLeave={(e) => {
- e.currentTarget.style.color = '#bdc3c7';
+ e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
+ e.currentTarget.style.transform = 'translateX(0)';
}}
>
Preise
diff --git a/frontend/src/components/Layout/Layout.tsx b/frontend/src/components/Layout/Layout.tsx
index b1eb7fc..9287fd2 100644
--- a/frontend/src/components/Layout/Layout.tsx
+++ b/frontend/src/components/Layout/Layout.tsx
@@ -1,4 +1,4 @@
-// frontend/src/components/Layout/Layout.tsx - KORRIGIERT
+// frontend/src/components/Layout/Layout.tsx - ELEGANT WHITE DESIGN
import React from 'react';
import Navigation from './Navigation';
import Footer from './Footer';
@@ -13,16 +13,20 @@ const Layout: React.FC
= ({ children }) => {
minHeight: '100vh',
display: 'flex',
flexDirection: 'column' as const,
+ background: '#FBFAF6',
+ fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
+ lineHeight: 1.6,
+ color: '#161718',
},
mainContent: {
flex: 1,
- backgroundColor: '#f8f9fa',
minHeight: 'calc(100vh - 140px)',
+ paddingTop: '80px',
},
contentContainer: {
maxWidth: '1200px',
margin: '0 auto',
- padding: '2rem 20px',
+ padding: '3rem 2rem',
},
};
diff --git a/frontend/src/components/Layout/Navigation.tsx b/frontend/src/components/Layout/Navigation.tsx
index 36c42fa..5acd217 100644
--- a/frontend/src/components/Layout/Navigation.tsx
+++ b/frontend/src/components/Layout/Navigation.tsx
@@ -1,10 +1,24 @@
-// frontend/src/components/Layout/Navigation.tsx
-import React, { useState } from 'react';
+// frontend/src/components/Layout/Navigation.tsx - ELEGANT WHITE DESIGN
+import React, { useState, useEffect } from 'react';
import { useAuth } from '../../contexts/AuthContext';
+import PillNav from '../PillNav/PillNav';
const Navigation: React.FC = () => {
const { user, logout, hasRole } = useAuth();
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
+ const [activePath, setActivePath] = useState('/');
+ const [isScrolled, setIsScrolled] = useState(false);
+
+ useEffect(() => {
+ setActivePath(window.location.pathname);
+
+ const handleScroll = () => {
+ setIsScrolled(window.scrollY > 10);
+ };
+
+ window.addEventListener('scroll', handleScroll);
+ return () => window.removeEventListener('scroll', handleScroll);
+ }, []);
const handleLogout = () => {
logout();
@@ -16,160 +30,194 @@ const Navigation: React.FC = () => {
};
const navigationItems = [
- { path: '/', label: '📊 Dashboard', roles: ['admin', 'instandhalter', 'user'] },
- { path: '/shift-plans', label: '📅 Schichtpläne', roles: ['admin', 'instandhalter', 'user'] },
- { path: '/employees', label: '👥 Mitarbeiter', roles: ['admin', 'instandhalter'] },
- { path: '/help', label: '❓ Hilfe & Support', roles: ['admin', 'instandhalter', 'user'] },
- { path: '/settings', label: '⚙️ Einstellungen', roles: ['admin', 'instandhalter', 'user'] },
+ { path: '/', label: 'Dashboard', roles: ['admin', 'instandhalter', 'user'] },
+ { path: '/shift-plans', label: 'Schichtpläne', roles: ['admin', 'instandhalter', 'user'] },
+ { path: '/employees', label: 'Mitarbeiter', roles: ['admin', 'instandhalter'] },
+ { path: '/help', label: 'Hilfe', roles: ['admin', 'instandhalter', 'user'] },
+ { path: '/settings', label: 'Einstellungen', roles: ['admin', 'instandhalter', 'user'] },
];
const filteredNavigation = navigationItems.filter(item =>
hasRole(item.roles)
);
+ const handlePillChange = (path: string) => {
+ setActivePath(path);
+ window.location.href = path;
+ };
+
+ const pillNavItems = filteredNavigation.map(item => ({
+ id: item.path,
+ label: item.label
+ }));
+
const styles = {
header: {
- background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
- color: 'white',
- boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
- position: 'sticky' as const,
+ background: isScrolled
+ ? 'rgba(251, 250, 246, 0.95)'
+ : '#FBFAF6',
+ backdropFilter: isScrolled ? 'blur(10px)' : 'none',
+ borderBottom: isScrolled
+ ? '1px solid rgba(22, 23, 24, 0.08)'
+ : '1px solid transparent',
+ color: '#161718',
+ position: 'fixed' as const,
top: 0,
+ left: 0,
+ right: 0,
zIndex: 1000,
+ transition: 'all 0.3s ease-in-out',
+ boxShadow: isScrolled
+ ? '0 2px 20px rgba(22, 23, 24, 0.06)'
+ : 'none',
},
headerContent: {
maxWidth: '1200px',
margin: '0 auto',
- padding: '0 20px',
+ padding: '0 2rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
height: '70px',
+ transition: 'all 0.3s ease',
},
logo: {
flex: 1,
+ display: 'flex',
+ justifyContent: 'flex-start',
},
logoH1: {
margin: 0,
fontSize: '1.5rem',
fontWeight: 700,
+ color: '#161718',
+ letterSpacing: '-0.02em',
},
- desktopNav: {
+ pillNavWrapper: {
display: 'flex',
- gap: '2rem',
+ justifyContent: 'center',
alignItems: 'center',
+ flex: 2,
+ minWidth: 0,
},
- navLink: {
- color: 'white',
- textDecoration: 'none',
- padding: '0.5rem 1rem',
- borderRadius: '6px',
- transition: 'all 0.3s ease',
- fontWeight: 500,
+ pillNavContainer: {
+ display: 'flex',
+ justifyContent: 'center',
+ maxWidth: '600px',
+ width: '100%',
+ margin: '0 auto',
},
userMenu: {
+ flex: 1,
display: 'flex',
alignItems: 'center',
- gap: '1rem',
- marginLeft: '2rem',
+ justifyContent: 'flex-end',
+ gap: '1.5rem',
},
userInfo: {
fontWeight: 500,
+ color: '#666',
+ fontSize: '0.9rem',
+ textAlign: 'right' as const,
},
logoutBtn: {
- background: 'rgba(255, 255, 255, 0.1)',
- color: 'white',
- border: '1px solid rgba(255, 255, 255, 0.3)',
- padding: '0.5rem 1rem',
- borderRadius: '6px',
+ background: 'transparent',
+ color: '#161718',
+ border: '1.5px solid #51258f',
+ padding: '0.5rem 1.25rem',
+ borderRadius: '8px',
cursor: 'pointer',
- transition: 'all 0.3s ease',
+ transition: 'all 0.2s ease-in-out',
+ fontWeight: 500,
+ fontSize: '0.9rem',
+ whiteSpace: 'nowrap' as const,
},
mobileMenuBtn: {
display: 'none',
background: 'none',
border: 'none',
- color: 'white',
+ color: '#161718',
fontSize: '1.5rem',
cursor: 'pointer',
padding: '0.5rem',
+ borderRadius: '4px',
+ transition: 'background-color 0.2s ease',
},
mobileNav: {
display: isMobileMenuOpen ? 'flex' : 'none',
flexDirection: 'column' as const,
- background: 'white',
- padding: '1rem',
- boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
+ background: '#FBFAF6',
+ padding: '1rem 0',
+ borderTop: '1px solid rgba(22, 23, 24, 0.1)',
+ boxShadow: '0 4px 20px rgba(22, 23, 24, 0.08)',
},
mobileNavLink: {
- color: '#333',
+ color: '#161718',
textDecoration: 'none',
- padding: '1rem',
- borderBottom: '1px solid #eee',
- transition: 'background-color 0.3s ease',
+ padding: '1rem 2rem',
+ borderBottom: '1px solid rgba(22, 23, 24, 0.05)',
+ transition: 'all 0.2s ease',
+ fontWeight: 500,
},
mobileUserInfo: {
- padding: '1rem',
- borderTop: '1px solid #eee',
- marginTop: '1rem',
- color: '#333',
+ padding: '1.5rem 2rem',
+ borderTop: '1px solid rgba(22, 23, 24, 0.1)',
+ marginTop: '0.5rem',
+ color: '#666',
},
mobileLogoutBtn: {
- background: '#667eea',
+ background: '#51258f',
color: 'white',
border: 'none',
- padding: '0.5rem 1rem',
- borderRadius: '6px',
+ padding: '0.75rem 1.5rem',
+ borderRadius: '8px',
cursor: 'pointer',
- marginTop: '0.5rem',
+ marginTop: '1rem',
width: '100%',
+ fontWeight: 500,
+ transition: 'all 0.2s ease',
},
};
return (
+ {/* Logo - Links */}
-
🔄 Schichtenplaner
+ Schichtenplaner
- {/* Desktop Navigation */}
-
+ {/* PillNav - Zentriert */}
+
- {/* User Menu */}
+ {/* User Menu - Rechts */}
- {user?.name} ({user?.role})
+ {user?.name} ({user?.role})
@@ -557,27 +545,27 @@ const Dashboard: React.FC = () => {
👥 Team-Übersicht
- Gesamt Mitarbeiter:
+ Mitarbeiter:
{data.teamStats.totalEmployees}
- Verfügbar heute:
+ Chef:
- {data.teamStats.availableToday}
+ {data.teamStats.manager}
- Im Urlaub:
+ Erfahrene:
- {data.teamStats.onVacation}
+ {data.teamStats.experienced}
- Krankgeschrieben:
+ Neue:
- {data.teamStats.sickLeave}
+ {data.teamStats.trainee}
@@ -649,7 +637,7 @@ const Dashboard: React.FC = () => {
border: '1px solid #e0e0e0',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
}}>
-
📝 Letzte Schichtpläne
+
📝 Schichtpläne
{data.recentPlans.length > 0 ? (
{data.recentPlans.map(plan => (
diff --git a/frontend/src/pages/Settings/Settings.tsx b/frontend/src/pages/Settings/Settings.tsx
index e470e5f..1e2aadd 100644
--- a/frontend/src/pages/Settings/Settings.tsx
+++ b/frontend/src/pages/Settings/Settings.tsx
@@ -313,28 +313,42 @@ const Settings: React.FC = () => {
+
- {/* Editable name */}
-
-
-
-
+
+
+
{
const [assignmentResult, setAssignmentResult] = useState
(null);
const [loading, setLoading] = useState(true);
const [publishing, setPublishing] = useState(false);
+ const [scheduledShifts, setScheduledShifts] = useState([]);
const [reverting, setReverting] = useState(false);
const [showAssignmentPreview, setShowAssignmentPreview] = useState(false);
useEffect(() => {
loadShiftPlanData();
+ debugScheduledShifts();
}, [id]);
const loadShiftPlanData = async () => {
@@ -51,13 +53,15 @@ const ShiftPlanView: React.FC = () => {
try {
setLoading(true);
- const [plan, employeesData] = await Promise.all([
+ const [plan, employeesData, shiftsData] = await Promise.all([
shiftPlanService.getShiftPlan(id),
- employeeService.getEmployees()
+ employeeService.getEmployees(),
+ shiftAssignmentService.getScheduledShiftsForPlan(id) // Load shifts here
]);
setShiftPlan(plan);
setEmployees(employeesData.filter(emp => emp.isActive));
+ setScheduledShifts(shiftsData);
// Load availabilities for all employees
const availabilityPromises = employeesData
@@ -73,6 +77,7 @@ const ShiftPlanView: React.FC = () => {
);
setAvailabilities(planAvailabilities);
+ debugAvailabilities();
} catch (error) {
console.error('Error loading shift plan data:', error);
@@ -86,8 +91,70 @@ const ShiftPlanView: React.FC = () => {
}
};
+ const debugAvailabilities = () => {
+ if (!shiftPlan || !employees.length || !availabilities.length) return;
+
+ console.log('🔍 AVAILABILITY ANALYSIS:', {
+ totalAvailabilities: availabilities.length,
+ employeesWithAvailabilities: new Set(availabilities.map(a => a.employeeId)).size,
+ totalEmployees: employees.length,
+ availabilityByEmployee: employees.map(emp => {
+ const empAvailabilities = availabilities.filter(a => a.employeeId === emp.id);
+ return {
+ employee: emp.name,
+ availabilities: empAvailabilities.length,
+ preferences: empAvailabilities.map(a => ({
+ day: a.dayOfWeek,
+ timeSlot: a.timeSlotId,
+ preference: a.preferenceLevel
+ }))
+ };
+ })
+ });
+
+ // Prüfe spezifisch für Manager/Admin
+ const manager = employees.find(emp => emp.role === 'admin');
+ if (manager) {
+ const managerAvailabilities = availabilities.filter(a => a.employeeId === manager.id);
+ console.log('🔍 MANAGER AVAILABILITIES:', {
+ manager: manager.name,
+ availabilities: managerAvailabilities.length,
+ details: managerAvailabilities.map(a => ({
+ day: a.dayOfWeek,
+ timeSlot: a.timeSlotId,
+ preference: a.preferenceLevel
+ }))
+ });
+ }
+ };
+
+ const debugScheduledShifts = async () => {
+ if (!shiftPlan) return;
+
+ try {
+ const shifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
+ console.log('🔍 SCHEDULED SHIFTS IN DATABASE:', {
+ total: shifts.length,
+ shifts: shifts.map(s => ({
+ id: s.id,
+ date: s.date,
+ timeSlotId: s.timeSlotId,
+ requiredEmployees: s.requiredEmployees
+ }))
+ });
+
+ // Check if we have any shifts at all
+ if (shifts.length === 0) {
+ console.error('❌ NO SCHEDULED SHIFTS IN DATABASE - This is the problem!');
+ console.log('💡 Solution: Regenerate scheduled shifts for this plan');
+ }
+ } catch (error) {
+ console.error('❌ Error loading scheduled shifts:', error);
+ }
+ };
+
// Extract plan-specific shifts using the same logic as AvailabilityManager
- const getTimetableData = () => {
+ const getTimetableData = () => {
if (!shiftPlan || !shiftPlan.shifts || !shiftPlan.timeSlots) {
return { days: [], timeSlotsByDay: {}, allTimeSlots: [] };
}
@@ -199,11 +266,53 @@ const ShiftPlanView: React.FC = () => {
});
};*/
+ const debugAssignments = async () => {
+ if (!shiftPlan) return;
+
+ try {
+ const shifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
+ console.log('🔍 DEBUG - Scheduled Shifts nach Veröffentlichung:', {
+ totalShifts: shifts.length,
+ shiftsWithAssignments: shifts.filter(s => s.assignedEmployees && s.assignedEmployees.length > 0).length,
+ allShifts: shifts.map(s => ({
+ id: s.id,
+ date: s.date,
+ timeSlotId: s.timeSlotId,
+ assignedEmployees: s.assignedEmployees,
+ assignedCount: s.assignedEmployees?.length || 0
+ }))
+ });
+ } catch (error) {
+ console.error('Debug error:', error);
+ }
+ };
+
const handlePreviewAssignment = async () => {
if (!shiftPlan) return;
+ debugScheduledShifts();
try {
setPublishing(true);
+
+ // DEBUG: Überprüfe die Eingabedaten
+ console.log('🔍 INPUT DATA FOR SCHEDULING:', {
+ shiftPlan: {
+ id: shiftPlan.id,
+ name: shiftPlan.name,
+ shifts: shiftPlan.shifts?.length,
+ timeSlots: shiftPlan.timeSlots?.length
+ },
+ employees: employees.length,
+ availabilities: availabilities.length,
+ employeeDetails: employees.map(emp => ({
+ id: emp.id,
+ name: emp.name,
+ role: emp.role,
+ employeeType: emp.employeeType,
+ canWorkAlone: emp.canWorkAlone
+ }))
+ });
+
const result = await ShiftAssignmentService.assignShifts(
shiftPlan,
employees,
@@ -215,6 +324,18 @@ const ShiftPlanView: React.FC = () => {
}
);
+ // DEBUG: Detaillierte Analyse des Results
+ console.log('🔍 DETAILED ASSIGNMENT RESULT:', {
+ totalAssignments: Object.keys(result.assignments).length,
+ assignments: result.assignments,
+ violations: result.violations,
+ hasResolutionReport: !!result.resolutionReport,
+ assignmentDetails: Object.entries(result.assignments).map(([shiftId, empIds]) => ({
+ shiftId,
+ employeeCount: empIds.length,
+ employees: empIds
+ }))
+ });
// DEBUG: Überprüfe die tatsächlichen Violations
console.log('🔍 VIOLATIONS ANALYSIS:', {
allViolations: result.violations,
@@ -283,24 +404,24 @@ const ShiftPlanView: React.FC = () => {
console.log('🔄 Starting to publish assignments...');
- const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
-
+ // ✅ KORREKTUR: Verwende die neu geladenen Shifts
+ const updatedShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
+
// Debug: Check if scheduled shifts exist
- if (!scheduledShifts || scheduledShifts.length === 0) {
+ if (!updatedShifts || updatedShifts.length === 0) {
throw new Error('No scheduled shifts found in the plan');
}
- // Update scheduled shifts with assignments
- const updatePromises = scheduledShifts.map(async (scheduledShift) => {
+ console.log(`📊 Found ${updatedShifts.length} scheduled shifts to update`);
+
+ // ✅ KORREKTUR: Verwende updatedShifts statt scheduledShifts
+ const updatePromises = updatedShifts.map(async (scheduledShift) => {
const assignedEmployees = assignmentResult.assignments[scheduledShift.id] || [];
- console.log(`📝 Updating shift ${scheduledShift.id} with`, assignedEmployees.length, 'employees');
+ console.log(`📝 Updating shift ${scheduledShift.id} with`, assignedEmployees, 'employees');
try {
- // First, verify the shift exists
- await shiftAssignmentService.getScheduledShift(scheduledShift.id);
-
- // Then update it
+ // Update the shift with assigned employees
await shiftAssignmentService.updateScheduledShift(scheduledShift.id, {
assignedEmployees
});
@@ -320,26 +441,43 @@ const ShiftPlanView: React.FC = () => {
status: 'published'
});
+ // ✅ KORREKTUR: Explizit alle Daten neu laden und State aktualisieren
+ const [reloadedPlan, reloadedShifts] = await Promise.all([
+ shiftPlanService.getShiftPlan(shiftPlan.id),
+ shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id)
+ ]);
+
+ setShiftPlan(reloadedPlan);
+ setScheduledShifts(reloadedShifts);
+
+ // Debug: Überprüfe die aktualisierten Daten
+ console.log('🔍 After publish - Reloaded data:', {
+ planStatus: reloadedPlan.status,
+ scheduledShiftsCount: reloadedShifts.length,
+ shiftsWithAssignments: reloadedShifts.filter(s => s.assignedEmployees && s.assignedEmployees.length > 0).length,
+ allAssignments: reloadedShifts.map(s => ({
+ id: s.id,
+ date: s.date,
+ assigned: s.assignedEmployees
+ }))
+ });
+
showNotification({
type: 'success',
title: 'Erfolg',
message: 'Schichtplan wurde erfolgreich veröffentlicht!'
});
- // Reload the plan to reflect changes
- loadShiftPlanData();
setShowAssignmentPreview(false);
} catch (error) {
console.error('❌ Error publishing shift plan:', error);
-
+
let message = 'Unbekannter Fehler';
if (error instanceof Error) {
message = error.message;
- } else if (typeof error === 'string') {
- message = error;
}
-
+
showNotification({
type: 'error',
title: 'Fehler',
@@ -378,7 +516,7 @@ const ShiftPlanView: React.FC = () => {
message: 'Schichtplan wurde erfolgreich zurück in den Entwurfsstatus gesetzt. Alle Daten wurden neu geladen.'
});
- const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
+ //const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
console.log('Scheduled shifts after revert:', {
hasScheduledShifts: !! scheduledShifts,
count: scheduledShifts.length || 0,
@@ -422,15 +560,38 @@ const ShiftPlanView: React.FC = () => {
};
};
+ const debugCurrentState = () => {
+ console.log('🔍 CURRENT STATE DEBUG:', {
+ shiftPlan: shiftPlan ? {
+ id: shiftPlan.id,
+ name: shiftPlan.name,
+ status: shiftPlan.status
+ } : null,
+ scheduledShifts: {
+ count: scheduledShifts.length,
+ withAssignments: scheduledShifts.filter(s => s.assignedEmployees && s.assignedEmployees.length > 0).length,
+ details: scheduledShifts.map(s => ({
+ id: s.id,
+ date: s.date,
+ timeSlotId: s.timeSlotId,
+ assignedEmployees: s.assignedEmployees
+ }))
+ },
+ employees: employees.length
+ });
+ };
+
// Render timetable using the same structure as AvailabilityManager
- const renderTimetable = async () => {
+ const renderTimetable = () => {
+ debugAssignments();
+ debugCurrentState();
const { days, allTimeSlots, timeSlotsByDay } = getTimetableData();
if (!shiftPlan?.id) {
console.warn("Shift plan ID is missing");
- return []; // safely exit
+ return null;
}
- const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
+ //const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
if (days.length === 0 || allTimeSlots.length === 0) {
@@ -534,10 +695,8 @@ const ShiftPlanView: React.FC = () => {
// Get assigned employees for this shift
let assignedEmployees: string[] = [];
let displayText = '';
-
-
- if (shiftPlan?.status === 'published' && scheduledShifts) {
+ if (shiftPlan?.status === 'published') {
// For published plans, use actual assignments from scheduled shifts
const scheduledShift = scheduledShifts.find(scheduled => {
const scheduledDayOfWeek = getDayOfWeek(scheduled.date);
@@ -554,7 +713,7 @@ const ShiftPlanView: React.FC = () => {
}
} else if (assignmentResult) {
// For draft with preview, use assignment result
- const scheduledShift = scheduledShifts?.find(scheduled => {
+ const scheduledShift = scheduledShifts.find(scheduled => {
const scheduledDayOfWeek = getDayOfWeek(scheduled.date);
return scheduledDayOfWeek === weekday.id &&
scheduled.timeSlotId === timeSlot.id;
diff --git a/frontend/src/services/shiftAssignmentService.ts b/frontend/src/services/shiftAssignmentService.ts
index f420826..4844d34 100644
--- a/frontend/src/services/shiftAssignmentService.ts
+++ b/frontend/src/services/shiftAssignmentService.ts
@@ -113,14 +113,27 @@ export class ShiftAssignmentService {
throw new Error(`Failed to fetch scheduled shifts: ${response.status}`);
}
- return await response.json();
+ const shifts = await response.json();
+
+ // DEBUG: Check the structure of returned shifts
+ console.log('🔍 SCHEDULED SHIFTS STRUCTURE:', shifts.slice(0, 3));
+
+ // Fix: Ensure timeSlotId is properly mapped
+ const fixedShifts = shifts.map((shift: any) => ({
+ ...shift,
+ timeSlotId: shift.timeSlotId || shift.time_slot_id, // Handle both naming conventions
+ requiredEmployees: shift.requiredEmployees || shift.required_employees || 2, // Default fallback
+ assignedEmployees: shift.assignedEmployees || shift.assigned_employees || []
+ }));
+
+ console.log('✅ Fixed scheduled shifts:', fixedShifts.length);
+ return fixedShifts;
} catch (error) {
console.error('Error fetching scheduled shifts for plan:', error);
throw error;
}
}
-
static async assignShifts(
shiftPlan: ShiftPlan,
employees: Employee[],
@@ -401,28 +414,25 @@ export class ShiftAssignmentService {
// ========== EXISTING HELPER METHODS ==========
static async getDefinedShifts(shiftPlan: ShiftPlan): Promise {
- let scheduledShifts: ScheduledShift[] = [];
try {
- scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
+ const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
+ console.log('📋 Loaded scheduled shifts:', scheduledShifts.length);
+
+ if (!shiftPlan.shifts || shiftPlan.shifts.length === 0) {
+ console.warn('⚠️ No shifts defined in shift plan');
+ return scheduledShifts;
+ }
+
+ // Use first week for weekly pattern (7 days)
+ const firstWeekShifts = this.getFirstWeekShifts(scheduledShifts);
+ console.log('📅 Using first week shifts for pattern:', firstWeekShifts.length);
+
+ return firstWeekShifts;
+
} catch (err) {
- console.error("Failed to load scheduled shifts:", err);
+ console.error("❌ Failed to load scheduled shifts:", err);
return [];
}
- if (scheduledShifts.length) return [];
-
- const definedShiftPatterns = new Set(
- shiftPlan.shifts.map(shift =>
- `${shift.dayOfWeek}-${shift.timeSlotId}`
- )
- );
-
- const definedShifts = scheduledShifts.filter(scheduledShift => {
- const dayOfWeek = this.getDayOfWeek(scheduledShift.date);
- const pattern = `${dayOfWeek}-${scheduledShift.timeSlotId}`;
- return definedShiftPatterns.has(pattern);
- });
-
- return definedShifts;
}
private static countAvailableEmployees(