From 85dca0ba4119d6255b4f958b8bcbabc81187f055 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 11 Oct 2025 19:56:43 +0200 Subject: [PATCH] backend without errors --- backend/src/controllers/employeeController.ts | 123 +++++----- backend/src/controllers/setupController.ts | 6 +- backend/src/models/ShiftPlan.ts | 1 - backend/src/scripts/checkTemplates.ts | 15 +- backend/src/scripts/setupDefaultTemplate.ts | 57 +++-- frontend/src/contexts/AuthContext.tsx | 2 +- .../pages/Employees/EmployeeManagement.tsx | 2 +- .../components/AvailabilityManager.tsx | 229 +++++++----------- frontend/src/services/employeeService.ts | 6 +- frontend/src/services/shiftPlanService.ts | 89 +------ frontend/src/services/shiftTemplateService.ts | 26 +- 11 files changed, 212 insertions(+), 344 deletions(-) diff --git a/backend/src/controllers/employeeController.ts b/backend/src/controllers/employeeController.ts index 07e5751..cb2d9bd 100644 --- a/backend/src/controllers/employeeController.ts +++ b/backend/src/controllers/employeeController.ts @@ -14,17 +14,16 @@ export const getEmployees = async (req: AuthRequest, res: Response): Promise('SELECT id FROM users WHERE email = ? AND is_active = 1', [email]); + const existingActiveUser = await db.get('SELECT id FROM employees WHERE email = ? AND is_active = 1', [email]); if (existingActiveUser) { console.log('❌ Email exists for active user:', existingActiveUser); @@ -104,10 +105,10 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise => { try { 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 - const existingEmployee = await db.get('SELECT * FROM users WHERE id = ?', [id]); + const existingEmployee = await db.get('SELECT * FROM employees WHERE id = ?', [id]); if (!existingEmployee) { res.status(404).json({ error: 'Employee not found' }); return; @@ -155,14 +158,15 @@ export const updateEmployee = async (req: AuthRequest, res: Response): Promise(` SELECT id, email, name, is_active, role - FROM users + FROM employees WHERE id = ? `, [id]); @@ -211,7 +216,7 @@ export const deleteEmployee = async (req: AuthRequest, res: Response): Promise( - '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}%`] ); @@ -229,14 +234,13 @@ export const deleteEmployee = async (req: AuthRequest, res: Response): Promise empId !== id); await db.run( - 'UPDATE assigned_shifts SET assigned_employees = ? WHERE id = ?', + 'UPDATE scheduled_shifts SET assigned_employees = ? WHERE id = ?', [JSON.stringify(filteredEmployees), shift.id] ); } catch (parseError) { 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( - 'UPDATE assigned_shifts SET assigned_employees = ? WHERE id = ?', + 'UPDATE scheduled_shifts SET assigned_employees = ? WHERE id = ?', [JSON.stringify([]), shift.id] ); } @@ -244,10 +248,9 @@ export const deleteEmployee = async (req: AuthRequest, res: Response): Promise(` - SELECT * FROM employee_availabilities + SELECT * FROM employee_availability WHERE employee_id = ? - ORDER BY day_of_week, start_time + ORDER BY day_of_week, time_slot_id `, [employeeId]); res.json(availabilities.map(avail => ({ id: avail.id, employeeId: avail.employee_id, + planId: avail.plan_id, dayOfWeek: avail.day_of_week, - startTime: avail.start_time, - endTime: avail.end_time, - isAvailable: avail.is_available === 1 + timeSlotId: avail.time_slot_id, + preferenceLevel: avail.preference_level, + notes: avail.notes }))); } catch (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 => { try { const { employeeId } = req.params; - const availabilities = req.body as Array<{ - id?: string; - employeeId: string; - dayOfWeek: number; - startTime: string; - endTime: string; - isAvailable: boolean; - }>; + const { planId, availabilities } = req.body; // 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) { res.status(404).json({ error: 'Employee not found' }); return; @@ -319,22 +316,23 @@ export const updateAvailabilities = async (req: AuthRequest, res: Response): Pro await db.run('BEGIN TRANSACTION'); try { - // Delete existing availabilities - await db.run('DELETE FROM employee_availabilities WHERE employee_id = ?', [employeeId]); + // Delete existing availabilities for this plan + await db.run('DELETE FROM employee_availability WHERE employee_id = ? AND plan_id = ?', [employeeId, planId]); // Insert new availabilities for (const availability of availabilities) { const availabilityId = uuidv4(); await db.run( - `INSERT INTO employee_availabilities (id, employee_id, day_of_week, start_time, end_time, is_available) - VALUES (?, ?, ?, ?, ?, ?)`, + `INSERT INTO employee_availability (id, employee_id, plan_id, day_of_week, time_slot_id, preference_level, notes) + VALUES (?, ?, ?, ?, ?, ?, ?)`, [ availabilityId, employeeId, + planId, availability.dayOfWeek, - availability.startTime, - availability.endTime, - availability.isAvailable ? 1 : 0 + availability.timeSlotId, + availability.preferenceLevel, + availability.notes || null ] ); } @@ -343,18 +341,19 @@ export const updateAvailabilities = async (req: AuthRequest, res: Response): Pro // Return updated availabilities const updatedAvailabilities = await db.all(` - SELECT * FROM employee_availabilities - WHERE employee_id = ? - ORDER BY day_of_week, start_time - `, [employeeId]); + SELECT * FROM employee_availability + WHERE employee_id = ? AND plan_id = ? + ORDER BY day_of_week, time_slot_id + `, [employeeId, planId]); res.json(updatedAvailabilities.map(avail => ({ id: avail.id, employeeId: avail.employee_id, + planId: avail.plan_id, dayOfWeek: avail.day_of_week, - startTime: avail.start_time, - endTime: avail.end_time, - isAvailable: avail.is_available === 1 + timeSlotId: avail.time_slot_id, + preferenceLevel: avail.preference_level, + notes: avail.notes }))); } catch (error) { diff --git a/backend/src/controllers/setupController.ts b/backend/src/controllers/setupController.ts index 9557451..f07cec6 100644 --- a/backend/src/controllers/setupController.ts +++ b/backend/src/controllers/setupController.ts @@ -32,7 +32,7 @@ export const setupAdmin = async (req: Request, res: Response): Promise => try { // Check if admin already exists 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'] ); @@ -70,8 +70,8 @@ export const setupAdmin = async (req: Request, res: Response): Promise => try { // Create admin user await db.run( - `INSERT INTO users (id, email, password, name, role, is_active) - VALUES (?, ?, ?, ?, ?, ?)`, + `INSERT INTO employees (id, email, password, name, role, is_active) + VALUES (?, ?, ?, ?, ?, ?)`, [adminId, email, hashedPassword, name, 'admin', 1] ); diff --git a/backend/src/models/ShiftPlan.ts b/backend/src/models/ShiftPlan.ts index 51d5d5d..50d089d 100644 --- a/backend/src/models/ShiftPlan.ts +++ b/backend/src/models/ShiftPlan.ts @@ -24,7 +24,6 @@ export interface TimeSlot { } export interface Shift { - timeSlot: any; id: string; planId: string; timeSlotId: string; diff --git a/backend/src/scripts/checkTemplates.ts b/backend/src/scripts/checkTemplates.ts index 02d0173..df2df11 100644 --- a/backend/src/scripts/checkTemplates.ts +++ b/backend/src/scripts/checkTemplates.ts @@ -1,19 +1,24 @@ +// backend/src/scripts/checkTemplates.ts import { db } from '../services/databaseService.js'; -import { ShiftPlan } from '../models/ShiftPlan.js'; async function checkTemplates() { try { - const templates = await db.all( - `SELECT sp.*, u.name as created_by_name + // KORREKTUR: employees statt users verwenden + const templates = await db.all( + `SELECT sp.*, e.name as created_by_name 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); for (const template of templates) { const shifts = await db.all( - `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] ); console.log(`Shifts for template ${template.id}:`, shifts); diff --git a/backend/src/scripts/setupDefaultTemplate.ts b/backend/src/scripts/setupDefaultTemplate.ts index 8e9697d..989a908 100644 --- a/backend/src/scripts/setupDefaultTemplate.ts +++ b/backend/src/scripts/setupDefaultTemplate.ts @@ -1,7 +1,7 @@ // backend/src/scripts/setupDefaultTemplate.ts import { v4 as uuidv4 } from 'uuid'; 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 { id: string; @@ -13,9 +13,10 @@ interface AdminUser { */ export async function setupDefaultTemplate(): Promise { 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( - 'SELECT * FROM shift_templates WHERE is_default = 1' + 'SELECT * FROM shift_plans WHERE is_template = 1 AND name = ?', + ['Standardwoche'] ); if (existingDefault) { @@ -23,9 +24,9 @@ export async function setupDefaultTemplate(): Promise { 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( - 'SELECT id FROM users WHERE role = ?', + 'SELECT id FROM employees WHERE role = ?', ['admin'] ); @@ -40,70 +41,74 @@ export async function setupDefaultTemplate(): Promise { // Transaktion starten await db.run('BEGIN TRANSACTION'); - const timeSlots = DEFAULT_TIME_SLOTS; - - try { - // Standard-Vorlage erstellen + // Standard-Vorlage erstellen - KORREKTUR: shift_plans verwenden await db.run( - `INSERT INTO shift_templates (id, name, description, is_default, created_by) - VALUES (?, ?, ?, ?, ?)`, + `INSERT INTO shift_plans (id, name, description, is_template, status, created_by) + VALUES (?, ?, ?, ?, ?, ?)`, [ templateId, - 'Standard Wochenplan', + 'Standardwoche', 'Mo-Do: Vormittags- und Nachmittagsschicht, Fr: nur Vormittagsschicht', - 1, + 1, // is_template = true + 'template', // status = 'template' adminUser.id ] ); console.log('Standard-Vorlage erstellt:', templateId); + // Zeit-Slots erstellen - KORREKTUR: time_slots verwenden + const timeSlots = DEFAULT_ZEBRA_TIME_SLOTS.map(slot => ({ + ...slot, + id: uuidv4() + })); + for (const slot of timeSlots) { await db.run( - `INSERT INTO template_time_slots (id, template_id, name, start_time, end_time) - VALUES (?, ?, ?, ?, ?)`, - [slot.id, templateId, slot.name, slot.startTime, slot.endTime] + `INSERT INTO time_slots (id, plan_id, name, start_time, end_time, description) + VALUES (?, ?, ?, ?, ?, ?)`, + [slot.id, templateId, slot.name, slot.startTime, slot.endTime, slot.description] ); } console.log('✅ Zeit-Slots erstellt'); - // Schichten für Mo-Do + // Schichten für Mo-Do - KORREKTUR: shifts verwenden for (let day = 1; day <= 4; day++) { // Vormittagsschicht 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 (?, ?, ?, ?, ?, ?)`, - [uuidv4(), templateId, day, timeSlots[0].id, 1, '#3498db'] + [uuidv4(), templateId, day, timeSlots[0].id, 2, '#3498db'] ); // Nachmittagsschicht 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 (?, ?, ?, ?, ?, ?)`, - [uuidv4(), templateId, day, timeSlots[1].id, 1, '#e74c3c'] + [uuidv4(), templateId, day, timeSlots[1].id, 2, '#e74c3c'] ); } // Freitag nur Vormittagsschicht 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 (?, ?, ?, ?, ?, ?)`, - [uuidv4(), templateId, 5, timeSlots[0].id, 1, '#3498db'] + [uuidv4(), templateId, 5, timeSlots[0].id, 2, '#3498db'] ); console.log('✅ Schichten erstellt'); - // In der problematischen Stelle: + // In der problematischen Stelle: KORREKTUR: shift_plans verwenden const createdTemplate = await db.get( - 'SELECT * FROM shift_templates WHERE id = ?', + 'SELECT * FROM shift_plans WHERE id = ?', [templateId] ) as { name: string } | undefined; console.log('📋 Erstellte Vorlage:', createdTemplate?.name); const shiftCount = await db.get( - 'SELECT COUNT(*) as count FROM template_shifts WHERE template_id = ?', + 'SELECT COUNT(*) as count FROM shifts WHERE plan_id = ?', [templateId] ) as { count: number } | undefined; console.log(`📊 Anzahl Schichten: ${shiftCount?.count}`); diff --git a/frontend/src/contexts/AuthContext.tsx b/frontend/src/contexts/AuthContext.tsx index b63c514..9b87313 100644 --- a/frontend/src/contexts/AuthContext.tsx +++ b/frontend/src/contexts/AuthContext.tsx @@ -1,5 +1,5 @@ import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; -import { Employee } from '../types/employee'; +import { Employee } from '../../../backend/src/models/employee'; interface LoginRequest { email: string; diff --git a/frontend/src/pages/Employees/EmployeeManagement.tsx b/frontend/src/pages/Employees/EmployeeManagement.tsx index 63a5bd8..48f75fb 100644 --- a/frontend/src/pages/Employees/EmployeeManagement.tsx +++ b/frontend/src/pages/Employees/EmployeeManagement.tsx @@ -1,6 +1,6 @@ // frontend/src/pages/Employees/EmployeeManagement.tsx import React, { useState, useEffect } from 'react'; -import { Employee } from '../../types/employee'; +import { Employee } from '../../../../backend/src/models/employee'; import { employeeService } from '../../services/employeeService'; import EmployeeList from './components/EmployeeList'; import EmployeeForm from './components/EmployeeForm'; diff --git a/frontend/src/pages/Employees/components/AvailabilityManager.tsx b/frontend/src/pages/Employees/components/AvailabilityManager.tsx index 9dcc697..8317d87 100644 --- a/frontend/src/pages/Employees/components/AvailabilityManager.tsx +++ b/frontend/src/pages/Employees/components/AvailabilityManager.tsx @@ -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 { Employee, Availability } from '../../../../../backend/src/models/employee'; +import { Employee, EmployeeAvailability } from '../../../../../backend/src/models/employee'; import { employeeService } from '../../../services/employeeService'; import { shiftPlanService } from '../../../services/shiftPlanService'; -import { ShiftPlan, TimeSlot } from '../../../../../backend/src/models/shiftPlan'; -import { shiftTemplateService } from '../../../services/shiftTemplateService'; -import { time } from 'console'; +import { ShiftPlan, TimeSlot, Shift } from '../../../../../backend/src/models/shiftPlan'; interface AvailabilityManagerProps { employee: Employee; @@ -13,19 +11,19 @@ interface AvailabilityManagerProps { onCancel: () => void; } +// Local interface extensions +interface ExtendedTimeSlot extends TimeSlot { + displayName?: string; + source?: string; +} + +interface Availability extends EmployeeAvailability { + isAvailable?: boolean; +} + // Verfügbarkeits-Level 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 = ({ employee, onSave, @@ -35,7 +33,7 @@ const AvailabilityManager: React.FC = ({ const [shiftPlans, setShiftPlans] = useState([]); const [selectedPlanId, setSelectedPlanId] = useState(''); const [selectedPlan, setSelectedPlan] = useState(null); - const [timeSlots, setTimeSlots] = useState([]); + const [timeSlots, setTimeSlots] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); @@ -47,7 +45,7 @@ const AvailabilityManager: React.FC = ({ { id: 4, name: 'Donnerstag' }, { id: 5, name: 'Freitag' }, { id: 6, name: 'Samstag' }, - { id: 0, name: 'Sonntag' } + { id: 7, name: 'Sonntag' } ]; const availabilityLevels = [ @@ -66,81 +64,51 @@ const AvailabilityManager: React.FC = ({ } }, [selectedPlanId]); - // NEU: Hole Zeit-Slots aus Schichtvorlagen als Hauptquelle - const loadTimeSlotsFromTemplates = async (): Promise => { - try { - console.log('🔄 LADE ZEIT-SLOTS AUS SCHICHTVORLAGEN...'); - const shiftPlan = await shiftPlanService.getShiftPlans(); - console.log('✅ SCHICHTVORLAGEN GELADEN:', shiftPlan); - - const allTimeSlots = new Map(); - - 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[] => { + // Load time slots from shift plans + const extractTimeSlotsFromPlans = (plans: ShiftPlan[]): ExtendedTimeSlot[] => { console.log('🔄 EXTRAHIERE ZEIT-SLOTS AUS SCHICHTPLÄNEN:', plans); - const allTimeSlots = new Map(); + const allTimeSlots = new Map(); plans.forEach(plan => { console.log(`📋 ANALYSIERE PLAN: ${plan.name}`, { id: plan.id, + timeSlots: plan.timeSlots, shifts: plan.shifts }); - // Prüfe ob Schichten existieren und ein Array sind - if (plan.shifts && Array.isArray(plan.shifts)) { + // Use timeSlots from plan if available + if (plan.timeSlots && Array.isArray(plan.timeSlots)) { + plan.timeSlots.forEach(timeSlot => { + console.log(` 🔍 ZEIT-SLOT:`, 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}` + }); + } + }); + } + + // 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); - - if (shift.timeSlot.startTime && shift.timeSlot.endTime) { - const key = `${shift.timeSlot.startTime}-${shift.timeSlot.endTime}`; + // 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, { - 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)})`, + ...timeSlot, + displayName: `${timeSlot.name} (${formatTime(timeSlot.startTime)}-${formatTime(timeSlot.endTime)})`, 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 = ({ return result; }; - const getDefaultTimeSlots = (): TimeSlot[] => { + /*const getDefaultTimeSlots = (): ExtendedTimeSlot[] => { console.log('⚠️ VERWENDE STANDARD-ZEIT-SLOTS'); return [ { @@ -180,7 +148,7 @@ const AvailabilityManager: React.FC = ({ source: 'Standard' } ]; - }; + };*/ const formatTime = (time: string): string => { return time.substring(0, 5); @@ -191,30 +159,28 @@ const AvailabilityManager: React.FC = ({ setLoading(true); console.log('🔄 LADE DATEN FÜR MITARBEITER:', employee.id); - // 1. Lade Verfügbarkeiten + // 1. Load availabilities let existingAvailabilities: Availability[] = []; 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); } catch (err) { console.log('⚠️ KEINE VERFÜGBARKEITEN GEFUNDEN'); } - // 2. Lade Schichtpläne + // 2. Load shift plans console.log('🔄 LADE SCHICHTPLÄNE...'); const plans = await shiftPlanService.getShiftPlans(); console.log('✅ SCHICHTPLÄNE GELADEN:', plans.length, plans); - // 3. VERSUCH 1: Lade Zeit-Slots aus Schichtvorlagen (bessere Quelle) - let extractedTimeSlots = await loadTimeSlotsFromTemplates(); - - // VERSUCH 2: Falls keine Zeit-Slots aus Vorlagen, versuche es mit Schichtplänen - if (extractedTimeSlots.length === 0) { - console.log('⚠️ KEINE ZEIT-SLOTS AUS VORLAGEN, VERSUCHE SCHICHTPLÄNE...'); - extractedTimeSlots = extractTimeSlotsFromPlans(plans); - } + // 3. Extract time slots from plans + let extractedTimeSlots = extractTimeSlotsFromPlans(plans); - // VERSUCH 3: Falls immer noch keine, verwende Standard-Slots + // 4. Fallback to default slots if none found if (extractedTimeSlots.length === 0) { console.log('⚠️ KEINE ZEIT-SLOTS GEFUNDEN, VERWENDE STANDARD-SLOTS'); extractedTimeSlots = getDefaultTimeSlots(); @@ -223,17 +189,17 @@ const AvailabilityManager: React.FC = ({ setTimeSlots(extractedTimeSlots); setShiftPlans(plans); - // 4. Erstelle Standard-Verfügbarkeiten falls nötig + // 5. Create default availabilities if needed if (existingAvailabilities.length === 0) { const defaultAvailabilities: Availability[] = daysOfWeek.flatMap(day => extractedTimeSlots.map(slot => ({ id: `temp-${day.id}-${slot.id}`, employeeId: employee.id, + planId: '', // Will be set when saving dayOfWeek: day.id, - startTime: slot.startTime, - endTime: slot.endTime, - isAvailable: false, - availabilityLevel: 3 as AvailabilityLevel + timeSlotId: slot.id, + preferenceLevel: 3 as AvailabilityLevel, + isAvailable: false })) ); setAvailabilities(defaultAvailabilities); @@ -242,7 +208,7 @@ const AvailabilityManager: React.FC = ({ setAvailabilities(existingAvailabilities); } - // 5. Wähle ersten Plan aus + // 6. Select first plan if (plans.length > 0) { const publishedPlan = plans.find(plan => plan.status === 'published'); const firstPlan = publishedPlan || plans[0]; @@ -264,8 +230,8 @@ const AvailabilityManager: React.FC = ({ setSelectedPlan(plan); console.log('✅ SCHICHTPLAN GELADEN:', { name: plan.name, - shiftsCount: plan.shifts?.length || 0, - shifts: plan.shifts + timeSlotsCount: plan.timeSlots?.length || 0, + shiftsCount: plan.shifts?.length || 0 }); } catch (err: any) { console.error('❌ FEHLER BEIM LADEN DES SCHICHTPLANS:', err); @@ -277,16 +243,9 @@ const AvailabilityManager: React.FC = ({ console.log(`🔄 ÄNDERE VERFÜGBARKEIT: Tag ${dayId}, Slot ${timeSlotId}, Level ${level}`); 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 => avail.dayOfWeek === dayId && - avail.startTime === timeSlot.startTime && - avail.endTime === timeSlot.endTime + avail.timeSlotId === timeSlotId ); console.log(`🔍 EXISTIERENDE VERFÜGBARKEIT GEFUNDEN AN INDEX:`, existingIndex); @@ -296,7 +255,7 @@ const AvailabilityManager: React.FC = ({ const updated = [...prev]; updated[existingIndex] = { ...updated[existingIndex], - availabilityLevel: level, + preferenceLevel: level, isAvailable: level !== 3 }; console.log('✅ VERFÜGBARKEIT AKTUALISIERT:', updated[existingIndex]); @@ -306,11 +265,11 @@ const AvailabilityManager: React.FC = ({ const newAvailability: Availability = { id: `temp-${dayId}-${timeSlotId}-${Date.now()}`, employeeId: employee.id, + planId: selectedPlanId || '', // Use selected plan if available dayOfWeek: dayId, - startTime: timeSlot.startTime, - endTime: timeSlot.endTime, - isAvailable: level !== 3, - availabilityLevel: level + timeSlotId: timeSlotId, + preferenceLevel: level, + isAvailable: level !== 3 }; console.log('🆕 NEUE VERFÜGBARKEIT ERSTELLT:', newAvailability); return [...prev, newAvailability]; @@ -319,19 +278,12 @@ const AvailabilityManager: React.FC = ({ }; 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 => avail.dayOfWeek === dayId && - avail.startTime === timeSlot.startTime && - avail.endTime === timeSlot.endTime + avail.timeSlotId === timeSlotId ); - const result = availability?.availabilityLevel || 3; + const result = availability?.preferenceLevel || 3; console.log(`🔍 ABFRAGE VERFÜGBARKEIT: Tag ${dayId}, Slot ${timeSlotId} = Level ${result}`); return result; @@ -342,7 +294,18 @@ const AvailabilityManager: React.FC = ({ setSaving(true); 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'); onSave(); @@ -354,10 +317,6 @@ const AvailabilityManager: React.FC = ({ } }; - const getTimeSlotsForTimetable = (): TimeSlot[] => { - return timeSlots; - }; - if (loading) { return (
@@ -366,8 +325,6 @@ const AvailabilityManager: React.FC = ({ ); } - const timetableTimeSlots = getTimeSlotsForTimetable(); - return (
= ({ )}
- {/* Rest der Komponente... */} + {/* Employee Info */}

{employee.name} @@ -442,7 +399,7 @@ const AvailabilityManager: React.FC = ({

)} - {/* Verfügbarkeits-Legende */} + {/* Availability Legend */}
= ({
- {/* Schichtplan Auswahl */} + {/* Shift Plan Selection */}
= ({ {shiftPlans.map(plan => ( ))} @@ -517,8 +474,8 @@ const AvailabilityManager: React.FC = ({
- {/* Verfügbarkeits-Timetable */} - {timetableTimeSlots.length > 0 ? ( + {/* Availability Timetable */} + {timeSlots.length > 0 ? (
= ({ }}> Verfügbarkeit definieren
- {timetableTimeSlots.length} Schichttypen verfügbar + {timeSlots.length} Schichttypen verfügbar
@@ -568,8 +525,8 @@ const AvailabilityManager: React.FC = ({ - {timetableTimeSlots.map((timeSlot, timeIndex) => ( - ( + = ({ }}>

Keine Schichttypen konfiguriert

-

Es wurden keine Zeit-Slots in den Schichtvorlagen oder -plänen gefunden.

+

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

- Bitte erstellen Sie zuerst Schichtvorlagen mit Zeit-Slots. + Bitte erstellen Sie zuerst Schichtpläne mit Zeit-Slots.

)} diff --git a/frontend/src/services/employeeService.ts b/frontend/src/services/employeeService.ts index e5e141f..a76a21f 100644 --- a/frontend/src/services/employeeService.ts +++ b/frontend/src/services/employeeService.ts @@ -1,5 +1,5 @@ // 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'; @@ -90,7 +90,7 @@ export class EmployeeService { } } - async getAvailabilities(employeeId: string): Promise { + async getAvailabilities(employeeId: string): Promise { const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, { headers: getAuthHeaders(), }); @@ -102,7 +102,7 @@ export class EmployeeService { return response.json(); } - async updateAvailabilities(employeeId: string, availabilities: Availability[]): Promise { + async updateAvailabilities(employeeId: string, availabilities: EmployeeAvailability[]): Promise { const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, { method: 'PUT', headers: getAuthHeaders(), diff --git a/frontend/src/services/shiftPlanService.ts b/frontend/src/services/shiftPlanService.ts index 1114b9f..e62e3be 100644 --- a/frontend/src/services/shiftPlanService.ts +++ b/frontend/src/services/shiftPlanService.ts @@ -1,6 +1,6 @@ // frontend/src/services/shiftPlanService.ts 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'; @@ -21,20 +21,7 @@ export const shiftPlanService = { throw new Error('Fehler beim Laden der Schichtpläne'); } - const data = 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 || [] - })); + return await response.json(); }, async getShiftPlan(id: string): Promise { @@ -53,20 +40,7 @@ export const shiftPlanService = { throw new Error('Schichtplan nicht gefunden'); } - const data = 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 || [] - })); + return await response.json(); }, async createShiftPlan(plan: CreateShiftPlanRequest): Promise { @@ -127,60 +101,5 @@ export const shiftPlanService = { } throw new Error('Fehler beim Löschen des Schichtplans'); } - }, - - async updateShiftPlanShift(planId: string, shift: ShiftSlot): Promise { - 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): Promise { - 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 { - 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'); - } } -}; +}; \ No newline at end of file diff --git a/frontend/src/services/shiftTemplateService.ts b/frontend/src/services/shiftTemplateService.ts index a64390d..ede1cd7 100644 --- a/frontend/src/services/shiftTemplateService.ts +++ b/frontend/src/services/shiftTemplateService.ts @@ -1,5 +1,5 @@ // 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'; const API_BASE = 'http://localhost:3002/api/shift-templates'; @@ -22,15 +22,10 @@ export const shiftTemplateService = { } const templates = await response.json(); - // Sortiere die Vorlagen so, dass die Standard-Vorlage immer zuerst kommt - 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(); - }); + return templates; }, - async getTemplate(id: string): Promise { + async getTemplate(id: string): Promise { const response = await fetch(`${API_BASE}/${id}`, { headers: { 'Content-Type': 'application/json', @@ -49,18 +44,7 @@ export const shiftTemplateService = { return response.json(); }, - async createTemplate(template: Omit): Promise { - // 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'); - } - } - + async createTemplate(template: Omit): Promise { const response = await fetch(API_BASE, { method: 'POST', headers: { @@ -81,7 +65,7 @@ export const shiftTemplateService = { return response.json(); }, - async updateTemplate(id: string, template: Partial): Promise { + async updateTemplate(id: string, template: Partial): Promise { const response = await fetch(`${API_BASE}/${id}`, { method: 'PUT', headers: {