From d60a6d9fae4bb11d911e1f3ca525d6d90163d50a Mon Sep 17 00:00:00 2001 From: donpat1to Date: Tue, 14 Oct 2025 22:47:08 +0200 Subject: [PATCH] removed unnecessary scheduledshift files in backend and put in shiftPlan --- .../controllers/scheduledShiftController.ts | 111 --- .../src/controllers/shiftPlanController.ts | 273 +++++--- backend/src/routes/scheduledShifts.ts | 25 - backend/src/routes/shiftPlans.ts | 24 +- backend/src/server.ts | 2 - frontend/src/pages/Dashboard/Dashboard.tsx | 631 ++++++++++++++---- frontend/src/services/shiftPlanService.ts | 40 +- 7 files changed, 740 insertions(+), 366 deletions(-) delete mode 100644 backend/src/controllers/scheduledShiftController.ts delete mode 100644 backend/src/routes/scheduledShifts.ts diff --git a/backend/src/controllers/scheduledShiftController.ts b/backend/src/controllers/scheduledShiftController.ts deleted file mode 100644 index bc2950e..0000000 --- a/backend/src/controllers/scheduledShiftController.ts +++ /dev/null @@ -1,111 +0,0 @@ -// backend/src/controllers/scheduledShiftController.ts -import { Request, Response } from 'express'; -import { v4 as uuidv4 } from 'uuid'; -import bcrypt from 'bcryptjs'; -import { db } from '../services/databaseService.js'; -import { AuthRequest } from '../middleware/auth.js'; -import { CreateEmployeeRequest } from '../models/Employee.js'; - -export const getScheduledShiftsFromPlan = async (req: AuthRequest, res: Response): Promise => { - try { - const { planId } = req.params; - - const shifts = await db.all( - `SELECT * FROM scheduled_shifts WHERE plan_id = ? ORDER BY date, time_slot_id`, - [planId] - ); - - // Parse JSON arrays safely - const parsedShifts = shifts.map((shift: any) => { - try { - return { - ...shift, - assigned_employees: JSON.parse(shift.assigned_employees || '[]') - }; - } catch (parseError) { - console.error('Error parsing assigned_employees:', parseError); - return { - ...shift, - assigned_employees: [] - }; - } - }); - - res.json(parsedShifts); - } catch (error) { - console.error('Error fetching scheduled shifts:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}; - -export const getScheduledShift = async (req: AuthRequest, res: Response): Promise => { - try { - const { id } = req.params; - - const shift = await db.get( - 'SELECT * FROM scheduled_shifts WHERE id = ?', - [id] - ) as any; - - if (!shift) { - res.status(404).json({ error: 'Scheduled shift not found' }); - } - - // Parse JSON array - const parsedShift = { - ...shift, - assigned_employees: JSON.parse(shift.assigned_employees || '[]') - }; - - res.json(parsedShift); - } catch (error: any) { - console.error('Error fetching scheduled shift:', error); - res.status(500).json({ error: 'Internal server error: ' + error.message }); - } -}; - -export const updateScheduledShift = async (req: AuthRequest, res: Response): Promise => { - try { - const { id } = req.params; - const { assignedEmployees } = req.body; - - console.log('🔄 Updating scheduled shift:', { - id, - assignedEmployees, - body: req.body - }); - - if (!Array.isArray(assignedEmployees)) { - res.status(400).json({ error: 'assignedEmployees must be an array' }); - } - - // Check if shift exists - const existingShift = await db.get( - 'SELECT id FROM scheduled_shifts WHERE id = ?', - [id] - ) as any; - - if (!existingShift) { - console.error('❌ Scheduled shift not found:', id); - res.status(404).json({ error: `Scheduled shift ${id} not found` }); - } - - // Update the shift - const result = await db.run( - 'UPDATE scheduled_shifts SET assigned_employees = ? WHERE id = ?', - [JSON.stringify(assignedEmployees), id] - ); - - console.log('✅ Scheduled shift updated successfully'); - - res.json({ - message: 'Scheduled shift updated successfully', - id: id, - assignedEmployees: assignedEmployees - }); - - } catch (error: any) { - console.error('❌ Error updating scheduled shift:', error); - res.status(500).json({ error: 'Internal server error: ' + error.message }); - } -}; \ No newline at end of file diff --git a/backend/src/controllers/shiftPlanController.ts b/backend/src/controllers/shiftPlanController.ts index c41bf35..413fc36 100644 --- a/backend/src/controllers/shiftPlanController.ts +++ b/backend/src/controllers/shiftPlanController.ts @@ -635,9 +635,9 @@ async function getShiftPlanById(planId: string): Promise { } // Helper function to generate scheduled shifts from template -async function generateScheduledShifts(planId: string, startDate: string, endDate: string): Promise { +export const generateScheduledShifts = async(planId: string, startDate: string, endDate: string): Promise => { try { - console.log(`🔄 Generiere geplante Schichten für Plan ${planId} von ${startDate} bis ${endDate}`); + console.log(`🔄 Generating scheduled shifts for Plan ${planId} from ${startDate} to ${endDate}`); // Get plan with shifts and time slots const plan = await getShiftPlanById(planId); @@ -645,6 +645,9 @@ async function generateScheduledShifts(planId: string, startDate: string, endDat throw new Error('Plan not found'); } + console.log('📋 Plan shifts:', plan.shifts?.length); + console.log('⏰ Plan time slots:', plan.timeSlots?.length); + const start = new Date(startDate); const end = new Date(endDate); @@ -655,6 +658,8 @@ async function generateScheduledShifts(planId: string, startDate: string, endDat // Find shifts for this day of week const shiftsForDay = plan.shifts.filter((shift: any) => shift.dayOfWeek === dayOfWeek); + console.log(`📅 Date: ${date.toISOString().split('T')[0]}, Day: ${dayOfWeek}, Shifts: ${shiftsForDay.length}`); + for (const shift of shiftsForDay) { const scheduledShiftId = uuidv4(); @@ -667,20 +672,74 @@ async function generateScheduledShifts(planId: string, startDate: string, endDat date.toISOString().split('T')[0], // YYYY-MM-DD format shift.timeSlotId, shift.requiredEmployees, - JSON.stringify([]) + JSON.stringify([]) // Start with empty assignments ] ); + + console.log(`✅ Created scheduled shift: ${scheduledShiftId}`); } } - console.log(`✅ Geplante Schichten generiert für Plan ${planId}`); + console.log(`✅ Scheduled shifts generated for Plan ${planId}`); } catch (error) { - console.error('❌ Fehler beim Generieren der geplanten Schichten:', error); + console.error('❌ Error generating scheduled shifts:', error); throw error; } } +export const generateScheduledShiftsForPlan = async (req: Request, res: Response): Promise => { + try { + const { id } = req.params; + + // Check if plan exists + const existingPlan = await getShiftPlanById(id); + if (!existingPlan) { + res.status(404).json({ error: 'Shift plan not found' }); + return; + } + + console.log('🔄 Manually generating scheduled shifts for plan:', { + id, + name: existingPlan.name, + isTemplate: existingPlan.isTemplate, + startDate: existingPlan.startDate, + endDate: existingPlan.endDate, + hasShifts: existingPlan.shifts?.length || 0 + }); + + if (existingPlan.isTemplate) { + res.status(400).json({ error: 'Cannot generate scheduled shifts for templates' }); + return; + } + + if (!existingPlan.startDate || !existingPlan.endDate) { + res.status(400).json({ error: 'Plan must have start and end dates' }); + return; + } + + // Delete existing scheduled shifts + await db.run('DELETE FROM scheduled_shifts WHERE plan_id = ?', [id]); + console.log('🗑️ Deleted existing scheduled shifts'); + + // Generate new scheduled shifts + await generateScheduledShifts(id, existingPlan.startDate, existingPlan.endDate); + + // Return updated plan + const updatedPlan = await getShiftPlanById(id); + + console.log('✅ Successfully generated scheduled shifts:', { + scheduledShifts: updatedPlan.scheduledShifts?.length || 0 + }); + + res.json(updatedPlan); + + } catch (error) { + console.error('❌ Error generating scheduled shifts:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}; + export const revertToDraft = async (req: Request, res: Response): Promise => { try { const { id } = req.params; @@ -729,95 +788,137 @@ export const revertToDraft = async (req: Request, res: Response): Promise } }; -// Neue Funktion: Create from Template -/*export const createFromTemplate = async (req: Request, res: Response): Promise => { +export const regenerateScheduledShifts = async (req: Request, res: Response): Promise => { try { - const { templatePlanId, name, startDate, endDate, description } = req.body; - const userId = (req as AuthRequest).user?.userId; + const { id } = req.params; - if (!userId) { - res.status(401).json({ error: 'Unauthorized' }); + // Check if plan exists + const existingPlan = await getShiftPlanById(id); + if (!existingPlan) { + res.status(404).json({ error: 'Shift plan not found' }); return; } - // Get the template plan - const templatePlan = await getShiftPlanById(templatePlanId); - if (!templatePlan) { - res.status(404).json({ error: 'Template plan not found' }); - return; + // Delete existing scheduled shifts + await db.run('DELETE FROM scheduled_shifts WHERE plan_id = ?', [id]); + + // Generate new scheduled shifts + if (existingPlan.startDate && existingPlan.endDate) { + await generateScheduledShifts(id, existingPlan.startDate, existingPlan.endDate); } - if (!templatePlan.isTemplate) { - res.status(400).json({ error: 'Specified plan is not a template' }); - return; - } - - const planId = uuidv4(); - - await db.run('BEGIN TRANSACTION'); - - try { - // Create new plan from template - await db.run( - `INSERT INTO shift_plans (id, name, description, start_date, end_date, is_template, status, created_by) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, - [planId, name, description || templatePlan.description, startDate, endDate, 0, 'draft', userId] - ); - - // Copy time slots - for (const timeSlot of templatePlan.timeSlots) { - const newTimeSlotId = uuidv4(); - await db.run( - `INSERT INTO time_slots (id, plan_id, name, start_time, end_time, description) - VALUES (?, ?, ?, ?, ?, ?)`, - [newTimeSlotId, planId, timeSlot.name, timeSlot.startTime, timeSlot.endTime, timeSlot.description || ''] - ); - } - - // Get the newly created time slots - const newTimeSlots = await db.all(` - SELECT * FROM time_slots WHERE plan_id = ? ORDER BY start_time - `, [planId]); - - // Copy shifts - for (const shift of templatePlan.shifts) { - const shiftId = uuidv4(); - - // Find matching time slot in new plan - const originalTimeSlot = templatePlan.timeSlots.find((ts: any) => ts.id === shift.timeSlotId); - const newTimeSlot = newTimeSlots.find((ts: any) => - ts.name === originalTimeSlot?.name && - ts.start_time === originalTimeSlot?.startTime && - ts.end_time === originalTimeSlot?.endTime - ); - - if (newTimeSlot) { - await db.run( - `INSERT INTO shifts (id, plan_id, day_of_week, time_slot_id, required_employees, color) - VALUES (?, ?, ?, ?, ?, ?)`, - [shiftId, planId, shift.dayOfWeek, newTimeSlot.id, shift.requiredEmployees, shift.color || '#3498db'] - ); - } - } - - // Generate scheduled shifts for the date range - if (startDate && endDate) { - await generateScheduledShifts(planId, startDate, endDate); - } - - await db.run('COMMIT'); - - // Return created plan - const createdPlan = await getShiftPlanById(planId); - res.status(201).json(createdPlan); - - } catch (error) { - await db.run('ROLLBACK'); - throw error; - } + console.log(`✅ Regenerated scheduled shifts for plan ${id}`); + + // Return updated plan + const updatedPlan = await getShiftPlanById(id); + res.json(updatedPlan); } catch (error) { - console.error('Error creating plan from template:', error); + console.error('Error regenerating scheduled shifts:', error); res.status(500).json({ error: 'Internal server error' }); } -};*/ \ No newline at end of file +}; + +export const getScheduledShiftsFromPlan = async (req: AuthRequest, res: Response): Promise => { + try { + const { planId } = req.params; + + const shifts = await db.all( + `SELECT * FROM scheduled_shifts WHERE plan_id = ? ORDER BY date, time_slot_id`, + [planId] + ); + + // Parse JSON arrays safely + const parsedShifts = shifts.map((shift: any) => { + try { + return { + ...shift, + assigned_employees: JSON.parse(shift.assigned_employees || '[]') + }; + } catch (parseError) { + console.error('Error parsing assigned_employees:', parseError); + return { + ...shift, + assigned_employees: [] + }; + } + }); + + res.json(parsedShifts); + } catch (error) { + console.error('Error fetching scheduled shifts:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}; + +export const getScheduledShift = async (req: AuthRequest, res: Response): Promise => { + try { + const { id } = req.params; + + const shift = await db.get( + 'SELECT * FROM scheduled_shifts WHERE id = ?', + [id] + ) as any; + + if (!shift) { + res.status(404).json({ error: 'Scheduled shift not found' }); + } + + // Parse JSON array + const parsedShift = { + ...shift, + assigned_employees: JSON.parse(shift.assigned_employees || '[]') + }; + + res.json(parsedShift); + } catch (error: any) { + console.error('Error fetching scheduled shift:', error); + res.status(500).json({ error: 'Internal server error: ' + error.message }); + } +}; + +export const updateScheduledShift = async (req: AuthRequest, res: Response): Promise => { + try { + const { id } = req.params; + const { assignedEmployees } = req.body; + + console.log('🔄 Updating scheduled shift:', { + id, + assignedEmployees, + body: req.body + }); + + if (!Array.isArray(assignedEmployees)) { + res.status(400).json({ error: 'assignedEmployees must be an array' }); + } + + // Check if shift exists + const existingShift = await db.get( + 'SELECT id FROM scheduled_shifts WHERE id = ?', + [id] + ) as any; + + if (!existingShift) { + console.error('❌ Scheduled shift not found:', id); + res.status(404).json({ error: `Scheduled shift ${id} not found` }); + } + + // Update the shift + const result = await db.run( + 'UPDATE scheduled_shifts SET assigned_employees = ? WHERE id = ?', + [JSON.stringify(assignedEmployees), id] + ); + + console.log('✅ Scheduled shift updated successfully'); + + res.json({ + message: 'Scheduled shift updated successfully', + id: id, + assignedEmployees: assignedEmployees + }); + + } catch (error: any) { + console.error('❌ Error updating scheduled shift:', error); + res.status(500).json({ error: 'Internal server error: ' + error.message }); + } +}; \ No newline at end of file diff --git a/backend/src/routes/scheduledShifts.ts b/backend/src/routes/scheduledShifts.ts deleted file mode 100644 index fc1bb30..0000000 --- a/backend/src/routes/scheduledShifts.ts +++ /dev/null @@ -1,25 +0,0 @@ -// backend/src/routes/scheduledShifts.ts - COMPLETE REWRITE -import express from 'express'; -import { authMiddleware, requireRole } from '../middleware/auth.js'; -import { getScheduledShiftsFromPlan, getScheduledShift, updateScheduledShift } from '../controllers/scheduledShiftController.js'; - -const router = express.Router(); - -router.use(authMiddleware); - -// Add a simple test route first -/*router.get('/test', (req, res) => { - console.log('✅ /api/scheduled-shifts/test route hit'); - res.json({ message: 'Scheduled shifts router is working!' }); -});*/ - -// GET all scheduled shifts for a plan (for debugging) -router.get('/plan/:planId', requireRole(['admin']), getScheduledShiftsFromPlan); - -// GET specific scheduled shift -router.get('/:id', requireRole(['admin']), getScheduledShift); - -// UPDATE scheduled shift -router.put('/:id', requireRole(['admin']), 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 b94bf38..82294fb 100644 --- a/backend/src/routes/shiftPlans.ts +++ b/backend/src/routes/shiftPlans.ts @@ -10,7 +10,12 @@ import { //getTemplates, //createFromTemplate, createFromPreset, - revertToDraft + revertToDraft, + regenerateScheduledShifts, + generateScheduledShiftsForPlan, + getScheduledShift, + getScheduledShiftsFromPlan, + updateScheduledShift } from '../controllers/shiftPlanController.js'; const router = express.Router(); @@ -46,4 +51,21 @@ router.delete('/:id', requireRole(['admin', 'instandhalter']), deleteShiftPlan); // PUT revert published plan to draft router.put('/:id/revert-to-draft', requireRole(['admin', 'instandhalter']), revertToDraft); + + +// SCHEDULED SHIFTS + +router.post('/:id/generate-shifts', requireRole(['admin', 'instandhalter']), generateScheduledShiftsForPlan); + +router.post('/:id/regenerate-shifts', requireRole(['admin', 'instandhalter']), regenerateScheduledShifts); + +// GET all scheduled shifts for a plan (for debugging) +router.get('/plan/:planId', requireRole(['admin']), getScheduledShiftsFromPlan); + +// GET specific scheduled shift +router.get('/:id', requireRole(['admin']), getScheduledShift); + +// UPDATE scheduled shift +router.put('/:id', requireRole(['admin']), updateScheduledShift); + export default router; \ No newline at end of file diff --git a/backend/src/server.ts b/backend/src/server.ts index 0a2c1b7..07a4bc6 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -8,7 +8,6 @@ import authRoutes from './routes/auth.js'; import employeeRoutes from './routes/employees.js'; import shiftPlanRoutes from './routes/shiftPlans.js'; import setupRoutes from './routes/setup.js'; -import scheduledShiftsRoutes from './routes/scheduledShifts.js'; const app = express(); const PORT = 3002; @@ -22,7 +21,6 @@ app.use('/api/setup', setupRoutes); app.use('/api/auth', authRoutes); app.use('/api/employees', employeeRoutes); app.use('/api/shift-plans', shiftPlanRoutes); -app.use('/api/scheduled-shifts', scheduledShiftsRoutes); // Error handling middleware should come after routes app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { diff --git a/frontend/src/pages/Dashboard/Dashboard.tsx b/frontend/src/pages/Dashboard/Dashboard.tsx index 88bb4de..92704a0 100644 --- a/frontend/src/pages/Dashboard/Dashboard.tsx +++ b/frontend/src/pages/Dashboard/Dashboard.tsx @@ -1,45 +1,274 @@ -// frontend/src/pages/Dashboard/Dashboard.tsx +// frontend/src/pages/Dashboard/Dashboard.tsx - Updated calculations import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { useAuth } from '../../contexts/AuthContext'; +import { shiftPlanService } from '../../services/shiftPlanService'; +import { employeeService } from '../../services/employeeService'; +import { ShiftPlan } from '../../models/ShiftPlan'; +import { Employee } from '../../models/Employee'; -// Mock Data für die Demo -const mockData = { - currentShiftPlan: { - id: '1', - name: 'November Schichtplan 2024', - period: '01.11.2024 - 30.11.2024', - status: 'Aktiv', - shiftsCovered: 85, - totalShifts: 120 - }, - upcomingShifts: [ - { id: '1', date: 'Heute', time: '08:00 - 16:00', type: 'Frühschicht', assigned: true }, - { id: '2', date: 'Morgen', time: '14:00 - 22:00', type: 'Spätschicht', assigned: true }, - { id: '3', date: '15.11.2024', time: '08:00 - 16:00', type: 'Frühschicht', assigned: false } - ], +interface DashboardData { + currentShiftPlan: ShiftPlan | null; + upcomingShifts: Array<{ + id: string; + date: string; + time: string; + type: string; + assigned: boolean; + planName: string; + }>; teamStats: { - totalEmployees: 24, - availableToday: 18, - onVacation: 3, - sickLeave: 2 - }, - recentActivities: [ - { id: '1', action: 'Schichtplan veröffentlicht', user: 'Max Mustermann', time: 'vor 2 Stunden' }, - { id: '2', action: 'Mitarbeiter hinzugefügt', user: 'Sarah Admin', time: 'vor 4 Stunden' }, - { id: '3', action: 'Verfügbarkeit geändert', user: 'Tom Bauer', time: 'vor 1 Tag' } - ] -}; + totalEmployees: number; + availableToday: number; + onVacation: number; + sickLeave: number; + }; + recentPlans: ShiftPlan[]; +} const Dashboard: React.FC = () => { const { user, hasRole } = useAuth(); const [loading, setLoading] = useState(true); + const [data, setData] = useState({ + currentShiftPlan: null, + upcomingShifts: [], + teamStats: { + totalEmployees: 0, + availableToday: 0, + onVacation: 0, + sickLeave: 0 + }, + recentPlans: [] + }); useEffect(() => { - // Simuliere Daten laden - setTimeout(() => setLoading(false), 1000); + loadDashboardData(); }, []); + const loadDashboardData = async () => { + try { + setLoading(true); + + console.log('🔄 Loading dashboard data...'); + + const [shiftPlans, employees] = await Promise.all([ + shiftPlanService.getShiftPlans(), + employeeService.getEmployees() + ]); + + console.log('📊 Loaded data:', { + plans: shiftPlans.length, + employees: employees.length, + plansWithShifts: shiftPlans.filter(p => p.scheduledShifts && p.scheduledShifts.length > 0).length + }); + + // Debug: Log plan details + shiftPlans.forEach(plan => { + console.log(`Plan: ${plan.name}`, { + status: plan.status, + startDate: plan.startDate, + endDate: plan.endDate, + scheduledShifts: plan.scheduledShifts?.length || 0, + isTemplate: plan.isTemplate + }); + }); + + // Find current shift plan (published and current date within range) + const today = new Date().toISOString().split('T')[0]; + const currentPlan = findCurrentShiftPlan(shiftPlans, today); + + // Get user's upcoming shifts + const userShifts = await loadUserUpcomingShifts(shiftPlans, today); + + // Calculate team stats + const activeEmployees = employees.filter(emp => emp.isActive); + const teamStats = calculateTeamStats(activeEmployees); + + // Get recent plans (non-templates, sorted by creation date) + const recentPlans = getRecentPlans(shiftPlans); + + setData({ + currentShiftPlan: currentPlan, + upcomingShifts: userShifts, + teamStats, + recentPlans + }); + + console.log('✅ Dashboard data loaded:', { + currentPlan: currentPlan?.name, + userShifts: userShifts.length, + teamStats, + recentPlans: recentPlans.length + }); + + } catch (error) { + console.error('❌ Error loading dashboard data:', error); + } finally { + setLoading(false); + } + }; + + const findCurrentShiftPlan = (plans: ShiftPlan[], today: string): ShiftPlan | null => { + // First, try to find a published plan where today is within the date range + const activePlan = plans.find(plan => + plan.status === 'published' && + !plan.isTemplate && + plan.startDate && + plan.endDate && + plan.startDate <= today && + plan.endDate >= today + ); + + if (activePlan) { + console.log('✅ Found active plan:', activePlan.name); + return activePlan; + } + + // If no active plan found, try to find the most recent published plan + const publishedPlans = plans + .filter(plan => plan.status === 'published' && !plan.isTemplate) + .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + + console.log('📅 Published plans available:', publishedPlans.map(p => p.name)); + + return publishedPlans[0] || null; + }; + + const loadUserUpcomingShifts = async (shiftPlans: ShiftPlan[], today: string): Promise => { + if (!user) return []; + + try { + const userShifts: DashboardData['upcomingShifts'] = []; + + // Check each plan for user assignments + for (const plan of shiftPlans) { + if (plan.status !== 'published' || !plan.scheduledShifts || plan.scheduledShifts.length === 0) { + continue; + } + + console.log(`🔍 Checking plan ${plan.name} for user shifts:`, plan.scheduledShifts.length); + + for (const scheduledShift of plan.scheduledShifts) { + // Ensure assignedEmployees is an array + const assignedEmployees = Array.isArray(scheduledShift.assignedEmployees) + ? scheduledShift.assignedEmployees + : []; + + if (scheduledShift.date >= today && assignedEmployees.includes(user.id)) { + const timeSlot = plan.timeSlots.find(ts => ts.id === scheduledShift.timeSlotId); + + userShifts.push({ + id: scheduledShift.id, + date: formatShiftDate(scheduledShift.date), + time: timeSlot ? `${timeSlot.startTime} - ${timeSlot.endTime}` : 'Unbekannt', + type: timeSlot?.name || 'Unbekannt', + assigned: true, + planName: plan.name + }); + } + } + } + + // Sort by date and limit to 5 upcoming shifts + return userShifts + .sort((a, b) => { + // Convert formatted dates back to Date objects for sorting + const dateA = a.date === 'Heute' ? today : a.date === 'Morgen' ? + new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().split('T')[0] : a.date; + const dateB = b.date === 'Heute' ? today : b.date === 'Morgen' ? + new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().split('T')[0] : b.date; + + return new Date(dateA).getTime() - new Date(dateB).getTime(); + }) + .slice(0, 5); + + } catch (error) { + console.error('Error loading user shifts:', error); + return []; + } + }; + + const calculateTeamStats = (employees: Employee[]) => { + const totalEmployees = employees.length; + + // For now, we'll use simpler calculations + // In a real app, you'd check actual availability from the database + const availableToday = Math.max(1, Math.floor(totalEmployees * 0.7)); // 70% available as estimate + + return { + totalEmployees, + availableToday, + onVacation: 0, // Would need vacation tracking + sickLeave: 0 // Would need sick leave tracking + }; + }; + + const getRecentPlans = (plans: ShiftPlan[]): ShiftPlan[] => { + return plans + .filter(plan => !plan.isTemplate) + .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) + .slice(0, 3); + }; + + const formatShiftDate = (dateString: string): string => { + const today = new Date().toISOString().split('T')[0]; + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + const tomorrowString = tomorrow.toISOString().split('T')[0]; + + if (dateString === today) { + return 'Heute'; + } else if (dateString === tomorrowString) { + return 'Morgen'; + } else { + return new Date(dateString).toLocaleDateString('de-DE', { + day: '2-digit', + month: '2-digit', + year: 'numeric' + }); + } + }; + + const formatPlanPeriod = (plan: ShiftPlan): string => { + if (!plan.startDate || !plan.endDate) return 'Kein Zeitraum definiert'; + + const start = new Date(plan.startDate).toLocaleDateString('de-DE'); + const end = new Date(plan.endDate).toLocaleDateString('de-DE'); + return `${start} - ${end}`; + }; + + const calculatePlanProgress = (plan: ShiftPlan): { covered: number; total: number; percentage: number } => { + if (!plan.scheduledShifts || plan.scheduledShifts.length === 0) { + console.log(`📊 Plan ${plan.name} has no scheduled shifts`); + return { covered: 0, total: 0, percentage: 0 }; + } + + const totalShifts = plan.scheduledShifts.length; + const coveredShifts = plan.scheduledShifts.filter(shift => { + const assigned = Array.isArray(shift.assignedEmployees) ? shift.assignedEmployees : []; + return assigned.length > 0; + }).length; + + const percentage = totalShifts > 0 ? Math.round((coveredShifts / totalShifts) * 100) : 0; + + console.log(`📊 Plan ${plan.name} progress:`, { + totalShifts, + coveredShifts, + percentage + }); + + return { + covered: coveredShifts, + total: totalShifts, + percentage + }; + }; + + // Add refresh functionality + const handleRefresh = () => { + loadDashboardData(); + }; + if (loading) { return (
@@ -48,6 +277,50 @@ const Dashboard: React.FC = () => { ); } + const regenerateScheduledShifts = async (planId: string) => { + await shiftPlanService.regenerateScheduledShifts(planId); + loadDashboardData(); + }; + + const PlanDebugInfo = () => { + if (!data.currentShiftPlan) return null; + + return ( +
+

🔍 Plan Debug Information:

+
Plan ID: {data.currentShiftPlan.id}
+
Status: {data.currentShiftPlan.status}
+
Is Template: {data.currentShiftPlan.isTemplate ? 'Yes' : 'No'}
+
Start Date: {data.currentShiftPlan.startDate}
+
End Date: {data.currentShiftPlan.endDate}
+
Shifts Defined: {data.currentShiftPlan.shifts?.length || 0}
+
Time Slots: {data.currentShiftPlan.timeSlots?.length || 0}
+
Scheduled Shifts: {data.currentShiftPlan.scheduledShifts?.length || 0}
+ + {data.currentShiftPlan.shifts && data.currentShiftPlan.shifts.length > 0 && ( +
+ Defined Shifts: + {data.currentShiftPlan.shifts.slice(0, 3).map(shift => ( +
+ Day {shift.dayOfWeek} - TimeSlot: {shift.timeSlotId} - Required: {shift.requiredEmployees} +
+ ))} + {data.currentShiftPlan.shifts.length > 3 &&
... and {data.currentShiftPlan.shifts.length - 3} more
} +
+ )} +
+ ); + }; + + const progress = data.currentShiftPlan ? calculatePlanProgress(data.currentShiftPlan) : { covered: 0, total: 0, percentage: 0 }; + return (
{/* Willkommens-Bereich */} @@ -56,21 +329,41 @@ const Dashboard: React.FC = () => { padding: '25px', borderRadius: '8px', marginBottom: '30px', - border: '1px solid #b6d7e8' + border: '1px solid #b6d7e8', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center' }}> -

- Willkommen zurĂĽck, {user?.name}! đź‘‹ -

-

- {new Date().toLocaleDateString('de-DE', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric' - })} -

+
+

+ Willkommen zurĂĽck, {user?.name}! đź‘‹ +

+

+ {new Date().toLocaleDateString('de-DE', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' + })} +

+
+
+ + {/* Quick Actions - Nur fĂĽr Admins/Instandhalter */} {hasRole(['admin', 'instandhalter']) && (
@@ -159,46 +452,78 @@ const Dashboard: React.FC = () => { boxShadow: '0 2px 4px rgba(0,0,0,0.1)' }}>

📊 Aktueller Schichtplan

-
-
- {mockData.currentShiftPlan.name} -
-
- {mockData.currentShiftPlan.period} -
-
- -
-
- Fortschritt: - {mockData.currentShiftPlan.shiftsCovered}/{mockData.currentShiftPlan.totalShifts} Schichten -
-
+ {data.currentShiftPlan ? ( + <> +
+
+ {data.currentShiftPlan.name} +
+
+ {formatPlanPeriod(data.currentShiftPlan)} +
+
+ +
+
+ Fortschritt: + + {progress.covered}/{progress.total} Schichten ({progress.percentage}%) + +
+
+
0 ? '#3498db' : '#95a5a6', + height: '8px', + borderRadius: '10px', + transition: 'width 0.3s ease' + }} /> +
+ {progress.total === 0 && ( +
+ Keine Schichten im Plan definiert +
+ )} +
+
+ display: 'inline-block', + backgroundColor: data.currentShiftPlan.status === 'published' ? '#2ecc71' : '#f39c12', + color: 'white', + padding: '4px 12px', + borderRadius: '20px', + fontSize: '12px', + fontWeight: 'bold' + }}> + {data.currentShiftPlan.status === 'published' ? 'Aktiv' : 'Entwurf'} +
+ + ) : ( +
+
đź“…
+
Kein aktiver Schichtplan
+ {hasRole(['admin', 'instandhalter']) && ( + + + + )}
-
- -
- {mockData.currentShiftPlan.status} -
+ )}
{/* Team-Statistiken */} @@ -214,25 +539,25 @@ const Dashboard: React.FC = () => {
Gesamt Mitarbeiter: - {mockData.teamStats.totalEmployees} + {data.teamStats.totalEmployees}
VerfĂĽgbar heute: - {mockData.teamStats.availableToday} + {data.teamStats.availableToday}
Im Urlaub: - {mockData.teamStats.onVacation} + {data.teamStats.onVacation}
Krankgeschrieben: - {mockData.teamStats.sickLeave} + {data.teamStats.sickLeave}
@@ -255,39 +580,47 @@ const Dashboard: React.FC = () => { boxShadow: '0 2px 4px rgba(0,0,0,0.1)' }}>

⏰ Meine nächsten Schichten

-
- {mockData.upcomingShifts.map(shift => ( -
-
-
{shift.date}
-
{shift.time}
-
{shift.type}
-
-
0 ? ( +
+ {data.upcomingShifts.map(shift => ( +
- {shift.assigned ? 'Zugewiesen' : 'Noch offen'} +
+
{shift.date}
+
{shift.time}
+
{shift.type}
+
{shift.planName}
+
+
+ Zugewiesen +
-
- ))} -
+ ))} +
+ ) : ( +
+
⏰
+
Keine anstehenden Schichten
+
+ )}
)} - {/* Letzte Aktivitäten (für Admins/Instandhalter) */} + {/* Letzte Schichtpläne (für Admins/Instandhalter) */} {hasRole(['admin', 'instandhalter']) && (
{ border: '1px solid #e0e0e0', boxShadow: '0 2px 4px rgba(0,0,0,0.1)' }}> -

📝 Letzte Aktivitäten

-
- {mockData.recentActivities.map(activity => ( -
-
- {activity.action} +

📝 Letzte Schichtpläne

+ {data.recentPlans.length > 0 ? ( +
+ {data.recentPlans.map(plan => ( +
+
+ {plan.name} +
+
+ {formatPlanPeriod(plan)} +
+
+ + Status: {plan.status === 'published' ? 'Veröffentlicht' : + plan.status === 'draft' ? 'Entwurf' : 'Archiviert'} + + + Anzeigen → + +
-
- von {activity.user} -
-
- {activity.time} -
-
- ))} -
+ ))} +
+ ) : ( +
+
đź“‹
+
Noch keine Schichtpläne erstellt
+ + + +
+ )}
)} diff --git a/frontend/src/services/shiftPlanService.ts b/frontend/src/services/shiftPlanService.ts index e69ce99..6e2e5ae 100644 --- a/frontend/src/services/shiftPlanService.ts +++ b/frontend/src/services/shiftPlanService.ts @@ -40,7 +40,13 @@ export const shiftPlanService = { throw new Error('Fehler beim Laden der Schichtpläne'); } - return await response.json(); + const plans = await response.json(); + + // Ensure scheduledShifts is always an array + return plans.map((plan: any) => ({ + ...plan, + scheduledShifts: plan.scheduledShifts || [] + })); }, async getShiftPlan(id: string): Promise { @@ -150,15 +156,29 @@ export const shiftPlanService = { return handleResponse(response); }, - // Create plan from template - /*createFromTemplate: async (data: CreateShiftFromTemplateRequest): Promise => { - const response = await fetch(`${API_BASE}/from-template`, { - method: 'POST', - headers: getAuthHeaders(), - body: JSON.stringify(data), - }); - return handleResponse(response); - },*/ + + async regenerateScheduledShifts(planId: string):Promise { + try { + console.log('🔄 Attempting to regenerate scheduled shifts...'); + + // You'll need to add this API endpoint to your backend + const response = await fetch(`${API_BASE}/${planId}/regenerate-shifts`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('token')}` + } + }); + + if (response.ok) { + console.log('✅ Scheduled shifts regenerated'); + } else { + console.error('❌ Failed to regenerate shifts'); + } + } catch (error) { + console.error('❌ Error regenerating shifts:', error); + } + }, // Create new plan createPlan: async (data: CreateShiftPlanRequest): Promise => {