diff --git a/backend/src/controllers/employeeController.ts b/backend/src/controllers/employeeController.ts index a6631ad..5ef86ef 100644 --- a/backend/src/controllers/employeeController.ts +++ b/backend/src/controllers/employeeController.ts @@ -295,6 +295,8 @@ export const getAvailabilities = async (req: AuthRequest, res: Response): Promis ORDER BY day_of_week, time_slot_id `, [employeeId]); + //console.log('✅ Successfully got availabilities from employee:', availabilities); + res.json(availabilities.map(avail => ({ id: avail.id, employeeId: avail.employee_id, @@ -348,6 +350,9 @@ export const updateAvailabilities = async (req: AuthRequest, res: Response): Pro await db.run('COMMIT'); + console.log('✅ Successfully updated availablities employee:', ); + + // Return updated availabilities const updatedAvailabilities = await db.all(` SELECT * FROM employee_availability @@ -365,6 +370,8 @@ export const updateAvailabilities = async (req: AuthRequest, res: Response): Pro notes: avail.notes }))); + console.log('✅ Successfully updated employee:', updateAvailabilities); + } catch (error) { await db.run('ROLLBACK'); throw error; diff --git a/backend/src/controllers/shiftPlanController.ts b/backend/src/controllers/shiftPlanController.ts index 413fc36..25d60d3 100644 --- a/backend/src/controllers/shiftPlanController.ts +++ b/backend/src/controllers/shiftPlanController.ts @@ -740,53 +740,6 @@ export const generateScheduledShiftsForPlan = async (req: Request, res: Response } }; -export const revertToDraft = async (req: Request, res: Response): Promise => { - try { - const { id } = req.params; - const userId = (req as AuthRequest).user?.userId; - - if (!userId) { - res.status(401).json({ error: 'Unauthorized' }); - return; - } - - // Check if plan exists - const existingPlan = await getShiftPlanById(id); - //const existingPlan: ShiftPlan = await db.get('SELECT * FROM shift_plans WHERE id = ?', [id]); - if (!existingPlan) { - res.status(404).json({ error: 'Shift plan not found' }); - return; - } - - // Only allow reverting from published to draft - if (existingPlan.status !== 'published') { - res.status(400).json({ error: 'Can only revert published plans to draft' }); - return; - } - - // Update plan status to draft - await db.run( - 'UPDATE shift_plans SET status = ? WHERE id = ?', - ['draft', id] - ); - - // Clear all assigned employees from scheduled shifts - await db.run( - 'UPDATE scheduled_shifts SET assigned_employees = ? WHERE plan_id = ?', - [JSON.stringify([]), id] - ); - - console.log(`✅ Plan ${id} reverted to draft status`); - - // Return updated plan - const updatedPlan = await getShiftPlanById(id); - res.json(updatedPlan); - - } catch (error) { - console.error('Error reverting plan to draft:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}; export const regenerateScheduledShifts = async (req: Request, res: Response): Promise => { try { @@ -921,4 +874,63 @@ export const updateScheduledShift = async (req: AuthRequest, res: Response): Pro console.error('❌ Error updating scheduled shift:', error); res.status(500).json({ error: 'Internal server error: ' + error.message }); } +}; + +export const clearAssignments = async (req: Request, res: Response): Promise => { + try { + const { id } = req.params; + + console.log('🔄 Clearing assignments for plan:', id); + + // Check if plan exists + const existingPlan = await db.get('SELECT * FROM shift_plans WHERE id = ?', [id]); + if (!existingPlan) { + res.status(404).json({ error: 'Shift plan not found' }); + return; + } + + await db.run('BEGIN TRANSACTION'); + + try { + // Get all scheduled shifts for this plan + const scheduledShifts = await db.all( + 'SELECT id FROM scheduled_shifts WHERE plan_id = ?', + [id] + ); + + console.log(`📋 Found ${scheduledShifts.length} scheduled shifts to clear`); + + // Clear all assignments (set assigned_employees to empty array) + for (const shift of scheduledShifts) { + await db.run( + 'UPDATE scheduled_shifts SET assigned_employees = ? WHERE id = ?', + [JSON.stringify([]), shift.id] + ); + console.log(`✅ Cleared assignments for shift: ${shift.id}`); + } + + // Update plan status back to draft + await db.run( + 'UPDATE shift_plans SET status = ? WHERE id = ?', + ['draft', id] + ); + + await db.run('COMMIT'); + + console.log(`✅ Successfully cleared all assignments for plan ${id}`); + + res.json({ + message: 'Assignments cleared successfully', + clearedShifts: scheduledShifts.length + }); + + } catch (error) { + await db.run('ROLLBACK'); + throw error; + } + + } catch (error) { + console.error('❌ Error clearing assignments:', error); + res.status(500).json({ error: 'Internal server error' }); + } }; \ No newline at end of file diff --git a/backend/src/routes/employees.ts b/backend/src/routes/employees.ts index c1287e1..d458c47 100644 --- a/backend/src/routes/employees.ts +++ b/backend/src/routes/employees.ts @@ -18,7 +18,7 @@ const router = express.Router(); router.use(authMiddleware); // Employee CRUD Routes -router.get('/', requireRole(['admin', 'instandhalter']), getEmployees); +router.get('/', authMiddleware, getEmployees); router.get('/:id', requireRole(['admin', 'instandhalter']), getEmployee); router.post('/', requireRole(['admin']), createEmployee); router.put('/:id', requireRole(['admin']), updateEmployee); diff --git a/backend/src/routes/scheduledShifts.ts b/backend/src/routes/scheduledShifts.ts index 0f472bf..91d1181 100644 --- a/backend/src/routes/scheduledShifts.ts +++ b/backend/src/routes/scheduledShifts.ts @@ -19,12 +19,12 @@ router.post('/:id/generate-shifts', requireRole(['admin', 'instandhalter']), gen router.post('/:id/regenerate-shifts', requireRole(['admin', 'instandhalter']), regenerateScheduledShifts); // GET all scheduled shifts for a plan -router.get('/plan/:planId', requireRole(['admin']), getScheduledShiftsFromPlan); +router.get('/plan/:planId', authMiddleware, getScheduledShiftsFromPlan); // GET specific scheduled shift -router.get('/:id', requireRole(['admin']), getScheduledShift); +router.get('/:id', authMiddleware, getScheduledShift); // UPDATE scheduled shift -router.put('/:id', requireRole(['admin']), updateScheduledShift); +router.put('/:id', authMiddleware, updateScheduledShift); export default router; \ No newline at end of file diff --git a/backend/src/routes/shiftPlans.ts b/backend/src/routes/shiftPlans.ts index 5d0de79..f171f42 100644 --- a/backend/src/routes/shiftPlans.ts +++ b/backend/src/routes/shiftPlans.ts @@ -8,7 +8,7 @@ import { updateShiftPlan, deleteShiftPlan, createFromPreset, - revertToDraft, + clearAssignments } from '../controllers/shiftPlanController.js'; const router = express.Router(); @@ -18,13 +18,13 @@ router.use(authMiddleware); // Combined routes for both shift plans and templates // GET all shift plans (including templates) -router.get('/', getShiftPlans); +router.get('/' , authMiddleware, getShiftPlans); // GET templates only //router.get('/templates', getTemplates); // GET specific shift plan or template -router.get('/:id', getShiftPlan); +router.get('/:id', authMiddleware, getShiftPlan); // POST create new shift plan router.post('/', requireRole(['admin', 'instandhalter']), createShiftPlan); @@ -41,7 +41,7 @@ router.put('/:id', requireRole(['admin', 'instandhalter']), updateShiftPlan); // DELETE shift plan or template router.delete('/:id', requireRole(['admin', 'instandhalter']), deleteShiftPlan); -// PUT revert published plan to draft -router.put('/:id/revert-to-draft', requireRole(['admin', 'instandhalter']), revertToDraft); +// POST clear assignments and reset to draft +router.post('/:id/clear-assignments', requireRole(['admin', 'instandhalter']), clearAssignments); export default router; \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 98510cc..db3cc92 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -5,7 +5,6 @@ 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'; @@ -133,17 +132,14 @@ const AppContent: React.FC = () => { function App() { return ( - - - - - - - - - - - + + + + + + + + ); } diff --git a/frontend/src/components/Layout/Footer.tsx b/frontend/src/components/Layout/Footer.tsx index e988d81..6fe54b8 100644 --- a/frontend/src/components/Layout/Footer.tsx +++ b/frontend/src/components/Layout/Footer.tsx @@ -4,7 +4,7 @@ import React from 'react'; const Footer: React.FC = () => { const styles = { footer: { - background: 'linear-gradient(135deg, #1a1325 0%, #24163a 100%)', + background: 'linear-gradient(0deg, #161718 0%, #24163a 100%)', color: 'white', marginTop: 'auto', borderTop: '1px solid rgba(251, 250, 246, 0.1)', @@ -44,7 +44,7 @@ const Footer: React.FC = () => { borderTop: '1px solid rgba(251, 250, 246, 0.1)', padding: '1.5rem 2rem', textAlign: 'center' as const, - color: 'rgba(251, 250, 246, 0.6)', + color: '#FBFAF6', fontSize: '0.9rem', }, }; @@ -201,8 +201,8 @@ const Footer: React.FC = () => {

- © 2025 Schichtenplaner. Alle Rechte vorbehalten. | - Made with ❤️ for efficient team management + © 2025 Schichtenplaner | + Made with for efficient team management

diff --git a/frontend/src/design/DesignSystem.tsx b/frontend/src/design/DesignSystem.tsx index e8c57a6..041460f 100644 --- a/frontend/src/design/DesignSystem.tsx +++ b/frontend/src/design/DesignSystem.tsx @@ -1,11 +1,9 @@ // frontend/src/design/DesignSystem.tsx -import React, { createContext, useContext, ReactNode } from 'react'; - -// Design Tokens export const designTokens = { colors: { // Primary Colors - white: '#FBFAF6', + default_white: '#ddd', //boxes + white: '#FBFAF6', //background, fonts black: '#161718', // Purple Gradients @@ -122,275 +120,4 @@ export const designTokens = { xl: '1280px', '2xl': '1536px', }, -} as const; - -// Context for Design System -interface DesignSystemContextType { - tokens: typeof designTokens; - getColor: (path: string) => string; - getSpacing: (size: keyof typeof designTokens.spacing) => string; -} - -const DesignSystemContext = createContext(undefined); - -// Design System Provider -interface DesignSystemProviderProps { - children: ReactNode; -} - -export const DesignSystemProvider: React.FC = ({ children }) => { - const getColor = (path: string): string => { - const parts = path.split('.'); - let current: any = designTokens.colors; - - for (const part of parts) { - if (current[part] === undefined) { - console.warn(`Color path "${path}" not found in design tokens`); - return designTokens.colors.primary; - } - current = current[part]; - } - - return current; - }; - - const getSpacing = (size: keyof typeof designTokens.spacing): string => { - return designTokens.spacing[size]; - }; - - const value: DesignSystemContextType = { - tokens: designTokens, - getColor, - getSpacing, - }; - - return ( - - {children} - - ); -}; - -// Hook to use Design System -export const useDesignSystem = (): DesignSystemContextType => { - const context = useContext(DesignSystemContext); - if (context === undefined) { - throw new Error('useDesignSystem must be used within a DesignSystemProvider'); - } - return context; -}; - -// Utility Components -export interface BoxProps { - children?: ReactNode; - className?: string; - style?: React.CSSProperties; - p?: keyof typeof designTokens.spacing; - px?: keyof typeof designTokens.spacing; - py?: keyof typeof designTokens.spacing; - m?: keyof typeof designTokens.spacing; - mx?: keyof typeof designTokens.spacing; - my?: keyof typeof designTokens.spacing; - bg?: string; - color?: string; - borderRadius?: keyof typeof designTokens.borderRadius; -} - -export const Box: React.FC = ({ - children, - className, - style, - p, - px, - py, - m, - mx, - my, - bg, - color, - borderRadius, - ...props -}) => { - const { tokens, getColor } = useDesignSystem(); - - const boxStyle: React.CSSProperties = { - padding: p && tokens.spacing[p], - paddingLeft: px && tokens.spacing[px], - paddingRight: px && tokens.spacing[px], - paddingTop: py && tokens.spacing[py], - paddingBottom: py && tokens.spacing[py], - margin: m && tokens.spacing[m], - marginLeft: mx && tokens.spacing[mx], - marginRight: mx && tokens.spacing[mx], - marginTop: my && tokens.spacing[my], - marginBottom: my && tokens.spacing[my], - backgroundColor: bg && getColor(bg), - color: color && getColor(color), - borderRadius: borderRadius && tokens.borderRadius[borderRadius], - fontFamily: tokens.typography.fontFamily, - ...style, - }; - - return ( -
- {children} -
- ); -}; - -export interface TextProps { - children: ReactNode; - className?: string; - style?: React.CSSProperties; - variant?: 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl'; - weight?: keyof typeof designTokens.typography.fontWeights; - color?: string; - align?: 'left' | 'center' | 'right' | 'justify'; - lineHeight?: keyof typeof designTokens.typography.lineHeights; - letterSpacing?: keyof typeof designTokens.typography.letterSpacing; -} - -export const Text: React.FC = ({ - children, - className, - style, - variant = 'base', - weight = 'normal', - color = 'text.primary', - align = 'left', - lineHeight = 'normal', - letterSpacing = 'normal', - ...props -}) => { - const { tokens, getColor } = useDesignSystem(); - - const textStyle: React.CSSProperties = { - fontSize: tokens.typography.fontSizes[variant], - fontWeight: tokens.typography.fontWeights[weight], - color: getColor(color), - textAlign: align, - lineHeight: tokens.typography.lineHeights[lineHeight], - letterSpacing: tokens.typography.letterSpacing[letterSpacing], - fontFamily: tokens.typography.fontFamily, - ...style, - }; - - return ( - - {children} - - ); -}; - -// Global Styles Component -export const GlobalStyles: React.FC = () => { - const { tokens } = useDesignSystem(); - - const globalStyles = ` - @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); - - * { - box-sizing: border-box; - margin: 0; - padding: 0; - } - - html { - font-family: ${tokens.typography.fontFamily}; - font-size: 16px; - line-height: ${tokens.typography.lineHeights.normal}; - color: ${tokens.colors.text.primary}; - background-color: ${tokens.colors.background}; - } - - body { - font-family: ${tokens.typography.fontFamily}; - font-weight: ${tokens.typography.fontWeights.normal}; - line-height: ${tokens.typography.lineHeights.normal}; - color: ${tokens.colors.text.primary}; - background-color: ${tokens.colors.background}; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - } - - h1, h2, h3, h4, h5, h6 { - font-family: ${tokens.typography.fontFamily}; - font-weight: ${tokens.typography.fontWeights.bold}; - line-height: ${tokens.typography.lineHeights.tight}; - color: ${tokens.colors.text.primary}; - } - - h1 { - font-size: ${tokens.typography.fontSizes['4xl']}; - letter-spacing: ${tokens.typography.letterSpacing.tight}; - } - - h2 { - font-size: ${tokens.typography.fontSizes['3xl']}; - letter-spacing: ${tokens.typography.letterSpacing.tight}; - } - - h3 { - font-size: ${tokens.typography.fontSizes['2xl']}; - } - - h4 { - font-size: ${tokens.typography.fontSizes.xl}; - } - - h5 { - font-size: ${tokens.typography.fontSizes.lg}; - } - - h6 { - font-size: ${tokens.typography.fontSizes.base}; - } - - p { - font-size: ${tokens.typography.fontSizes.base}; - line-height: ${tokens.typography.lineHeights.relaxed}; - color: ${tokens.colors.text.primary}; - } - - a { - color: ${tokens.colors.primary}; - text-decoration: none; - transition: ${tokens.transitions.default}; - } - - a:hover { - color: ${tokens.colors.secondary}; - } - - button { - font-family: ${tokens.typography.fontFamily}; - transition: ${tokens.transitions.default}; - } - - input, textarea, select { - font-family: ${tokens.typography.fontFamily}; - } - - /* Scrollbar Styling */ - ::-webkit-scrollbar { - width: 8px; - } - - ::-webkit-scrollbar-track { - background: ${tokens.colors.background}; - } - - ::-webkit-scrollbar-thumb { - background: ${tokens.colors.border.medium}; - border-radius: ${tokens.borderRadius.full}; - } - - ::-webkit-scrollbar-thumb:hover { - background: ${tokens.colors.border.dark}; - } - `; - - return ; -}; - -export default designTokens; \ No newline at end of file +} as const; \ No newline at end of file diff --git a/frontend/src/design/index.ts b/frontend/src/design/index.ts deleted file mode 100644 index bb8663b..0000000 --- a/frontend/src/design/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -// frontend/src/design/index.ts -export { designTokens } from './DesignSystem'; -export { DesignSystemProvider, useDesignSystem, GlobalStyles, Box, Text } from './DesignSystem'; -export type { BoxProps, TextProps } from './DesignSystem'; \ No newline at end of file diff --git a/frontend/src/pages/Dashboard/Dashboard.tsx b/frontend/src/pages/Dashboard/Dashboard.tsx index 40088e0..4f7b571 100644 --- a/frontend/src/pages/Dashboard/Dashboard.tsx +++ b/frontend/src/pages/Dashboard/Dashboard.tsx @@ -282,12 +282,7 @@ const Dashboard: React.FC = () => { percentage }; }; - - // Add refresh functionality - const handleRefresh = () => { - loadDashboardData(); - }; - + if (loading) { return (
@@ -370,8 +365,6 @@ const Dashboard: React.FC = () => {
- - {/* Quick Actions - Nur für Admins/Instandhalter */} {hasRole(['admin', 'instandhalter']) && (
diff --git a/frontend/src/pages/Employees/components/AvailabilityManager.tsx b/frontend/src/pages/Employees/components/AvailabilityManager.tsx index ea77287..0761a86 100644 --- a/frontend/src/pages/Employees/components/AvailabilityManager.tsx +++ b/frontend/src/pages/Employees/components/AvailabilityManager.tsx @@ -437,6 +437,8 @@ const AvailabilityManager: React.FC = ({ await employeeService.updateAvailabilities(employee.id, requestData); console.log('✅ VERFÜGBARKEITEN ERFOLGREICH GESPEICHERT'); + + window.dispatchEvent(new CustomEvent('availabilitiesChanged')); onSave(); } catch (err: any) { diff --git a/frontend/src/pages/Employees/components/EmployeeList.tsx b/frontend/src/pages/Employees/components/EmployeeList.tsx index e2fc475..bdce32f 100644 --- a/frontend/src/pages/Employees/components/EmployeeList.tsx +++ b/frontend/src/pages/Employees/components/EmployeeList.tsx @@ -1,4 +1,4 @@ -// frontend/src/pages/Employees/components/EmployeeList.tsx - KORRIGIERT +// frontend/src/pages/Employees/components/EmployeeList.tsx import React, { useState } from 'react'; import { ROLE_CONFIG, EMPLOYEE_TYPE_CONFIG } from '../../../models/defaults/employeeDefaults'; import { Employee } from '../../../models/Employee'; @@ -11,6 +11,9 @@ interface EmployeeListProps { onManageAvailability: (employee: Employee) => void; } +type SortField = 'name' | 'employeeType' | 'canWorkAlone' | 'role' | 'lastLogin'; +type SortDirection = 'asc' | 'desc'; + const EmployeeList: React.FC = ({ employees, onEdit, @@ -19,8 +22,11 @@ const EmployeeList: React.FC = ({ }) => { const [filter, setFilter] = useState<'all' | 'active' | 'inactive'>('active'); const [searchTerm, setSearchTerm] = useState(''); + const [sortField, setSortField] = useState('name'); + const [sortDirection, setSortDirection] = useState('asc'); const { user: currentUser, hasRole } = useAuth(); + // Filter employees based on active/inactive and search term const filteredEmployees = employees.filter(employee => { if (filter === 'active' && !employee.isActive) return false; if (filter === 'inactive' && employee.isActive) return false; @@ -38,6 +44,60 @@ const EmployeeList: React.FC = ({ return true; }); + // Sort employees based on selected field and direction + const sortedEmployees = [...filteredEmployees].sort((a, b) => { + let aValue: any; + let bValue: any; + + switch (sortField) { + case 'name': + aValue = a.name.toLowerCase(); + bValue = b.name.toLowerCase(); + break; + case 'employeeType': + aValue = a.employeeType; + bValue = b.employeeType; + break; + case 'canWorkAlone': + aValue = a.canWorkAlone; + bValue = b.canWorkAlone; + break; + case 'role': + aValue = a.role; + bValue = b.role; + break; + case 'lastLogin': + // Handle null values for lastLogin (put them at the end) + aValue = a.lastLogin ? new Date(a.lastLogin).getTime() : 0; + bValue = b.lastLogin ? new Date(b.lastLogin).getTime() : 0; + break; + default: + return 0; + } + + if (sortDirection === 'asc') { + return aValue < bValue ? -1 : aValue > bValue ? 1 : 0; + } else { + return aValue > bValue ? -1 : aValue < bValue ? 1 : 0; + } + }); + + const handleSort = (field: SortField) => { + if (sortField === field) { + // Toggle direction if same field + setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); + } else { + // New field, default to ascending + setSortField(field); + setSortDirection('asc'); + } + }; + + const getSortIndicator = (field: SortField) => { + if (sortField !== field) return '↕'; + return sortDirection === 'asc' ? '↑' : '↓'; + }; + // Simplified permission checks const canDeleteEmployee = (employee: Employee): boolean => { if (!hasRole(['admin'])) return false; @@ -78,8 +138,8 @@ const EmployeeList: React.FC = ({ const getIndependenceBadge = (canWorkAlone: boolean) => { return canWorkAlone - ? { text: '✅ Eigenständig', color: '#27ae60', bgColor: '#d5f4e6' } - : { text: '❌ Betreuung', color: '#e74c3c', bgColor: '#fadbd8' }; + ? { text: 'Eigenständig', color: '#27ae60', bgColor: '#d5f4e6' } + : { text: 'Betreuung', color: '#e74c3c', bgColor: '#fadbd8' }; }; type Role = typeof ROLE_CONFIG[number]['value']; @@ -161,7 +221,7 @@ const EmployeeList: React.FC = ({
- {filteredEmployees.length} von {employees.length} Mitarbeitern + {sortedEmployees.length} von {employees.length} Mitarbeitern
@@ -185,16 +245,41 @@ const EmployeeList: React.FC = ({ color: '#2c3e50', alignItems: 'center' }}> -
Name & E-Mail
-
Typ
-
Eigenständigkeit
-
Rolle
+
handleSort('name')} + style={{ cursor: 'pointer', userSelect: 'none', display: 'flex', alignItems: 'center', gap: '5px' }} + > + Name & E-Mail {getSortIndicator('name')} +
+
handleSort('employeeType')} + style={{ textAlign: 'center', cursor: 'pointer', userSelect: 'none', display: 'flex', alignItems: 'center', gap: '5px', justifyContent: 'center' }} + > + Typ {getSortIndicator('employeeType')} +
+
handleSort('canWorkAlone')} + style={{ textAlign: 'center', cursor: 'pointer', userSelect: 'none', display: 'flex', alignItems: 'center', gap: '5px', justifyContent: 'center' }} + > + Eigenständigkeit {getSortIndicator('canWorkAlone')} +
+
handleSort('role')} + style={{ textAlign: 'center', cursor: 'pointer', userSelect: 'none', display: 'flex', alignItems: 'center', gap: '5px', justifyContent: 'center' }} + > + Rolle {getSortIndicator('role')} +
Status
-
Letzter Login
+
handleSort('lastLogin')} + style={{ textAlign: 'center', cursor: 'pointer', userSelect: 'none', display: 'flex', alignItems: 'center', gap: '5px', justifyContent: 'center' }} + > + Letzter Login {getSortIndicator('lastLogin')} +
Aktionen
- {filteredEmployees.map(employee => { + {sortedEmployees.map(employee => { const employeeType = getEmployeeTypeBadge(employee.employeeType); const independence = getIndependenceBadge(employee.canWorkAlone); const roleColor = getRoleBadge(employee.role); diff --git a/frontend/src/pages/Help/Help.tsx b/frontend/src/pages/Help/Help.tsx index a211c6a..86edc61 100644 --- a/frontend/src/pages/Help/Help.tsx +++ b/frontend/src/pages/Help/Help.tsx @@ -206,102 +206,7 @@ const Help: React.FC = () => { ))} - - {/* Network Visualization */} -
- {/* Employees */} -
-

👥 Mitarbeiter

-
-
• Manager (1)
-
• Erfahrene ({currentStage >= 1 ? '3' : '0'})
-
• Neue ({currentStage >= 1 ? '2' : '0'})
-
-
- - {/* Shifts */} -
-

📅 Schichten

-
-
• Vormittag (5)
-
• Nachmittag (4)
-
• Manager-Schichten (3)
-
-
- - {/* Current Actions */} -
-

⚡ Aktive Aktionen

-
- {currentStage === 0 && ( - <> -
• Grundzuweisung läuft
-
• Erfahrene priorisieren
- - )} - {currentStage === 1 && ( - <> -
• Manager wird zugewiesen
-
• Erfahrene suchen
- - )} - {currentStage === 2 && ( - <> -
• Überbesetzung prüfen
-
• Pool-Verwaltung aktiv
- - )} - {currentStage === 3 && ( - <> -
• Finale Validierung
-
• Bericht generieren
- - )} -
-
- - {/* Problems & Solutions */} -
-

🔍 Probleme & Lösungen

-
- {currentStage >= 2 ? ( - <> -
✅ 2 Probleme behoben
-
❌ 0 kritische Probleme
-
⚠️ 1 Warnung
- - ) : ( -
Noch keine Probleme analysiert
- )} -
-
- {/* Business Rules */}
{ boxShadow: '0 4px 6px rgba(0,0,0,0.1)', border: '1px solid #e0e0e0' }}> -

📋 Geschäftsregeln

+

📋 Validierungs Regeln

{businessRules.map((rule, index) => (
{
-
+ +
{
  • Planen Sie Manager-Verfügbarkeit im Voraus
  • -