backend without errors

This commit is contained in:
2025-10-11 19:56:43 +02:00
parent 738b7f645a
commit 85dca0ba41
11 changed files with 212 additions and 344 deletions

View File

@@ -14,17 +14,16 @@ export const getEmployees = async (req: AuthRequest, res: Response): Promise<voi
SELECT SELECT
id, email, name, role, is_active as isActive, id, email, name, role, is_active as isActive,
employee_type as employeeType, employee_type as employeeType,
is_sufficiently_independent as isSufficientlyIndependent, contract_type as contractType,
can_work_alone as canWorkAlone,
created_at as createdAt, created_at as createdAt,
last_login as lastLogin last_login as lastLogin
FROM users FROM employees
WHERE is_active = 1 WHERE is_active = 1
ORDER BY name ORDER BY name
`); `);
console.log('✅ Employees found:', employees.length); console.log('✅ Employees found:', employees.length);
console.log('📋 Employees data:', employees);
res.json(employees); res.json(employees);
} catch (error) { } catch (error) {
console.error('❌ Error fetching employees:', error); console.error('❌ Error fetching employees:', error);
@@ -40,10 +39,11 @@ export const getEmployee = async (req: AuthRequest, res: Response): Promise<void
SELECT SELECT
id, email, name, role, is_active as isActive, id, email, name, role, is_active as isActive,
employee_type as employeeType, employee_type as employeeType,
is_sufficiently_independent as isSufficientlyIndependent, contract_type as contractType,
can_work_alone as canWorkAlone,
created_at as createdAt, created_at as createdAt,
last_login as lastLogin last_login as lastLogin
FROM users FROM employees
WHERE id = ? WHERE id = ?
`, [id]); `, [id]);
@@ -72,14 +72,15 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
name, name,
role, role,
employeeType, employeeType,
isSufficientlyIndependent, contractType,
canWorkAlone // Statt isSufficientlyIndependent
} = req.body as CreateEmployeeRequest; } = req.body as CreateEmployeeRequest;
// Validierung // Validierung
if (!email || !password || !name || !role || !employeeType) { if (!email || !password || !name || !role || !employeeType || !contractType) {
console.log('❌ Validation failed: Missing required fields'); console.log('❌ Validation failed: Missing required fields');
res.status(400).json({ res.status(400).json({
error: 'Email, password, name, role und employeeType sind erforderlich' error: 'Email, password, name, role, employeeType und contractType sind erforderlich'
}); });
return; return;
} }
@@ -91,7 +92,7 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
} }
// Check if email already exists // Check if email already exists
const existingActiveUser = await db.get<any>('SELECT id FROM users WHERE email = ? AND is_active = 1', [email]); const existingActiveUser = await db.get<any>('SELECT id FROM employees WHERE email = ? AND is_active = 1', [email]);
if (existingActiveUser) { if (existingActiveUser) {
console.log('❌ Email exists for active user:', existingActiveUser); console.log('❌ Email exists for active user:', existingActiveUser);
@@ -104,10 +105,10 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
const employeeId = uuidv4(); const employeeId = uuidv4();
await db.run( await db.run(
`INSERT INTO users ( `INSERT INTO employees (
id, email, password, name, role, employee_type, is_sufficiently_independent, id, email, password, name, role, employee_type, contract_type, can_work_alone,
is_active is_active
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[ [
employeeId, employeeId,
email, email,
@@ -115,7 +116,8 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
name, name,
role, role,
employeeType, employeeType,
isSufficientlyIndependent ? 1 : 0, contractType,
canWorkAlone ? 1 : 0, // Statt isSufficientlyIndependent
1 1
] ]
); );
@@ -125,10 +127,11 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
SELECT SELECT
id, email, name, role, is_active as isActive, id, email, name, role, is_active as isActive,
employee_type as employeeType, employee_type as employeeType,
is_sufficiently_independent as isSufficientlyIndependent, contract_type as contractType,
can_work_alone as canWorkAlone,
created_at as createdAt, created_at as createdAt,
last_login as lastLogin last_login as lastLogin
FROM users FROM employees
WHERE id = ? WHERE id = ?
`, [employeeId]); `, [employeeId]);
@@ -142,12 +145,12 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
export const updateEmployee = async (req: AuthRequest, res: Response): Promise<void> => { export const updateEmployee = async (req: AuthRequest, res: Response): Promise<void> => {
try { try {
const { id } = req.params; const { id } = req.params;
const { name, role, isActive, employeeType, isSufficientlyIndependent } = req.body; const { name, role, isActive, employeeType, contractType, canWorkAlone } = req.body; // Statt isSufficientlyIndependent
console.log('📝 Update Employee Request:', { id, name, role, isActive, employeeType, isSufficientlyIndependent }); console.log('📝 Update Employee Request:', { id, name, role, isActive, employeeType, contractType, canWorkAlone });
// Check if employee exists // Check if employee exists
const existingEmployee = await db.get('SELECT * FROM users WHERE id = ?', [id]); const existingEmployee = await db.get('SELECT * FROM employees WHERE id = ?', [id]);
if (!existingEmployee) { if (!existingEmployee) {
res.status(404).json({ error: 'Employee not found' }); res.status(404).json({ error: 'Employee not found' });
return; return;
@@ -155,14 +158,15 @@ export const updateEmployee = async (req: AuthRequest, res: Response): Promise<v
// Update employee // Update employee
await db.run( await db.run(
`UPDATE users `UPDATE employees
SET name = COALESCE(?, name), SET name = COALESCE(?, name),
role = COALESCE(?, role), role = COALESCE(?, role),
is_active = COALESCE(?, is_active), is_active = COALESCE(?, is_active),
employee_type = COALESCE(?, employee_type), employee_type = COALESCE(?, employee_type),
is_sufficiently_independent = COALESCE(?, is_sufficiently_independent) contract_type = COALESCE(?, contract_type),
can_work_alone = COALESCE(?, can_work_alone)
WHERE id = ?`, WHERE id = ?`,
[name, role, isActive, employeeType, isSufficientlyIndependent, id] [name, role, isActive, employeeType, contractType, canWorkAlone, id]
); );
console.log('✅ Employee updated successfully'); console.log('✅ Employee updated successfully');
@@ -172,10 +176,11 @@ export const updateEmployee = async (req: AuthRequest, res: Response): Promise<v
SELECT SELECT
id, email, name, role, is_active as isActive, id, email, name, role, is_active as isActive,
employee_type as employeeType, employee_type as employeeType,
is_sufficiently_independent as isSufficientlyIndependent, contract_type as contractType,
can_work_alone as canWorkAlone,
created_at as createdAt, created_at as createdAt,
last_login as lastLogin last_login as lastLogin
FROM users FROM employees
WHERE id = ? WHERE id = ?
`, [id]); `, [id]);
@@ -194,7 +199,7 @@ export const deleteEmployee = async (req: AuthRequest, res: Response): Promise<v
// Check if employee exists // Check if employee exists
const existingEmployee = await db.get<any>(` const existingEmployee = await db.get<any>(`
SELECT id, email, name, is_active, role SELECT id, email, name, is_active, role
FROM users FROM employees
WHERE id = ? WHERE id = ?
`, [id]); `, [id]);
@@ -211,7 +216,7 @@ export const deleteEmployee = async (req: AuthRequest, res: Response): Promise<v
try { try {
// 1. Remove availabilities // 1. Remove availabilities
await db.run('DELETE FROM employee_availabilities WHERE employee_id = ?', [id]); await db.run('DELETE FROM employee_availability WHERE employee_id = ?', [id]);
// 2. Remove from assigned_shifts (JSON field cleanup) // 2. Remove from assigned_shifts (JSON field cleanup)
interface AssignedShift { interface AssignedShift {
@@ -220,7 +225,7 @@ export const deleteEmployee = async (req: AuthRequest, res: Response): Promise<v
} }
const assignedShifts = await db.all<AssignedShift>( const assignedShifts = await db.all<AssignedShift>(
'SELECT id, assigned_employees FROM assigned_shifts WHERE json_extract(assigned_employees, "$") LIKE ?', 'SELECT id, assigned_employees FROM scheduled_shifts WHERE json_extract(assigned_employees, "$") LIKE ?',
[`%${id}%`] [`%${id}%`]
); );
@@ -229,14 +234,13 @@ export const deleteEmployee = async (req: AuthRequest, res: Response): Promise<v
const employeesArray: string[] = JSON.parse(shift.assigned_employees || '[]'); const employeesArray: string[] = JSON.parse(shift.assigned_employees || '[]');
const filteredEmployees = employeesArray.filter((empId: string) => empId !== id); const filteredEmployees = employeesArray.filter((empId: string) => empId !== id);
await db.run( await db.run(
'UPDATE assigned_shifts SET assigned_employees = ? WHERE id = ?', 'UPDATE scheduled_shifts SET assigned_employees = ? WHERE id = ?',
[JSON.stringify(filteredEmployees), shift.id] [JSON.stringify(filteredEmployees), shift.id]
); );
} catch (parseError) { } catch (parseError) {
console.warn(`Could not parse assigned_employees for shift ${shift.id}:`, shift.assigned_employees); console.warn(`Could not parse assigned_employees for shift ${shift.id}:`, shift.assigned_employees);
// Falls JSON parsing fehlschlägt, setze leeres Array
await db.run( await db.run(
'UPDATE assigned_shifts SET assigned_employees = ? WHERE id = ?', 'UPDATE scheduled_shifts SET assigned_employees = ? WHERE id = ?',
[JSON.stringify([]), shift.id] [JSON.stringify([]), shift.id]
); );
} }
@@ -244,10 +248,9 @@ export const deleteEmployee = async (req: AuthRequest, res: Response): Promise<v
// 3. Nullify created_by references // 3. Nullify created_by references
await db.run('UPDATE shift_plans SET created_by = NULL WHERE created_by = ?', [id]); await db.run('UPDATE shift_plans SET created_by = NULL WHERE created_by = ?', [id]);
await db.run('UPDATE shift_templates SET created_by = NULL WHERE created_by = ?', [id]);
// 4. Finally delete the user // 4. Finally delete the employee
await db.run('DELETE FROM users WHERE id = ?', [id]); await db.run('DELETE FROM employees WHERE id = ?', [id]);
await db.run('COMMIT'); await db.run('COMMIT');
console.log('✅ Successfully deleted employee:', existingEmployee.email); console.log('✅ Successfully deleted employee:', existingEmployee.email);
@@ -271,25 +274,26 @@ export const getAvailabilities = async (req: AuthRequest, res: Response): Promis
const { employeeId } = req.params; const { employeeId } = req.params;
// Check if employee exists // Check if employee exists
const existingEmployee = await db.get('SELECT id FROM users WHERE id = ?', [employeeId]); const existingEmployee = await db.get('SELECT id FROM employees WHERE id = ?', [employeeId]);
if (!existingEmployee) { if (!existingEmployee) {
res.status(404).json({ error: 'Employee not found' }); res.status(404).json({ error: 'Employee not found' });
return; return;
} }
const availabilities = await db.all<any>(` const availabilities = await db.all<any>(`
SELECT * FROM employee_availabilities SELECT * FROM employee_availability
WHERE employee_id = ? WHERE employee_id = ?
ORDER BY day_of_week, start_time ORDER BY day_of_week, time_slot_id
`, [employeeId]); `, [employeeId]);
res.json(availabilities.map(avail => ({ res.json(availabilities.map(avail => ({
id: avail.id, id: avail.id,
employeeId: avail.employee_id, employeeId: avail.employee_id,
planId: avail.plan_id,
dayOfWeek: avail.day_of_week, dayOfWeek: avail.day_of_week,
startTime: avail.start_time, timeSlotId: avail.time_slot_id,
endTime: avail.end_time, preferenceLevel: avail.preference_level,
isAvailable: avail.is_available === 1 notes: avail.notes
}))); })));
} catch (error) { } catch (error) {
console.error('Error fetching availabilities:', error); console.error('Error fetching availabilities:', error);
@@ -300,17 +304,10 @@ export const getAvailabilities = async (req: AuthRequest, res: Response): Promis
export const updateAvailabilities = async (req: AuthRequest, res: Response): Promise<void> => { export const updateAvailabilities = async (req: AuthRequest, res: Response): Promise<void> => {
try { try {
const { employeeId } = req.params; const { employeeId } = req.params;
const availabilities = req.body as Array<{ const { planId, availabilities } = req.body;
id?: string;
employeeId: string;
dayOfWeek: number;
startTime: string;
endTime: string;
isAvailable: boolean;
}>;
// Check if employee exists // Check if employee exists
const existingEmployee = await db.get('SELECT id FROM users WHERE id = ?', [employeeId]); const existingEmployee = await db.get('SELECT id FROM employees WHERE id = ?', [employeeId]);
if (!existingEmployee) { if (!existingEmployee) {
res.status(404).json({ error: 'Employee not found' }); res.status(404).json({ error: 'Employee not found' });
return; return;
@@ -319,22 +316,23 @@ export const updateAvailabilities = async (req: AuthRequest, res: Response): Pro
await db.run('BEGIN TRANSACTION'); await db.run('BEGIN TRANSACTION');
try { try {
// Delete existing availabilities // Delete existing availabilities for this plan
await db.run('DELETE FROM employee_availabilities WHERE employee_id = ?', [employeeId]); await db.run('DELETE FROM employee_availability WHERE employee_id = ? AND plan_id = ?', [employeeId, planId]);
// Insert new availabilities // Insert new availabilities
for (const availability of availabilities) { for (const availability of availabilities) {
const availabilityId = uuidv4(); const availabilityId = uuidv4();
await db.run( await db.run(
`INSERT INTO employee_availabilities (id, employee_id, day_of_week, start_time, end_time, is_available) `INSERT INTO employee_availability (id, employee_id, plan_id, day_of_week, time_slot_id, preference_level, notes)
VALUES (?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?)`,
[ [
availabilityId, availabilityId,
employeeId, employeeId,
planId,
availability.dayOfWeek, availability.dayOfWeek,
availability.startTime, availability.timeSlotId,
availability.endTime, availability.preferenceLevel,
availability.isAvailable ? 1 : 0 availability.notes || null
] ]
); );
} }
@@ -343,18 +341,19 @@ export const updateAvailabilities = async (req: AuthRequest, res: Response): Pro
// Return updated availabilities // Return updated availabilities
const updatedAvailabilities = await db.all<any>(` const updatedAvailabilities = await db.all<any>(`
SELECT * FROM employee_availabilities SELECT * FROM employee_availability
WHERE employee_id = ? WHERE employee_id = ? AND plan_id = ?
ORDER BY day_of_week, start_time ORDER BY day_of_week, time_slot_id
`, [employeeId]); `, [employeeId, planId]);
res.json(updatedAvailabilities.map(avail => ({ res.json(updatedAvailabilities.map(avail => ({
id: avail.id, id: avail.id,
employeeId: avail.employee_id, employeeId: avail.employee_id,
planId: avail.plan_id,
dayOfWeek: avail.day_of_week, dayOfWeek: avail.day_of_week,
startTime: avail.start_time, timeSlotId: avail.time_slot_id,
endTime: avail.end_time, preferenceLevel: avail.preference_level,
isAvailable: avail.is_available === 1 notes: avail.notes
}))); })));
} catch (error) { } catch (error) {

View File

@@ -32,7 +32,7 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
try { try {
// Check if admin already exists // Check if admin already exists
const adminExists = await db.get<{ 'COUNT(*)': number }>( const adminExists = await db.get<{ 'COUNT(*)': number }>(
'SELECT COUNT(*) FROM users WHERE role = ? AND is_active = 1', 'SELECT COUNT(*) FROM employees WHERE role = ? AND is_active = 1',
['admin'] ['admin']
); );
@@ -70,7 +70,7 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
try { try {
// Create admin user // Create admin user
await db.run( await db.run(
`INSERT INTO users (id, email, password, name, role, is_active) `INSERT INTO employees (id, email, password, name, role, is_active)
VALUES (?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?)`,
[adminId, email, hashedPassword, name, 'admin', 1] [adminId, email, hashedPassword, name, 'admin', 1]
); );

View File

@@ -24,7 +24,6 @@ export interface TimeSlot {
} }
export interface Shift { export interface Shift {
timeSlot: any;
id: string; id: string;
planId: string; planId: string;
timeSlotId: string; timeSlotId: string;

View File

@@ -1,19 +1,24 @@
// backend/src/scripts/checkTemplates.ts
import { db } from '../services/databaseService.js'; import { db } from '../services/databaseService.js';
import { ShiftPlan } from '../models/ShiftPlan.js';
async function checkTemplates() { async function checkTemplates() {
try { try {
const templates = await db.all<ShiftPlan>( // KORREKTUR: employees statt users verwenden
`SELECT sp.*, u.name as created_by_name const templates = await db.all<any>(
`SELECT sp.*, e.name as created_by_name
FROM shift_plans sp FROM shift_plans sp
LEFT JOIN users u ON sp.created_by = u.id` LEFT JOIN employees e ON sp.created_by = e.id
WHERE sp.is_template = 1`
); );
console.log('Templates:', templates); console.log('Templates:', templates);
for (const template of templates) { for (const template of templates) {
const shifts = await db.all<any>( const shifts = await db.all<any>(
`SELECT * FROM template_shifts WHERE template_id = ?`, `SELECT s.*, ts.name as time_slot_name
FROM shifts s
LEFT JOIN time_slots ts ON s.time_slot_id = ts.id
WHERE s.plan_id = ?`,
[template.id] [template.id]
); );
console.log(`Shifts for template ${template.id}:`, shifts); console.log(`Shifts for template ${template.id}:`, shifts);

View File

@@ -1,7 +1,7 @@
// backend/src/scripts/setupDefaultTemplate.ts // backend/src/scripts/setupDefaultTemplate.ts
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { db } from '../services/databaseService.js'; import { db } from '../services/databaseService.js';
import { DEFAULT_ZEBRA_TIME_SLOTS, TemplateShift } from '../models/ShiftPlan.js'; import { DEFAULT_ZEBRA_TIME_SLOTS } from '../models/defaults/shiftPlanDefaults.js';
interface AdminUser { interface AdminUser {
id: string; id: string;
@@ -13,9 +13,10 @@ interface AdminUser {
*/ */
export async function setupDefaultTemplate(): Promise<void> { export async function setupDefaultTemplate(): Promise<void> {
try { try {
// Prüfen ob bereits eine Standard-Vorlage existiert // Prüfen ob bereits eine Standard-Vorlage existiert - KORREKTUR: shift_plans verwenden
const existingDefault = await db.get( const existingDefault = await db.get(
'SELECT * FROM shift_templates WHERE is_default = 1' 'SELECT * FROM shift_plans WHERE is_template = 1 AND name = ?',
['Standardwoche']
); );
if (existingDefault) { if (existingDefault) {
@@ -23,9 +24,9 @@ export async function setupDefaultTemplate(): Promise<void> {
return; return;
} }
// Admin-Benutzer für die Standard-Vorlage finden // Admin-Benutzer für die Standard-Vorlage finden - KORREKTUR: employees verwenden
const adminUser = await db.get<AdminUser>( const adminUser = await db.get<AdminUser>(
'SELECT id FROM users WHERE role = ?', 'SELECT id FROM employees WHERE role = ?',
['admin'] ['admin']
); );
@@ -40,70 +41,74 @@ export async function setupDefaultTemplate(): Promise<void> {
// Transaktion starten // Transaktion starten
await db.run('BEGIN TRANSACTION'); await db.run('BEGIN TRANSACTION');
const timeSlots = DEFAULT_TIME_SLOTS;
try { try {
// Standard-Vorlage erstellen // Standard-Vorlage erstellen - KORREKTUR: shift_plans verwenden
await db.run( await db.run(
`INSERT INTO shift_templates (id, name, description, is_default, created_by) `INSERT INTO shift_plans (id, name, description, is_template, status, created_by)
VALUES (?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?)`,
[ [
templateId, templateId,
'Standard Wochenplan', 'Standardwoche',
'Mo-Do: Vormittags- und Nachmittagsschicht, Fr: nur Vormittagsschicht', 'Mo-Do: Vormittags- und Nachmittagsschicht, Fr: nur Vormittagsschicht',
1, 1, // is_template = true
'template', // status = 'template'
adminUser.id adminUser.id
] ]
); );
console.log('Standard-Vorlage erstellt:', templateId); 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) { for (const slot of timeSlots) {
await db.run( await db.run(
`INSERT INTO template_time_slots (id, template_id, name, start_time, end_time) `INSERT INTO time_slots (id, plan_id, name, start_time, end_time, description)
VALUES (?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?)`,
[slot.id, templateId, slot.name, slot.startTime, slot.endTime] [slot.id, templateId, slot.name, slot.startTime, slot.endTime, slot.description]
); );
} }
console.log('✅ Zeit-Slots erstellt'); console.log('✅ Zeit-Slots erstellt');
// Schichten für Mo-Do // Schichten für Mo-Do - KORREKTUR: shifts verwenden
for (let day = 1; day <= 4; day++) { for (let day = 1; day <= 4; day++) {
// Vormittagsschicht // Vormittagsschicht
await db.run( await db.run(
`INSERT INTO template_shifts (id, template_id, day_of_week, time_slot_id, required_employees, color) `INSERT INTO shifts (id, plan_id, day_of_week, time_slot_id, required_employees, color)
VALUES (?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?)`,
[uuidv4(), templateId, day, timeSlots[0].id, 1, '#3498db'] [uuidv4(), templateId, day, timeSlots[0].id, 2, '#3498db']
); );
// Nachmittagsschicht // Nachmittagsschicht
await db.run( await db.run(
`INSERT INTO template_shifts (id, template_id, day_of_week, time_slot_id, required_employees, color) `INSERT INTO shifts (id, plan_id, day_of_week, time_slot_id, required_employees, color)
VALUES (?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?)`,
[uuidv4(), templateId, day, timeSlots[1].id, 1, '#e74c3c'] [uuidv4(), templateId, day, timeSlots[1].id, 2, '#e74c3c']
); );
} }
// Freitag nur Vormittagsschicht // Freitag nur Vormittagsschicht
await db.run( await db.run(
`INSERT INTO template_shifts (id, template_id, day_of_week, time_slot_id, required_employees, color) `INSERT INTO shifts (id, plan_id, day_of_week, time_slot_id, required_employees, color)
VALUES (?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?)`,
[uuidv4(), templateId, 5, timeSlots[0].id, 1, '#3498db'] [uuidv4(), templateId, 5, timeSlots[0].id, 2, '#3498db']
); );
console.log('✅ Schichten erstellt'); console.log('✅ Schichten erstellt');
// In der problematischen Stelle: // In der problematischen Stelle: KORREKTUR: shift_plans verwenden
const createdTemplate = await db.get( const createdTemplate = await db.get(
'SELECT * FROM shift_templates WHERE id = ?', 'SELECT * FROM shift_plans WHERE id = ?',
[templateId] [templateId]
) as { name: string } | undefined; ) as { name: string } | undefined;
console.log('📋 Erstellte Vorlage:', createdTemplate?.name); console.log('📋 Erstellte Vorlage:', createdTemplate?.name);
const shiftCount = await db.get( const shiftCount = await db.get(
'SELECT COUNT(*) as count FROM template_shifts WHERE template_id = ?', 'SELECT COUNT(*) as count FROM shifts WHERE plan_id = ?',
[templateId] [templateId]
) as { count: number } | undefined; ) as { count: number } | undefined;
console.log(`📊 Anzahl Schichten: ${shiftCount?.count}`); console.log(`📊 Anzahl Schichten: ${shiftCount?.count}`);

View File

@@ -1,5 +1,5 @@
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { Employee } from '../types/employee'; import { Employee } from '../../../backend/src/models/employee';
interface LoginRequest { interface LoginRequest {
email: string; email: string;

View File

@@ -1,6 +1,6 @@
// frontend/src/pages/Employees/EmployeeManagement.tsx // frontend/src/pages/Employees/EmployeeManagement.tsx
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Employee } from '../../types/employee'; import { Employee } from '../../../../backend/src/models/employee';
import { employeeService } from '../../services/employeeService'; import { employeeService } from '../../services/employeeService';
import EmployeeList from './components/EmployeeList'; import EmployeeList from './components/EmployeeList';
import EmployeeForm from './components/EmployeeForm'; import EmployeeForm from './components/EmployeeForm';

View File

@@ -1,11 +1,9 @@
// frontend/src/pages/Employees/components/AvailabilityManager.tsx - KORRIGIERT // frontend/src/pages/Employees/components/AvailabilityManager.tsx
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Employee, Availability } from '../../../../../backend/src/models/employee'; import { Employee, EmployeeAvailability } from '../../../../../backend/src/models/employee';
import { employeeService } from '../../../services/employeeService'; import { employeeService } from '../../../services/employeeService';
import { shiftPlanService } from '../../../services/shiftPlanService'; import { shiftPlanService } from '../../../services/shiftPlanService';
import { ShiftPlan, TimeSlot } from '../../../../../backend/src/models/shiftPlan'; import { ShiftPlan, TimeSlot, Shift } from '../../../../../backend/src/models/shiftPlan';
import { shiftTemplateService } from '../../../services/shiftTemplateService';
import { time } from 'console';
interface AvailabilityManagerProps { interface AvailabilityManagerProps {
employee: Employee; employee: Employee;
@@ -13,19 +11,19 @@ interface AvailabilityManagerProps {
onCancel: () => void; onCancel: () => void;
} }
// Local interface extensions
interface ExtendedTimeSlot extends TimeSlot {
displayName?: string;
source?: string;
}
interface Availability extends EmployeeAvailability {
isAvailable?: boolean;
}
// Verfügbarkeits-Level // Verfügbarkeits-Level
export type AvailabilityLevel = 1 | 2 | 3; export type AvailabilityLevel = 1 | 2 | 3;
// Interface für Zeit-Slots
interface TimeSlot {
id: string;
name: string;
startTime: string;
endTime: string;
displayName: string;
source: string;
}
const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
employee, employee,
onSave, onSave,
@@ -35,7 +33,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
const [shiftPlans, setShiftPlans] = useState<ShiftPlan[]>([]); const [shiftPlans, setShiftPlans] = useState<ShiftPlan[]>([]);
const [selectedPlanId, setSelectedPlanId] = useState<string>(''); const [selectedPlanId, setSelectedPlanId] = useState<string>('');
const [selectedPlan, setSelectedPlan] = useState<ShiftPlan | null>(null); const [selectedPlan, setSelectedPlan] = useState<ShiftPlan | null>(null);
const [timeSlots, setTimeSlots] = useState<TimeSlot[]>([]); const [timeSlots, setTimeSlots] = useState<ExtendedTimeSlot[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
@@ -47,7 +45,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
{ id: 4, name: 'Donnerstag' }, { id: 4, name: 'Donnerstag' },
{ id: 5, name: 'Freitag' }, { id: 5, name: 'Freitag' },
{ id: 6, name: 'Samstag' }, { id: 6, name: 'Samstag' },
{ id: 0, name: 'Sonntag' } { id: 7, name: 'Sonntag' }
]; ];
const availabilityLevels = [ const availabilityLevels = [
@@ -66,81 +64,51 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
} }
}, [selectedPlanId]); }, [selectedPlanId]);
// NEU: Hole Zeit-Slots aus Schichtvorlagen als Hauptquelle // Load time slots from shift plans
const loadTimeSlotsFromTemplates = async (): Promise<TimeSlot[]> => { const extractTimeSlotsFromPlans = (plans: ShiftPlan[]): ExtendedTimeSlot[] => {
try {
console.log('🔄 LADE ZEIT-SLOTS AUS SCHICHTVORLAGEN...');
const shiftPlan = await shiftPlanService.getShiftPlans();
console.log('✅ SCHICHTVORLAGEN GELADEN:', shiftPlan);
const allTimeSlots = new Map<string, TimeSlot>();
shiftPlan.forEach(plan => {
console.log(`📋 VORLAGE: ${plan.name}`, plan);
// Extrahiere Zeit-Slots aus den Schicht-Zeitbereichen
if (plan.shifts && plan.shifts.length > 0) {
plan.shifts.forEach(shift => {
const key = `${shift.timeSlot.startTime}-${shift.timeSlot.endTime}`;
if (!allTimeSlots.has(key)) {
allTimeSlots.set(key, {
id: shift.id || `slot-${shift.timeSlot.startTime.replace(/:/g, '')}-${shift.timeSlot.endTime.replace(/:/g, '')}`,
name: shift.timeSlot.name || 'Schicht',
startTime: shift.timeSlot.startTime,
endTime: shift.timeSlot.endTime,
displayName: `${shift.timeSlot.name || 'Schicht'} (${formatTime(shift.timeSlot.startTime)}-${formatTime(shift.timeSlot.endTime)})`,
source: `Vorlage: ${plan.name}`
});
}
});
}
});
const result = Array.from(allTimeSlots.values()).sort((a, b) =>
a.startTime.localeCompare(b.startTime)
);
console.log('✅ ZEIT-SLOTS AUS VORLAGEN:', result);
return result;
} catch (error) {
console.error('❌ FEHLER BEIM LADEN DER VORLAGEN:', error);
return getDefaultTimeSlots();
}
};
// NEU: Alternative Methode - Extrahiere aus Schichtplänen
const extractTimeSlotsFromPlans = (plans: ShiftPlan[]): TimeSlot[] => {
console.log('🔄 EXTRAHIERE ZEIT-SLOTS AUS SCHICHTPLÄNEN:', plans); console.log('🔄 EXTRAHIERE ZEIT-SLOTS AUS SCHICHTPLÄNEN:', plans);
const allTimeSlots = new Map<string, TimeSlot>(); const allTimeSlots = new Map<string, ExtendedTimeSlot>();
plans.forEach(plan => { plans.forEach(plan => {
console.log(`📋 ANALYSIERE PLAN: ${plan.name}`, { console.log(`📋 ANALYSIERE PLAN: ${plan.name}`, {
id: plan.id, id: plan.id,
timeSlots: plan.timeSlots,
shifts: plan.shifts shifts: plan.shifts
}); });
// Prüfe ob Schichten existieren und ein Array sind // Use timeSlots from plan if available
if (plan.shifts && Array.isArray(plan.shifts)) { if (plan.timeSlots && Array.isArray(plan.timeSlots)) {
plan.shifts.forEach(shift => { plan.timeSlots.forEach(timeSlot => {
console.log(` 🔍 SCHICHT:`, shift); console.log(` 🔍 ZEIT-SLOT:`, timeSlot);
const key = `${timeSlot.startTime}-${timeSlot.endTime}`;
if (shift.timeSlot.startTime && shift.timeSlot.endTime) {
const key = `${shift.timeSlot.startTime}-${shift.timeSlot.endTime}`;
if (!allTimeSlots.has(key)) { if (!allTimeSlots.has(key)) {
allTimeSlots.set(key, { allTimeSlots.set(key, {
id: `slot-${shift.timeSlot.startTime.replace(/:/g, '')}-${shift.timeSlot.endTime.replace(/:/g, '')}`, ...timeSlot,
name: shift.timeSlot.name || 'Schicht', displayName: `${timeSlot.name} (${formatTime(timeSlot.startTime)}-${formatTime(timeSlot.endTime)})`,
startTime: shift.timeSlot.startTime, source: `Plan: ${plan.name}`
endTime: shift.timeSlot.endTime, });
displayName: `${shift.timeSlot.name || 'Schicht'} (${formatTime(shift.timeSlot.startTime)}-${formatTime(shift.timeSlot.endTime)})`, }
});
}
// Also extract from shifts if timeSlots is empty
if ((!plan.timeSlots || plan.timeSlots.length === 0) && plan.shifts && Array.isArray(plan.shifts)) {
plan.shifts.forEach(shift => {
console.log(` 🔍 SCHICHT:`, shift);
// For shifts, we need to find the corresponding time slot
const timeSlot = plan.timeSlots?.find(ts => ts.id === shift.timeSlotId);
if (timeSlot) {
const key = `${timeSlot.startTime}-${timeSlot.endTime}`;
if (!allTimeSlots.has(key)) {
allTimeSlots.set(key, {
...timeSlot,
displayName: `${timeSlot.name} (${formatTime(timeSlot.startTime)}-${formatTime(timeSlot.endTime)})`,
source: `Plan: ${plan.name}` source: `Plan: ${plan.name}`
}); });
} }
} }
}); });
} else {
console.log(` ❌ KEINE SCHICHTEN IN PLAN ${plan.name} oder keine Array-Struktur`);
} }
}); });
@@ -152,7 +120,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
return result; return result;
}; };
const getDefaultTimeSlots = (): TimeSlot[] => { /*const getDefaultTimeSlots = (): ExtendedTimeSlot[] => {
console.log('⚠️ VERWENDE STANDARD-ZEIT-SLOTS'); console.log('⚠️ VERWENDE STANDARD-ZEIT-SLOTS');
return [ return [
{ {
@@ -180,7 +148,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
source: 'Standard' source: 'Standard'
} }
]; ];
}; };*/
const formatTime = (time: string): string => { const formatTime = (time: string): string => {
return time.substring(0, 5); return time.substring(0, 5);
@@ -191,30 +159,28 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
setLoading(true); setLoading(true);
console.log('🔄 LADE DATEN FÜR MITARBEITER:', employee.id); console.log('🔄 LADE DATEN FÜR MITARBEITER:', employee.id);
// 1. Lade Verfügbarkeiten // 1. Load availabilities
let existingAvailabilities: Availability[] = []; let existingAvailabilities: Availability[] = [];
try { try {
existingAvailabilities = await employeeService.getAvailabilities(employee.id); const availabilitiesData = await employeeService.getAvailabilities(employee.id);
existingAvailabilities = availabilitiesData.map(avail => ({
...avail,
isAvailable: avail.preferenceLevel !== 3
}));
console.log('✅ VERFÜGBARKEITEN GELADEN:', existingAvailabilities.length); console.log('✅ VERFÜGBARKEITEN GELADEN:', existingAvailabilities.length);
} catch (err) { } catch (err) {
console.log('⚠️ KEINE VERFÜGBARKEITEN GEFUNDEN'); console.log('⚠️ KEINE VERFÜGBARKEITEN GEFUNDEN');
} }
// 2. Lade Schichtpläne // 2. Load shift plans
console.log('🔄 LADE SCHICHTPLÄNE...'); console.log('🔄 LADE SCHICHTPLÄNE...');
const plans = await shiftPlanService.getShiftPlans(); const plans = await shiftPlanService.getShiftPlans();
console.log('✅ SCHICHTPLÄNE GELADEN:', plans.length, plans); console.log('✅ SCHICHTPLÄNE GELADEN:', plans.length, plans);
// 3. VERSUCH 1: Lade Zeit-Slots aus Schichtvorlagen (bessere Quelle) // 3. Extract time slots from plans
let extractedTimeSlots = await loadTimeSlotsFromTemplates(); let extractedTimeSlots = extractTimeSlotsFromPlans(plans);
// VERSUCH 2: Falls keine Zeit-Slots aus Vorlagen, versuche es mit Schichtplänen // 4. Fallback to default slots if none found
if (extractedTimeSlots.length === 0) {
console.log('⚠️ KEINE ZEIT-SLOTS AUS VORLAGEN, VERSUCHE SCHICHTPLÄNE...');
extractedTimeSlots = extractTimeSlotsFromPlans(plans);
}
// VERSUCH 3: Falls immer noch keine, verwende Standard-Slots
if (extractedTimeSlots.length === 0) { if (extractedTimeSlots.length === 0) {
console.log('⚠️ KEINE ZEIT-SLOTS GEFUNDEN, VERWENDE STANDARD-SLOTS'); console.log('⚠️ KEINE ZEIT-SLOTS GEFUNDEN, VERWENDE STANDARD-SLOTS');
extractedTimeSlots = getDefaultTimeSlots(); extractedTimeSlots = getDefaultTimeSlots();
@@ -223,17 +189,17 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
setTimeSlots(extractedTimeSlots); setTimeSlots(extractedTimeSlots);
setShiftPlans(plans); setShiftPlans(plans);
// 4. Erstelle Standard-Verfügbarkeiten falls nötig // 5. Create default availabilities if needed
if (existingAvailabilities.length === 0) { if (existingAvailabilities.length === 0) {
const defaultAvailabilities: Availability[] = daysOfWeek.flatMap(day => const defaultAvailabilities: Availability[] = daysOfWeek.flatMap(day =>
extractedTimeSlots.map(slot => ({ extractedTimeSlots.map(slot => ({
id: `temp-${day.id}-${slot.id}`, id: `temp-${day.id}-${slot.id}`,
employeeId: employee.id, employeeId: employee.id,
planId: '', // Will be set when saving
dayOfWeek: day.id, dayOfWeek: day.id,
startTime: slot.startTime, timeSlotId: slot.id,
endTime: slot.endTime, preferenceLevel: 3 as AvailabilityLevel,
isAvailable: false, isAvailable: false
availabilityLevel: 3 as AvailabilityLevel
})) }))
); );
setAvailabilities(defaultAvailabilities); setAvailabilities(defaultAvailabilities);
@@ -242,7 +208,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
setAvailabilities(existingAvailabilities); setAvailabilities(existingAvailabilities);
} }
// 5. Wähle ersten Plan aus // 6. Select first plan
if (plans.length > 0) { if (plans.length > 0) {
const publishedPlan = plans.find(plan => plan.status === 'published'); const publishedPlan = plans.find(plan => plan.status === 'published');
const firstPlan = publishedPlan || plans[0]; const firstPlan = publishedPlan || plans[0];
@@ -264,8 +230,8 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
setSelectedPlan(plan); setSelectedPlan(plan);
console.log('✅ SCHICHTPLAN GELADEN:', { console.log('✅ SCHICHTPLAN GELADEN:', {
name: plan.name, name: plan.name,
shiftsCount: plan.shifts?.length || 0, timeSlotsCount: plan.timeSlots?.length || 0,
shifts: plan.shifts shiftsCount: plan.shifts?.length || 0
}); });
} catch (err: any) { } catch (err: any) {
console.error('❌ FEHLER BEIM LADEN DES SCHICHTPLANS:', err); console.error('❌ FEHLER BEIM LADEN DES SCHICHTPLANS:', err);
@@ -277,16 +243,9 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
console.log(`🔄 ÄNDERE VERFÜGBARKEIT: Tag ${dayId}, Slot ${timeSlotId}, Level ${level}`); console.log(`🔄 ÄNDERE VERFÜGBARKEIT: Tag ${dayId}, Slot ${timeSlotId}, Level ${level}`);
setAvailabilities(prev => { setAvailabilities(prev => {
const timeSlot = timeSlots.find(s => s.id === timeSlotId);
if (!timeSlot) {
console.log('❌ ZEIT-SLOT NICHT GEFUNDEN:', timeSlotId);
return prev;
}
const existingIndex = prev.findIndex(avail => const existingIndex = prev.findIndex(avail =>
avail.dayOfWeek === dayId && avail.dayOfWeek === dayId &&
avail.startTime === timeSlot.startTime && avail.timeSlotId === timeSlotId
avail.endTime === timeSlot.endTime
); );
console.log(`🔍 EXISTIERENDE VERFÜGBARKEIT GEFUNDEN AN INDEX:`, existingIndex); console.log(`🔍 EXISTIERENDE VERFÜGBARKEIT GEFUNDEN AN INDEX:`, existingIndex);
@@ -296,7 +255,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
const updated = [...prev]; const updated = [...prev];
updated[existingIndex] = { updated[existingIndex] = {
...updated[existingIndex], ...updated[existingIndex],
availabilityLevel: level, preferenceLevel: level,
isAvailable: level !== 3 isAvailable: level !== 3
}; };
console.log('✅ VERFÜGBARKEIT AKTUALISIERT:', updated[existingIndex]); console.log('✅ VERFÜGBARKEIT AKTUALISIERT:', updated[existingIndex]);
@@ -306,11 +265,11 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
const newAvailability: Availability = { const newAvailability: Availability = {
id: `temp-${dayId}-${timeSlotId}-${Date.now()}`, id: `temp-${dayId}-${timeSlotId}-${Date.now()}`,
employeeId: employee.id, employeeId: employee.id,
planId: selectedPlanId || '', // Use selected plan if available
dayOfWeek: dayId, dayOfWeek: dayId,
startTime: timeSlot.startTime, timeSlotId: timeSlotId,
endTime: timeSlot.endTime, preferenceLevel: level,
isAvailable: level !== 3, isAvailable: level !== 3
availabilityLevel: level
}; };
console.log('🆕 NEUE VERFÜGBARKEIT ERSTELLT:', newAvailability); console.log('🆕 NEUE VERFÜGBARKEIT ERSTELLT:', newAvailability);
return [...prev, newAvailability]; return [...prev, newAvailability];
@@ -319,19 +278,12 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
}; };
const getAvailabilityForDayAndSlot = (dayId: number, timeSlotId: string): AvailabilityLevel => { const getAvailabilityForDayAndSlot = (dayId: number, timeSlotId: string): AvailabilityLevel => {
const timeSlot = timeSlots.find(s => s.id === timeSlotId);
if (!timeSlot) {
console.log('❌ ZEIT-SLOT NICHT GEFUNDEN FÜR ABFRAGE:', timeSlotId);
return 3;
}
const availability = availabilities.find(avail => const availability = availabilities.find(avail =>
avail.dayOfWeek === dayId && avail.dayOfWeek === dayId &&
avail.startTime === timeSlot.startTime && avail.timeSlotId === timeSlotId
avail.endTime === timeSlot.endTime
); );
const result = availability?.availabilityLevel || 3; const result = availability?.preferenceLevel || 3;
console.log(`🔍 ABFRAGE VERFÜGBARKEIT: Tag ${dayId}, Slot ${timeSlotId} = Level ${result}`); console.log(`🔍 ABFRAGE VERFÜGBARKEIT: Tag ${dayId}, Slot ${timeSlotId} = Level ${result}`);
return result; return result;
@@ -342,7 +294,18 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
setSaving(true); setSaving(true);
setError(''); setError('');
await employeeService.updateAvailabilities(employee.id, availabilities); // Convert to EmployeeAvailability format for API
const availabilitiesToSave: EmployeeAvailability[] = availabilities.map(avail => ({
id: avail.id,
employeeId: avail.employeeId,
planId: avail.planId || selectedPlanId, // Use selected plan if planId is empty
dayOfWeek: avail.dayOfWeek,
timeSlotId: avail.timeSlotId,
preferenceLevel: avail.preferenceLevel,
notes: avail.notes
}));
await employeeService.updateAvailabilities(employee.id, availabilitiesToSave);
console.log('✅ VERFÜGBARKEITEN ERFOLGREICH GESPEICHERT'); console.log('✅ VERFÜGBARKEITEN ERFOLGREICH GESPEICHERT');
onSave(); onSave();
@@ -354,10 +317,6 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
} }
}; };
const getTimeSlotsForTimetable = (): TimeSlot[] => {
return timeSlots;
};
if (loading) { if (loading) {
return ( return (
<div style={{ textAlign: 'center', padding: '40px' }}> <div style={{ textAlign: 'center', padding: '40px' }}>
@@ -366,8 +325,6 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
); );
} }
const timetableTimeSlots = getTimeSlotsForTimetable();
return ( return (
<div style={{ <div style={{
maxWidth: '1400px', maxWidth: '1400px',
@@ -419,7 +376,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
)} )}
</div> </div>
{/* Rest der Komponente... */} {/* Employee Info */}
<div style={{ marginBottom: '20px' }}> <div style={{ marginBottom: '20px' }}>
<h3 style={{ margin: '0 0 10px 0', color: '#34495e' }}> <h3 style={{ margin: '0 0 10px 0', color: '#34495e' }}>
{employee.name} {employee.name}
@@ -442,7 +399,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
</div> </div>
)} )}
{/* Verfügbarkeits-Legende */} {/* Availability Legend */}
<div style={{ <div style={{
marginBottom: '30px', marginBottom: '30px',
padding: '20px', padding: '20px',
@@ -479,7 +436,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
</div> </div>
</div> </div>
{/* Schichtplan Auswahl */} {/* Shift Plan Selection */}
<div style={{ <div style={{
marginBottom: '30px', marginBottom: '30px',
padding: '20px', padding: '20px',
@@ -509,7 +466,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
<option value="">Bitte auswählen...</option> <option value="">Bitte auswählen...</option>
{shiftPlans.map(plan => ( {shiftPlans.map(plan => (
<option key={plan.id} value={plan.id}> <option key={plan.id} value={plan.id}>
{plan.name} ({plan.shifts?.length || 0} Schichten) {plan.name} ({plan.timeSlots?.length || 0} Zeit-Slots)
</option> </option>
))} ))}
</select> </select>
@@ -517,8 +474,8 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
</div> </div>
</div> </div>
{/* Verfügbarkeits-Timetable */} {/* Availability Timetable */}
{timetableTimeSlots.length > 0 ? ( {timeSlots.length > 0 ? (
<div style={{ <div style={{
marginBottom: '30px', marginBottom: '30px',
border: '1px solid #e0e0e0', border: '1px solid #e0e0e0',
@@ -533,7 +490,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
}}> }}>
Verfügbarkeit definieren Verfügbarkeit definieren
<div style={{ fontSize: '14px', fontWeight: 'normal', marginTop: '5px' }}> <div style={{ fontSize: '14px', fontWeight: 'normal', marginTop: '5px' }}>
{timetableTimeSlots.length} Schichttypen verfügbar {timeSlots.length} Schichttypen verfügbar
</div> </div>
</div> </div>
@@ -568,8 +525,8 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{timetableTimeSlots.map((timeSlot, timeIndex) => ( {timeSlots.map((timeSlot, timeIndex) => (
<tr key={`timeSlot-${timeSlot.id}-timeIndex-${timeIndex}`} style={{ <tr key={timeSlot.id} style={{
backgroundColor: timeIndex % 2 === 0 ? 'white' : '#f8f9fa' backgroundColor: timeIndex % 2 === 0 ? 'white' : '#f8f9fa'
}}> }}>
<td style={{ <td style={{
@@ -643,9 +600,9 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
}}> }}>
<div style={{ fontSize: '48px', marginBottom: '20px' }}></div> <div style={{ fontSize: '48px', marginBottom: '20px' }}></div>
<h4>Keine Schichttypen konfiguriert</h4> <h4>Keine Schichttypen konfiguriert</h4>
<p>Es wurden keine Zeit-Slots in den Schichtvorlagen oder -plänen gefunden.</p> <p>Es wurden keine Zeit-Slots in den Schichtplänen gefunden.</p>
<p style={{ fontSize: '14px', marginTop: '10px' }}> <p style={{ fontSize: '14px', marginTop: '10px' }}>
Bitte erstellen Sie zuerst Schichtvorlagen mit Zeit-Slots. Bitte erstellen Sie zuerst Schichtpläne mit Zeit-Slots.
</p> </p>
</div> </div>
)} )}

View File

@@ -1,5 +1,5 @@
// frontend/src/services/employeeService.ts // frontend/src/services/employeeService.ts
import { Employee, CreateEmployeeRequest, UpdateEmployeeRequest, Availability } from '../types/employee'; import { Employee, CreateEmployeeRequest, UpdateEmployeeRequest, EmployeeAvailability } from '../../../backend/src/models/employee';
const API_BASE_URL = 'http://localhost:3002/api'; const API_BASE_URL = 'http://localhost:3002/api';
@@ -90,7 +90,7 @@ export class EmployeeService {
} }
} }
async getAvailabilities(employeeId: string): Promise<Availability[]> { async getAvailabilities(employeeId: string): Promise<EmployeeAvailability[]> {
const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, { const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, {
headers: getAuthHeaders(), headers: getAuthHeaders(),
}); });
@@ -102,7 +102,7 @@ export class EmployeeService {
return response.json(); return response.json();
} }
async updateAvailabilities(employeeId: string, availabilities: Availability[]): Promise<Availability[]> { async updateAvailabilities(employeeId: string, availabilities: EmployeeAvailability[]): Promise<EmployeeAvailability[]> {
const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, { const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, {
method: 'PUT', method: 'PUT',
headers: getAuthHeaders(), headers: getAuthHeaders(),

View File

@@ -1,6 +1,6 @@
// frontend/src/services/shiftPlanService.ts // frontend/src/services/shiftPlanService.ts
import { authService } from './authService'; import { authService } from './authService';
import { ShiftPlan, CreateShiftPlanRequest, ShiftSlot } from '../types/shiftPlan.js'; import { ShiftPlan, CreateShiftPlanRequest, Shift } from '../../../backend/src/models/shiftPlan.js';
const API_BASE = 'http://localhost:3002/api/shift-plans'; const API_BASE = 'http://localhost:3002/api/shift-plans';
@@ -21,20 +21,7 @@ export const shiftPlanService = {
throw new Error('Fehler beim Laden der Schichtpläne'); throw new Error('Fehler beim Laden der Schichtpläne');
} }
const data = await response.json(); return await response.json();
// Convert snake_case to camelCase
return data.map((plan: any) => ({
id: plan.id,
name: plan.name,
startDate: plan.start_date, // Convert here
endDate: plan.end_date, // Convert here
templateId: plan.template_id,
status: plan.status,
createdBy: plan.created_by,
createdAt: plan.created_at,
shifts: plan.shifts || []
}));
}, },
async getShiftPlan(id: string): Promise<ShiftPlan> { async getShiftPlan(id: string): Promise<ShiftPlan> {
@@ -53,20 +40,7 @@ export const shiftPlanService = {
throw new Error('Schichtplan nicht gefunden'); throw new Error('Schichtplan nicht gefunden');
} }
const data = await response.json(); return await response.json();
// Convert snake_case to camelCase
return data.map((plan: any) => ({
id: plan.id,
name: plan.name,
startDate: plan.start_date,
endDate: plan.end_date,
templateId: plan.template_id,
status: plan.status,
createdBy: plan.created_by,
createdAt: plan.created_at,
shifts: plan.shifts || []
}));
}, },
async createShiftPlan(plan: CreateShiftPlanRequest): Promise<ShiftPlan> { async createShiftPlan(plan: CreateShiftPlanRequest): Promise<ShiftPlan> {
@@ -127,60 +101,5 @@ export const shiftPlanService = {
} }
throw new Error('Fehler beim Löschen des Schichtplans'); throw new Error('Fehler beim Löschen des Schichtplans');
} }
},
async updateShiftPlanShift(planId: string, shift: ShiftSlot): Promise<void> {
const response = await fetch(`${API_BASE}/${planId}/shifts/${shift.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
...authService.getAuthHeaders()
},
body: JSON.stringify(shift)
});
if (!response.ok) {
if (response.status === 401) {
authService.logout();
throw new Error('Nicht authorisiert - bitte erneut anmelden');
}
throw new Error('Fehler beim Aktualisieren der Schicht');
}
},
async addShiftPlanShift(planId: string, shift: Omit<ShiftSlot, 'id' | 'shiftPlanId' | 'assignedEmployees'>): Promise<void> {
const response = await fetch(`${API_BASE}/${planId}/shifts`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authService.getAuthHeaders()
},
body: JSON.stringify(shift)
});
if (!response.ok) {
if (response.status === 401) {
authService.logout();
throw new Error('Nicht authorisiert - bitte erneut anmelden');
}
throw new Error('Fehler beim Hinzufügen der Schicht');
}
},
async deleteShiftPlanShift(planId: string, shiftId: string): Promise<void> {
const response = await fetch(`${API_BASE}/${planId}/shifts/${shiftId}`, {
method: 'DELETE',
headers: {
...authService.getAuthHeaders()
}
});
if (!response.ok) {
if (response.status === 401) {
authService.logout();
throw new Error('Nicht authorisiert - bitte erneut anmelden');
}
throw new Error('Fehler beim Löschen der Schicht');
}
} }
}; };

View File

@@ -1,5 +1,5 @@
// frontend/src/services/shiftTemplateService.ts // frontend/src/services/shiftTemplateService.ts
import { ShiftPlan } from '../../../backend/src/models/shiftTemplate.js'; import { ShiftPlan } from '../../../backend/src/models/shiftPlan.js';
import { authService } from './authService'; import { authService } from './authService';
const API_BASE = 'http://localhost:3002/api/shift-templates'; const API_BASE = 'http://localhost:3002/api/shift-templates';
@@ -22,15 +22,10 @@ export const shiftTemplateService = {
} }
const templates = await response.json(); const templates = await response.json();
// Sortiere die Vorlagen so, dass die Standard-Vorlage immer zuerst kommt return templates;
return templates.sort((a: TemplateShift, b: TemplateShift) => {
if (a.isDefault && !b.isDefault) return -1;
if (!a.isDefault && b.isDefault) return 1;
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
});
}, },
async getTemplate(id: string): Promise<TemplateShift> { async getTemplate(id: string): Promise<ShiftPlan> {
const response = await fetch(`${API_BASE}/${id}`, { const response = await fetch(`${API_BASE}/${id}`, {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -49,18 +44,7 @@ export const shiftTemplateService = {
return response.json(); return response.json();
}, },
async createTemplate(template: Omit<TemplateShift, 'id' | 'createdAt' | 'createdBy'>): Promise<TemplateShift> { async createTemplate(template: Omit<ShiftPlan, 'id' | 'createdAt' | 'createdBy'>): Promise<ShiftPlan> {
// Wenn diese Vorlage als Standard markiert ist,
// fragen wir den Benutzer, ob er wirklich die Standard-Vorlage ändern möchte
if (template.isDefault) {
const confirm = window.confirm(
'Diese Vorlage wird als neue Standard-Vorlage festgelegt. Die bisherige Standard-Vorlage wird dadurch zu einer normalen Vorlage. Möchten Sie fortfahren?'
);
if (!confirm) {
throw new Error('Operation abgebrochen');
}
}
const response = await fetch(API_BASE, { const response = await fetch(API_BASE, {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -81,7 +65,7 @@ export const shiftTemplateService = {
return response.json(); return response.json();
}, },
async updateTemplate(id: string, template: Partial<TemplateShift>): Promise<TemplateShift> { async updateTemplate(id: string, template: Partial<ShiftPlan>): Promise<ShiftPlan> {
const response = await fetch(`${API_BASE}/${id}`, { const response = await fetch(`${API_BASE}/${id}`, {
method: 'PUT', method: 'PUT',
headers: { headers: {