From b6fd57dfc7a036903154c2e340e4e16883087148 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 13 Oct 2025 14:34:45 +0200 Subject: [PATCH] settings works for every user --- backend/src/controllers/employeeController.ts | 25 +- .../src/controllers/shiftPlanController.ts | 4 +- backend/src/routes/employees.ts | 6 +- backend/src/routes/shiftPlans.ts | 4 +- backend/src/scripts/initializeDatabase.ts | 1 - backend/src/scripts/setupDefaultTemplate.ts | 127 ---- backend/src/server.ts | 5 - frontend/src/App.tsx | 2 +- frontend/src/components/Layout/Navigation.tsx | 2 +- .../components/AvailabilityManager.tsx | 544 ++++++++---------- .../Employees/components/EmployeeForm.tsx | 227 +++++++- frontend/src/pages/Settings/Settings.tsx | 10 +- frontend/src/services/shiftPlanService.ts | 4 +- 13 files changed, 505 insertions(+), 456 deletions(-) delete mode 100644 backend/src/scripts/setupDefaultTemplate.ts diff --git a/backend/src/controllers/employeeController.ts b/backend/src/controllers/employeeController.ts index c29a736..69ee828 100644 --- a/backend/src/controllers/employeeController.ts +++ b/backend/src/controllers/employeeController.ts @@ -372,6 +372,15 @@ export const changePassword = async (req: AuthRequest, res: Response): Promise('SELECT password FROM employees WHERE id = ?', [id]); if (!employee) { @@ -379,10 +388,18 @@ export const changePassword = async (req: AuthRequest, res: Response): Promise => { +/*export const getTemplates = async (req: Request, res: Response): Promise => { try { console.log('🔍 Lade Vorlagen...'); @@ -707,7 +707,7 @@ export const getTemplates = async (req: Request, res: Response): Promise = console.error('Error fetching templates:', error); res.status(500).json({ error: 'Internal server error' }); } -}; +};*/ // Neue Funktion: Create from Template /*export const createFromTemplate = async (req: Request, res: Response): Promise => { diff --git a/backend/src/routes/employees.ts b/backend/src/routes/employees.ts index ae9a4af..c1287e1 100644 --- a/backend/src/routes/employees.ts +++ b/backend/src/routes/employees.ts @@ -23,10 +23,10 @@ router.get('/:id', requireRole(['admin', 'instandhalter']), getEmployee); router.post('/', requireRole(['admin']), createEmployee); router.put('/:id', requireRole(['admin']), updateEmployee); router.delete('/:id', requireRole(['admin']), deleteEmployee); -router.put('/:id/password', requireRole(['admin']), changePassword); +router.put('/:id/password', authMiddleware, changePassword); // Availability Routes -router.get('/:employeeId/availabilities', requireRole(['admin', 'instandhalter']), getAvailabilities); -router.put('/:employeeId/availabilities', requireRole(['admin', 'instandhalter']), updateAvailabilities); +router.get('/:employeeId/availabilities', authMiddleware, getAvailabilities); +router.put('/:employeeId/availabilities', authMiddleware, updateAvailabilities); export default router; \ No newline at end of file diff --git a/backend/src/routes/shiftPlans.ts b/backend/src/routes/shiftPlans.ts index 5338b0f..720fc90 100644 --- a/backend/src/routes/shiftPlans.ts +++ b/backend/src/routes/shiftPlans.ts @@ -7,7 +7,7 @@ import { createShiftPlan, updateShiftPlan, deleteShiftPlan, - getTemplates, + //getTemplates, //createFromTemplate, createFromPreset } from '../controllers/shiftPlanController.js'; @@ -22,7 +22,7 @@ router.use(authMiddleware); router.get('/', getShiftPlans); // GET templates only -router.get('/templates', getTemplates); +//router.get('/templates', getTemplates); // GET specific shift plan or template router.get('/:id', getShiftPlan); diff --git a/backend/src/scripts/initializeDatabase.ts b/backend/src/scripts/initializeDatabase.ts index d0e7a30..cc52940 100644 --- a/backend/src/scripts/initializeDatabase.ts +++ b/backend/src/scripts/initializeDatabase.ts @@ -3,7 +3,6 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { db } from '../services/databaseService.js'; -import { setupDefaultTemplate } from './setupDefaultTemplate.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); diff --git a/backend/src/scripts/setupDefaultTemplate.ts b/backend/src/scripts/setupDefaultTemplate.ts deleted file mode 100644 index 989a908..0000000 --- a/backend/src/scripts/setupDefaultTemplate.ts +++ /dev/null @@ -1,127 +0,0 @@ -// backend/src/scripts/setupDefaultTemplate.ts -import { v4 as uuidv4 } from 'uuid'; -import { db } from '../services/databaseService.js'; -import { DEFAULT_ZEBRA_TIME_SLOTS } from '../models/defaults/shiftPlanDefaults.js'; - -interface AdminUser { - id: string; -} - -/** - * Sets up the default shift template if it doesn't exist - * @returns {Promise} - */ -export async function setupDefaultTemplate(): Promise { - try { - // Prüfen ob bereits eine Standard-Vorlage existiert - KORREKTUR: shift_plans verwenden - const existingDefault = await db.get( - 'SELECT * FROM shift_plans WHERE is_template = 1 AND name = ?', - ['Standardwoche'] - ); - - if (existingDefault) { - console.log('Standard-Vorlage existiert bereits'); - return; - } - - // Admin-Benutzer für die Standard-Vorlage finden - KORREKTUR: employees verwenden - const adminUser = await db.get( - 'SELECT id FROM employees WHERE role = ?', - ['admin'] - ); - - if (!adminUser) { - console.log('Kein Admin-Benutzer gefunden. Standard-Vorlage kann nicht erstellt werden.'); - return; - } - - const templateId = uuidv4(); - console.log('🔄 Erstelle Standard-Vorlage mit ID:', templateId); - - // Transaktion starten - await db.run('BEGIN TRANSACTION'); - - try { - // Standard-Vorlage erstellen - KORREKTUR: shift_plans verwenden - await db.run( - `INSERT INTO shift_plans (id, name, description, is_template, status, created_by) - VALUES (?, ?, ?, ?, ?, ?)`, - [ - templateId, - 'Standardwoche', - 'Mo-Do: Vormittags- und Nachmittagsschicht, Fr: nur Vormittagsschicht', - 1, // is_template = true - 'template', // status = 'template' - adminUser.id - ] - ); - - console.log('Standard-Vorlage erstellt:', templateId); - - // Zeit-Slots erstellen - KORREKTUR: time_slots verwenden - const timeSlots = DEFAULT_ZEBRA_TIME_SLOTS.map(slot => ({ - ...slot, - id: uuidv4() - })); - - for (const slot of timeSlots) { - await db.run( - `INSERT INTO time_slots (id, plan_id, name, start_time, end_time, description) - VALUES (?, ?, ?, ?, ?, ?)`, - [slot.id, templateId, slot.name, slot.startTime, slot.endTime, slot.description] - ); - } - - console.log('✅ Zeit-Slots erstellt'); - - // Schichten für Mo-Do - KORREKTUR: shifts verwenden - for (let day = 1; day <= 4; day++) { - // Vormittagsschicht - await db.run( - `INSERT INTO shifts (id, plan_id, day_of_week, time_slot_id, required_employees, color) - VALUES (?, ?, ?, ?, ?, ?)`, - [uuidv4(), templateId, day, timeSlots[0].id, 2, '#3498db'] - ); - - // Nachmittagsschicht - await db.run( - `INSERT INTO shifts (id, plan_id, day_of_week, time_slot_id, required_employees, color) - VALUES (?, ?, ?, ?, ?, ?)`, - [uuidv4(), templateId, day, timeSlots[1].id, 2, '#e74c3c'] - ); - } - - // Freitag nur Vormittagsschicht - await db.run( - `INSERT INTO shifts (id, plan_id, day_of_week, time_slot_id, required_employees, color) - VALUES (?, ?, ?, ?, ?, ?)`, - [uuidv4(), templateId, 5, timeSlots[0].id, 2, '#3498db'] - ); - - console.log('✅ Schichten erstellt'); - - // In der problematischen Stelle: KORREKTUR: shift_plans verwenden - const createdTemplate = await db.get( - 'SELECT * FROM shift_plans WHERE id = ?', - [templateId] - ) as { name: string } | undefined; - console.log('📋 Erstellte Vorlage:', createdTemplate?.name); - - const shiftCount = await db.get( - 'SELECT COUNT(*) as count FROM shifts WHERE plan_id = ?', - [templateId] - ) as { count: number } | undefined; - console.log(`📊 Anzahl Schichten: ${shiftCount?.count}`); - - await db.run('COMMIT'); - console.log('🎉 Standard-Vorlage erfolgreich initialisiert'); - - } catch (error) { - await db.run('ROLLBACK'); - console.error('❌ Fehler beim Erstellen der Vorlage:', error); - throw error; - } - } catch (error) { - console.error('❌ Fehler in setupDefaultTemplate:', error); - } -} \ No newline at end of file diff --git a/backend/src/server.ts b/backend/src/server.ts index 838021b..3a57f36 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -1,7 +1,6 @@ // backend/src/server.ts import express from 'express'; import cors from 'cors'; -import { setupDefaultTemplate } from './scripts/setupDefaultTemplate.js'; import { initializeDatabase } from './scripts/initializeDatabase.js'; // Route imports @@ -62,10 +61,6 @@ const initializeApp = async () => { const { applyMigration } = await import('./scripts/applyMigration.js'); await applyMigration(); //console.log('✅ Database migrations applied'); - - // Setup default template - await setupDefaultTemplate(); - //console.log('✅ Default template checked/created'); // Start server only after successful initialization app.listen(PORT, () => { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ff2d005..db3cc92 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -111,7 +111,7 @@ const AppContent: React.FC = () => { } /> + } /> diff --git a/frontend/src/components/Layout/Navigation.tsx b/frontend/src/components/Layout/Navigation.tsx index 50b600e..36c42fa 100644 --- a/frontend/src/components/Layout/Navigation.tsx +++ b/frontend/src/components/Layout/Navigation.tsx @@ -20,7 +20,7 @@ const Navigation: React.FC = () => { { 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'] }, + { path: '/settings', label: '⚙️ Einstellungen', roles: ['admin', 'instandhalter', 'user'] }, ]; const filteredNavigation = navigationItems.filter(item => diff --git a/frontend/src/pages/Employees/components/AvailabilityManager.tsx b/frontend/src/pages/Employees/components/AvailabilityManager.tsx index 83b9701..ea77287 100644 --- a/frontend/src/pages/Employees/components/AvailabilityManager.tsx +++ b/frontend/src/pages/Employees/components/AvailabilityManager.tsx @@ -31,10 +31,8 @@ const AvailabilityManager: React.FC = ({ }) => { const [availabilities, setAvailabilities] = useState([]); const [shiftPlans, setShiftPlans] = useState([]); - const [usedDays, setUsedDays] = useState<{id: number, name: string}[]>([]); const [selectedPlanId, setSelectedPlanId] = useState(''); const [selectedPlan, setSelectedPlan] = useState(null); - const [timeSlots, setTimeSlots] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); @@ -62,118 +60,54 @@ const AvailabilityManager: React.FC = ({ useEffect(() => { if (selectedPlanId) { loadSelectedPlan(); - } else { - setTimeSlots([]); } }, [selectedPlanId]); - const getUsedDaysFromPlan = (plan: ShiftPlan | null) => { - if (!plan || !plan.shifts) return []; - - const usedDays = new Set(); - plan.shifts.forEach(shift => { - usedDays.add(shift.dayOfWeek); - }); - - const daysArray = Array.from(usedDays).sort(); - console.log('📅 VERWENDETE TAGE IM PLAN:', daysArray); - - return daysArray.map(dayId => { - return daysOfWeek.find(day => day.id === dayId) || { id: dayId, name: `Tag ${dayId}` }; - }); - }; - - const getUsedTimeSlotsFromPlan = (plan: ShiftPlan | null): ExtendedTimeSlot[] => { - if (!plan || !plan.shifts || !plan.timeSlots) return []; - - const usedTimeSlotIds = new Set(); - plan.shifts.forEach(shift => { - usedTimeSlotIds.add(shift.timeSlotId); - }); - - const usedTimeSlots = plan.timeSlots - .filter(timeSlot => usedTimeSlotIds.has(timeSlot.id)) - .map(timeSlot => ({ - ...timeSlot, - displayName: `${timeSlot.name} (${formatTime(timeSlot.startTime)}-${formatTime(timeSlot.endTime)})`, - source: `Plan: ${plan.name}` - })) - .sort((a, b) => a.startTime.localeCompare(b.startTime)); - - console.log('⏰ VERWENDETE ZEIT-SLOTS IM PLAN:', usedTimeSlots); - return usedTimeSlots; - }; - - - // Load time slots from shift plans - CORRECTED VERSION - const extractTimeSlotsFromPlans = (plans: ShiftPlan[]): ExtendedTimeSlot[] => { - console.log('🔄 EXTRAHIERE ZEIT-SLOTS AUS SCHICHTPLÄNEN:', plans); - - const allTimeSlots = new Map(); - - plans.forEach(plan => { - console.log(`📋 ANALYSIERE PLAN: ${plan.name}`, { - id: plan.id, - timeSlots: plan.timeSlots, - shifts: plan.shifts - }); - - // Use timeSlots from plan if available - if (plan.timeSlots && Array.isArray(plan.timeSlots) && plan.timeSlots.length > 0) { - plan.timeSlots.forEach(timeSlot => { - console.log(` 🔍 ZEIT-SLOT:`, timeSlot); - const key = timeSlot.id; // Use ID as key to avoid duplicates - if (!allTimeSlots.has(key)) { - allTimeSlots.set(key, { - ...timeSlot, - displayName: `${timeSlot.name} (${formatTime(timeSlot.startTime)}-${formatTime(timeSlot.endTime)})`, - source: `Plan: ${plan.name}` - }); - } - }); - } else { - console.warn(`⚠️ PLAN ${plan.name} HAT KEINE TIME_SLOTS:`, plan.timeSlots); - } - - // Alternative: Extract from shifts if timeSlots array exists but is empty - if (plan.shifts && Array.isArray(plan.shifts) && plan.shifts.length > 0) { - console.log(`🔍 VERSUCHE TIME_SLOTS AUS SHIFTS ZU EXTRAHIEREN:`, plan.shifts.length); - - // Create a set of unique timeSlotIds from shifts - const uniqueTimeSlotIds = new Set(plan.shifts.map(shift => shift.timeSlotId)); - - uniqueTimeSlotIds.forEach(timeSlotId => { - // Try to find time slot in plan's timeSlots first - const existingTimeSlot = plan.timeSlots?.find(ts => ts.id === timeSlotId); - - if (existingTimeSlot) { - const key = existingTimeSlot.id; - if (!allTimeSlots.has(key)) { - allTimeSlots.set(key, { - ...existingTimeSlot, - displayName: `${existingTimeSlot.name} (${formatTime(existingTimeSlot.startTime)}-${formatTime(existingTimeSlot.endTime)})`, - source: `Plan: ${plan.name} (from shift)` - }); - } - } else { - // If time slot not found in plan.timeSlots, create a basic one from the ID - console.warn(`⚠️ TIME_SLOT MIT ID ${timeSlotId} NICHT IN PLAN.TIME_SLOTS GEFUNDEN`); - } - }); - } - }); - - const result = Array.from(allTimeSlots.values()).sort((a, b) => - a.startTime.localeCompare(b.startTime) - ); - - console.log('✅ ZEIT-SLOTS AUS PLÄNEN GEFUNDEN:', result.length, result); - return result; - }; - const formatTime = (time: string): string => { if (!time) return '--:--'; - return time.substring(0, 5); // Ensure HH:MM format + return time.substring(0, 5); + }; + + // Create a data structure that maps days to their actual time slots + const getTimetableData = () => { + if (!selectedPlan || !selectedPlan.shifts || !selectedPlan.timeSlots) { + return { days: [], timeSlotsByDay: {} }; + } + + // Group shifts by day + const shiftsByDay = selectedPlan.shifts.reduce((acc, shift) => { + if (!acc[shift.dayOfWeek]) { + acc[shift.dayOfWeek] = []; + } + acc[shift.dayOfWeek].push(shift); + return acc; + }, {} as Record); + + // Get unique days that have shifts + const days = Array.from(new Set(selectedPlan.shifts.map(shift => shift.dayOfWeek))) + .sort() + .map(dayId => { + return daysOfWeek.find(day => day.id === dayId) || { id: dayId, name: `Tag ${dayId}` }; + }); + + // For each day, get the time slots that actually have shifts + const timeSlotsByDay: Record = {}; + + days.forEach(day => { + const shiftsForDay = shiftsByDay[day.id] || []; + const timeSlotIdsForDay = new Set(shiftsForDay.map(shift => shift.timeSlotId)); + + timeSlotsByDay[day.id] = selectedPlan.timeSlots + .filter(timeSlot => timeSlotIdsForDay.has(timeSlot.id)) + .map(timeSlot => ({ + ...timeSlot, + displayName: `${timeSlot.name} (${formatTime(timeSlot.startTime)}-${formatTime(timeSlot.endTime)})`, + source: `Plan: ${selectedPlan.name}` + })) + .sort((a, b) => a.startTime.localeCompare(b.startTime)); + }); + + return { days, timeSlotsByDay }; }; const loadData = async () => { @@ -207,16 +141,13 @@ const AvailabilityManager: React.FC = ({ const planWithShifts = plans.find(plan => plan.shifts && plan.shifts.length > 0 && plan.timeSlots && plan.timeSlots.length > 0 - ) || plans[0]; // Fallback to first plan + ) || plans[0]; setSelectedPlanId(planWithShifts.id); console.log('✅ SCHICHTPLAN AUSGEWÄHLT:', planWithShifts.name); // Load the selected plan to get its actual used time slots and days await loadSelectedPlan(); - } else { - setTimeSlots([]); - setUsedDays([]); } // 4. Set existing availabilities @@ -242,19 +173,6 @@ const AvailabilityManager: React.FC = ({ usedDays: Array.from(new Set(plan.shifts?.map(s => s.dayOfWeek) || [])).sort(), usedTimeSlots: Array.from(new Set(plan.shifts?.map(s => s.timeSlotId) || [])).length }); - - // Only show time slots and days that are actually used in the plan - const usedTimeSlots = getUsedTimeSlotsFromPlan(plan); - const usedDays = getUsedDaysFromPlan(plan); - - console.log('✅ VERWENDETE DATEN:', { - timeSlots: usedTimeSlots.length, - days: usedDays.length, - dayIds: usedDays.map(d => d.id) - }); - - setTimeSlots(usedTimeSlots); - setUsedDays(usedDays); // We'll add this state variable } catch (err: any) { console.error('❌ FEHLER BEIM LADEN DES SCHICHTPLANS:', err); setError('Schichtplan konnte nicht geladen werden: ' + (err.message || 'Unbekannter Fehler')); @@ -270,8 +188,6 @@ const AvailabilityManager: React.FC = ({ avail.timeSlotId === timeSlotId ); - console.log(`🔍 EXISTIERENDE VERFÜGBARKEIT GEFUNDEN AN INDEX:`, existingIndex); - if (existingIndex >= 0) { // Update existing availability const updated = [...prev]; @@ -280,7 +196,6 @@ const AvailabilityManager: React.FC = ({ preferenceLevel: level, isAvailable: level !== 3 }; - console.log('✅ VERFÜGBARKEIT AKTUALISIERT:', updated[existingIndex]); return updated; } else { // Create new availability @@ -293,7 +208,6 @@ const AvailabilityManager: React.FC = ({ preferenceLevel: level, isAvailable: level !== 3 }; - console.log('🆕 NEUE VERFÜGBARKEIT ERSTELLT:', newAvailability); return [...prev, newAvailability]; } }); @@ -306,11 +220,186 @@ const AvailabilityManager: React.FC = ({ ); const result = availability?.preferenceLevel || 3; - console.log(`🔍 ABFRAGE VERFÜGBARKEIT: Tag ${dayId}, Slot ${timeSlotId} = Level ${result}`); - return result; }; + // Update the timetable rendering to use the new data structure + const renderTimetable = () => { + const { days, timeSlotsByDay } = getTimetableData(); + + if (days.length === 0 || Object.keys(timeSlotsByDay).length === 0) { + return ( +
+
📅
+

Keine Shifts im ausgewählten Plan

+

Der ausgewählte Schichtplan hat keine Shifts definiert.

+
+ ); + } + + // Get all unique time slots across all days for row headers + const allTimeSlotIds = new Set(); + days.forEach(day => { + timeSlotsByDay[day.id]?.forEach(timeSlot => { + allTimeSlotIds.add(timeSlot.id); + }); + }); + + const allTimeSlots = Array.from(allTimeSlotIds) + .map(id => selectedPlan?.timeSlots?.find(ts => ts.id === id)) + .filter(Boolean) + .map(timeSlot => ({ + ...timeSlot!, + displayName: `${timeSlot!.name} (${formatTime(timeSlot!.startTime)}-${formatTime(timeSlot!.endTime)})`, + source: `Plan: ${selectedPlan!.name}` + })) + .sort((a, b) => a.startTime.localeCompare(b.startTime)); + + return ( +
+
+ Verfügbarkeit definieren +
+ {allTimeSlots.length} Schichttypen • {days.length} Tage • Nur tatsächlich im Plan verwendete Schichten +
+
+ +
+ + + + + {days.map(weekday => ( + + ))} + + + + {allTimeSlots.map((timeSlot, timeIndex) => ( + + + {days.map(weekday => { + // Check if this time slot exists for this day + const timeSlotForDay = timeSlotsByDay[weekday.id]?.find(ts => ts.id === timeSlot.id); + + if (!timeSlotForDay) { + return ( + + ); + } + + const currentLevel = getAvailabilityForDayAndSlot(weekday.id, timeSlot.id); + const levelConfig = availabilityLevels.find(l => l.level === currentLevel); + + return ( + + ); + })} + + ))} + +
+ Schicht (Zeit) + + {weekday.name} +
+ {timeSlot.displayName} +
+ {timeSlot.source} +
+
+ - + + +
+
+
+ ); + }; + const handleSave = async () => { try { setSaving(true); @@ -321,10 +410,13 @@ const AvailabilityManager: React.FC = ({ return; } - // Filter availabilities to only include those with actual time slots - const validAvailabilities = availabilities.filter(avail => - timeSlots.some(slot => slot.id === avail.timeSlotId) - ); + const { days, timeSlotsByDay } = getTimetableData(); + + // Filter availabilities to only include those with actual shifts + const validAvailabilities = availabilities.filter(avail => { + const timeSlotsForDay = timeSlotsByDay[avail.dayOfWeek] || []; + return timeSlotsForDay.some(slot => slot.id === avail.timeSlotId); + }); if (validAvailabilities.length === 0) { setError('Keine gültigen Verfügbarkeiten zum Speichern gefunden'); @@ -363,9 +455,18 @@ const AvailabilityManager: React.FC = ({ ); } + const { days, timeSlotsByDay } = getTimetableData(); + const allTimeSlotIds = new Set(); + days.forEach(day => { + timeSlotsByDay[day.id]?.forEach(timeSlot => { + allTimeSlotIds.add(timeSlot.id); + }); + }); + const timeSlotsCount = allTimeSlotIds.size; + return (
= ({ {/* Debug-Info */}

- {timeSlots.length === 0 ? '❌ PROBLEM: Keine Zeit-Slots gefunden' : '✅ Plan-Daten geladen'} + {timeSlotsCount === 0 ? '❌ PROBLEM: Keine Zeit-Slots gefunden' : '✅ Plan-Daten geladen'}

Ausgewählter Plan: {selectedPlan?.name || 'Keiner'}
-
Verwendete Zeit-Slots: {timeSlots.length}
-
Verwendete Tage: {usedDays.length} ({usedDays.map(d => d.name).join(', ')})
+
Verwendete Zeit-Slots: {timeSlotsCount}
+
Verwendete Tage: {days.length} ({days.map(d => d.name).join(', ')})
Gesamte Shifts im Plan: {selectedPlan?.shifts?.length || 0}
@@ -415,18 +516,6 @@ const AvailabilityManager: React.FC = ({ )}
-
- Verfügbarkeit für: {selectedPlan?.name || 'Kein Plan ausgewählt'} -
- {timeSlots.length} Schichttypen • {usedDays.length} Tage • Nur tatsächlich im Plan verwendete Schichten und Tage -
-
- {/* Employee Info */}

@@ -534,144 +623,7 @@ const AvailabilityManager: React.FC = ({

{/* Availability Timetable */} - {timeSlots.length > 0 ? ( -
-
- Verfügbarkeit definieren -
- {timeSlots.length} Schichttypen verfügbar • Wählen Sie für jeden Tag und jede Schicht die Verfügbarkeit -
-
- -
- - - - - {usedDays.map(weekday => ( - - ))} - - - - {timeSlots.map((timeSlot, timeIndex) => ( - - - {usedDays.map(weekday => { - const currentLevel = getAvailabilityForDayAndSlot(weekday.id, timeSlot.id); - const levelConfig = availabilityLevels.find(l => l.level === currentLevel); - - return ( - - ); - })} - - ))} - -
- Schicht (Zeit) - - {weekday.name} -
- {timeSlot.displayName} -
- {timeSlot.source} -
-
- -
-
-
- ) : ( -
-
-

Keine Schichttypen konfiguriert

-

Es wurden keine Zeit-Slots in den Schichtplänen gefunden.

-

- Bitte erstellen Sie zuerst Schichtpläne mit Zeit-Slots oder wählen Sie einen anderen Schichtplan aus. -

-
- Gefundene Schichtpläne: {shiftPlans.length}
- Schichtpläne mit TimeSlots: {shiftPlans.filter(p => p.timeSlots && p.timeSlots.length > 0).length} -
-
- )} + {renderTimetable()} {/* Buttons */}
= ({
+ {/* Passwort ändern (nur für Admins im Edit-Modus) */} + {mode === 'edit' && hasRole(['admin']) && ( +
+

🔒 Passwort zurücksetzen

+ + {!showPasswordSection ? ( + + ) : ( +
+
+ + +
+ +
+ + +
+ +
+ Hinweis: Als Administrator können Sie das Passwort des Benutzers ohne Kenntnis des aktuellen Passworts zurücksetzen. +
+ + +
+ )} +
+ )} + {/* Systemrolle (nur für Admins) */} {hasRole(['admin']) && (
= ({ cursor: 'pointer' }} onClick={() => { - // Use a direct setter instead of the function form setFormData(prev => ({ ...prev, role: role.value as 'admin' | 'maintenance' | 'user' @@ -479,7 +692,7 @@ const EmployeeForm: React.FC = ({