diff --git a/backend/src/controllers/scheduledShiftController.ts b/backend/src/controllers/scheduledShiftController.ts new file mode 100644 index 0000000..bc2950e --- /dev/null +++ b/backend/src/controllers/scheduledShiftController.ts @@ -0,0 +1,111 @@ +// 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/routes/scheduledShifts.ts b/backend/src/routes/scheduledShifts.ts index f24ab75..fc1bb30 100644 --- a/backend/src/routes/scheduledShifts.ts +++ b/backend/src/routes/scheduledShifts.ts @@ -1,117 +1,25 @@ // backend/src/routes/scheduledShifts.ts - COMPLETE REWRITE import express from 'express'; import { authMiddleware, requireRole } from '../middleware/auth.js'; -import { db } from '../services/databaseService.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', async (req, res) => { - 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' }); - } -}); +router.get('/plan/:planId', requireRole(['admin']), getScheduledShiftsFromPlan); // GET specific scheduled shift -router.get('/:id', async (req, res) => { - try { - const { id } = req.params; - - const shift = await db.get( - 'SELECT * FROM scheduled_shifts WHERE id = ?', - [id] - ) as any; - - if (!shift) { - return 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 }); - } -}); +router.get('/:id', requireRole(['admin']), getScheduledShift); // UPDATE scheduled shift -router.put('/:id', requireRole(['admin', 'instandhalter']), async (req, res) => { - try { - const { id } = req.params; - const { assignedEmployees } = req.body; - - console.log('๐Ÿ”„ Updating scheduled shift:', { - id, - assignedEmployees, - body: req.body - }); - - if (!Array.isArray(assignedEmployees)) { - return 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); - return 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 }); - } -}); +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 5af4814..0a2c1b7 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -8,7 +8,7 @@ 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 scheduledShiftsRouter from './routes/scheduledShifts.js'; +import scheduledShiftsRoutes from './routes/scheduledShifts.js'; const app = express(); const PORT = 3002; @@ -22,7 +22,7 @@ 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', scheduledShiftsRouter); +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/ShiftPlans/ShiftPlanView.tsx b/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx index 3d8ef01..b7d7054 100644 --- a/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx +++ b/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx @@ -4,7 +4,7 @@ import { useParams, useNavigate } from 'react-router-dom'; import { useAuth } from '../../contexts/AuthContext'; import { shiftPlanService } from '../../services/shiftPlanService'; import { employeeService } from '../../services/employeeService'; -import { ShiftAssignmentService, AssignmentResult } from '../../services/shiftAssignmentService'; +import { shiftAssignmentService, ShiftAssignmentService, AssignmentResult } from '../../services/shiftAssignmentService'; import { ShiftPlan, TimeSlot } from '../../models/ShiftPlan'; import { Employee, EmployeeAvailability } from '../../models/Employee'; import { useNotification } from '../../contexts/NotificationContext'; @@ -138,10 +138,10 @@ const ShiftPlanView: React.FC = () => { try { // First, verify the shift exists - await shiftPlanService.getScheduledShift(scheduledShift.id); + await shiftAssignmentService.getScheduledShift(scheduledShift.id); // Then update it - await shiftPlanService.updateScheduledShift(scheduledShift.id, { + await shiftAssignmentService.updateScheduledShift(scheduledShift.id, { assignedEmployees }); @@ -254,36 +254,6 @@ const ShiftPlanView: React.FC = () => { const timetableData = getTimetableData(); - const debugApiEndpoints = async () => { - if (!shiftPlan) return; - - console.log('๐Ÿ” Testing API endpoints for plan:', shiftPlan.id); - - try { - // Test the scheduled shifts endpoint - const shifts = await shiftPlanService.getScheduledShiftsForPlan(shiftPlan.id); - console.log('โœ… GET /api/scheduled-shifts/plan/:planId works:', shifts.length, 'shifts found'); - - if (shifts.length > 0) { - const firstShift = shifts[0]; - console.log('๐Ÿ” First shift:', firstShift); - - // Test updating the first shift - try { - await shiftPlanService.updateScheduledShift(firstShift.id, { - assignedEmployees: ['test-employee'] - }); - console.log('โœ… PUT /api/scheduled-shifts/:id works'); - } catch (updateError) { - console.error('โŒ PUT /api/scheduled-shifts/:id failed:', updateError); - } - } - } catch (error) { - console.error('โŒ GET /api/scheduled-shifts/plan/:planId failed:', error); - } - }; - - return (
{/* Existing header code... */} @@ -460,21 +430,6 @@ const ShiftPlanView: React.FC = () => { > {publishing ? 'Verรถffentliche...' : 'Verรถffentlichen'} - -
diff --git a/frontend/src/services/shiftAssignmentService.ts b/frontend/src/services/shiftAssignmentService.ts index ec62866..89a3e5b 100644 --- a/frontend/src/services/shiftAssignmentService.ts +++ b/frontend/src/services/shiftAssignmentService.ts @@ -1,6 +1,9 @@ // frontend/src/services/shiftAssignmentService.ts - WEEKLY PATTERN VERSION import { ShiftPlan, ScheduledShift } from '../models/ShiftPlan'; import { Employee, EmployeeAvailability } from '../models/Employee'; +import { authService } from './authService'; + +const API_BASE_URL = 'http://localhost:3002/api/scheduled-shifts'; export interface AssignmentResult { assignments: { [shiftId: string]: string[] }; @@ -15,7 +18,117 @@ export interface WeeklyPattern { weekNumber: number; } +// Helper function to get auth headers +const getAuthHeaders = () => { + const token = localStorage.getItem('token'); + return { + 'Content-Type': 'application/json', + ...(token && { 'Authorization': `Bearer ${token}` }) + }; +}; + export class ShiftAssignmentService { + async updateScheduledShift(id: string, updates: { assignedEmployees: string[] }): Promise { + try { + console.log('๐Ÿ”„ Updating scheduled shift via API:', { id, updates }); + + const response = await fetch(`${API_BASE_URL}/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + ...authService.getAuthHeaders() + }, + body: JSON.stringify(updates) + }); + + // First, check if we got any response + if (!response.ok) { + // Try to get error message from response + const responseText = await response.text(); + console.error('โŒ Server response:', responseText); + + let errorMessage = `HTTP ${response.status}: ${response.statusText}`; + + // Try to parse as JSON if possible + try { + const errorData = JSON.parse(responseText); + errorMessage = errorData.error || errorMessage; + } catch (e) { + // If not JSON, use the text as is + errorMessage = responseText || errorMessage; + } + + throw new Error(errorMessage); + } + + // Try to parse successful response + const responseText = await response.text(); + let result; + try { + result = responseText ? JSON.parse(responseText) : {}; + } catch (e) { + console.warn('โš ๏ธ Response was not JSON, but request succeeded'); + result = { message: 'Update successful' }; + } + + console.log('โœ… Scheduled shift updated successfully:', result); + + } catch (error) { + console.error('โŒ Error updating scheduled shift:', error); + throw error; + } + } + + async getScheduledShift(id: string): Promise { + try { + const response = await fetch(`${API_BASE_URL}/${id}`, { + headers: { + 'Authorization': `Bearer ${localStorage.getItem('token')}` + } + }); + + if (!response.ok) { + const responseText = await response.text(); + let errorMessage = `HTTP ${response.status}: ${response.statusText}`; + + try { + const errorData = JSON.parse(responseText); + errorMessage = errorData.error || errorMessage; + } catch (e) { + errorMessage = responseText || errorMessage; + } + + throw new Error(errorMessage); + } + + const responseText = await response.text(); + return responseText ? JSON.parse(responseText) : {}; + } catch (error) { + console.error('Error fetching scheduled shift:', error); + throw error; + } + } + + // New method to get all scheduled shifts for a plan + async getScheduledShiftsForPlan(planId: string): Promise { + try { + const response = await fetch(`${API_BASE_URL}/plan/${planId}`, { + headers: { + 'Authorization': `Bearer ${localStorage.getItem('token')}` + } + }); + + if (!response.ok) { + throw new Error(`Failed to fetch scheduled shifts: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error('Error fetching scheduled shifts for plan:', error); + throw error; + } + } + static async assignShifts( shiftPlan: ShiftPlan, @@ -403,4 +516,6 @@ export class ShiftAssignmentService { const date = new Date(dateString); return date.getDay() === 0 ? 7 : date.getDay(); } -} \ No newline at end of file +} + +export const shiftAssignmentService = new ShiftAssignmentService(); \ No newline at end of file diff --git a/frontend/src/services/shiftPlanService.ts b/frontend/src/services/shiftPlanService.ts index ee62431..39fe99d 100644 --- a/frontend/src/services/shiftPlanService.ts +++ b/frontend/src/services/shiftPlanService.ts @@ -186,105 +186,4 @@ export const shiftPlanService = { description: preset.description })); }, - - async updateScheduledShift(id: string, updates: { assignedEmployees: string[] }): Promise { - try { - console.log('๐Ÿ”„ Updating scheduled shift via API:', { id, updates }); - - const response = await fetch(`/api/scheduled-shifts/${id}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}` - }, - body: JSON.stringify(updates) - }); - - // First, check if we got any response - if (!response.ok) { - // Try to get error message from response - const responseText = await response.text(); - console.error('โŒ Server response:', responseText); - - let errorMessage = `HTTP ${response.status}: ${response.statusText}`; - - // Try to parse as JSON if possible - try { - const errorData = JSON.parse(responseText); - errorMessage = errorData.error || errorMessage; - } catch (e) { - // If not JSON, use the text as is - errorMessage = responseText || errorMessage; - } - - throw new Error(errorMessage); - } - - // Try to parse successful response - const responseText = await response.text(); - let result; - try { - result = responseText ? JSON.parse(responseText) : {}; - } catch (e) { - console.warn('โš ๏ธ Response was not JSON, but request succeeded'); - result = { message: 'Update successful' }; - } - - console.log('โœ… Scheduled shift updated successfully:', result); - - } catch (error) { - console.error('โŒ Error updating scheduled shift:', error); - throw error; - } - }, - - async getScheduledShift(id: string): Promise { - try { - const response = await fetch(`/api/scheduled-shifts/${id}`, { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } - }); - - if (!response.ok) { - const responseText = await response.text(); - let errorMessage = `HTTP ${response.status}: ${response.statusText}`; - - try { - const errorData = JSON.parse(responseText); - errorMessage = errorData.error || errorMessage; - } catch (e) { - errorMessage = responseText || errorMessage; - } - - throw new Error(errorMessage); - } - - const responseText = await response.text(); - return responseText ? JSON.parse(responseText) : {}; - } catch (error) { - console.error('Error fetching scheduled shift:', error); - throw error; - } - }, - - // New method to get all scheduled shifts for a plan - async getScheduledShiftsForPlan(planId: string): Promise { - try { - const response = await fetch(`/api/scheduled-shifts/plan/${planId}`, { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } - }); - - if (!response.ok) { - throw new Error(`Failed to fetch scheduled shifts: ${response.status}`); - } - - return await response.json(); - } catch (error) { - console.error('Error fetching scheduled shifts for plan:', error); - throw error; - } - } }; \ No newline at end of file