added scheduledShiftController in backend; fixed API routes for scheduleController

This commit is contained in:
2025-10-13 23:09:08 +02:00
parent bd85895408
commit e260f91d18
6 changed files with 242 additions and 254 deletions

View File

@@ -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<void> => {
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<void> => {
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<void> => {
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 });
}
};

View File

@@ -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;

View File

@@ -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) => {

View File

@@ -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 (
<div style={{ padding: '20px' }}>
{/* Existing header code... */}
@@ -460,21 +430,6 @@ const ShiftPlanView: React.FC = () => {
>
{publishing ? 'Veröffentliche...' : 'Veröffentlichen'}
</button>
<button
onClick={debugApiEndpoints}
style={{
padding: '8px 16px',
backgroundColor: '#f39c12',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
marginRight: '10px'
}}
>
Test API Endpoints
</button>
</div>
</div>
</div>

View File

@@ -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<void> {
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<any> {
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<ScheduledShift[]> {
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();
}
}
}
export const shiftAssignmentService = new ShiftAssignmentService();

View File

@@ -186,105 +186,4 @@ export const shiftPlanService = {
description: preset.description
}));
},
async updateScheduledShift(id: string, updates: { assignedEmployees: string[] }): Promise<void> {
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<any> {
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<ScheduledShift[]> {
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;
}
}
};