From 142bee1cf9b6e25adb4b234e527c1145a0588830 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sun, 12 Oct 2025 17:27:07 +0200 Subject: [PATCH] removed users table relicts --- backend/src/controllers/setupController.ts | 2 +- .../src/models/defaults/employeeDefaults.ts | 6 +- backend/src/scripts/checkTemplates.ts | 1 - backend/src/scripts/initializeDatabase.ts | 16 +- backend/src/server.ts | 2 +- frontend/src/models/ShiftPlan.ts | 1 + .../src/models/defaults/employeeDefaults.ts | 10 +- .../Employees/components/EmployeeForm.tsx | 15 +- .../Employees/components/EmployeeList.tsx | 11 +- .../src/pages/ShiftPlans/ShiftPlanCreate.tsx | 37 +- .../src/pages/ShiftPlans/ShiftPlanEdit.tsx | 350 +++++++++--------- .../src/pages/ShiftPlans/ShiftPlanView.tsx | 10 + frontend/src/services/shiftPlanService.ts | 2 +- 13 files changed, 254 insertions(+), 209 deletions(-) diff --git a/backend/src/controllers/setupController.ts b/backend/src/controllers/setupController.ts index f07cec6..821a72f 100644 --- a/backend/src/controllers/setupController.ts +++ b/backend/src/controllers/setupController.ts @@ -8,7 +8,7 @@ import { db } from '../services/databaseService.js'; export const checkSetupStatus = async (req: Request, res: Response): Promise => { try { 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'] ); diff --git a/backend/src/models/defaults/employeeDefaults.ts b/backend/src/models/defaults/employeeDefaults.ts index f6131a0..9058f2e 100644 --- a/backend/src/models/defaults/employeeDefaults.ts +++ b/backend/src/models/defaults/employeeDefaults.ts @@ -44,9 +44,9 @@ export const EMPLOYEE_TYPE_CONFIG = { } as const; export const ROLE_CONFIG = [ - { value: 'user', label: 'Mitarbeiter', description: 'Kann eigene Schichten einsehen', color: '#27ae60' }, - { value: 'instandhalter', label: 'Instandhalter', description: 'Kann Schichtpläne erstellen und Mitarbeiter verwalten', color: '#3498db' }, - { value: 'admin', label: 'Administrator', description: 'Voller Zugriff auf alle Funktionen', color: '#e74c3c' } + { value: 'user' as const, label: 'Mitarbeiter', description: 'Kann eigene Schichten einsehen', color: '#27ae60' }, + { value: 'maintenance' as const, label: 'Instandhalter', description: 'Kann Schichtpläne erstellen und Mitarbeiter verwalten', color: '#3498db' }, + { value: 'admin' as const, label: 'Administrator', description: 'Voller Zugriff auf alle Funktionen', color: '#e74c3c' } ] as const; // Contract type descriptions diff --git a/backend/src/scripts/checkTemplates.ts b/backend/src/scripts/checkTemplates.ts index df2df11..dac82ea 100644 --- a/backend/src/scripts/checkTemplates.ts +++ b/backend/src/scripts/checkTemplates.ts @@ -3,7 +3,6 @@ import { db } from '../services/databaseService.js'; async function checkTemplates() { try { - // KORREKTUR: employees statt users verwenden const templates = await db.all( `SELECT sp.*, e.name as created_by_name FROM shift_plans sp diff --git a/backend/src/scripts/initializeDatabase.ts b/backend/src/scripts/initializeDatabase.ts index f40e756..d0e7a30 100644 --- a/backend/src/scripts/initializeDatabase.ts +++ b/backend/src/scripts/initializeDatabase.ts @@ -18,7 +18,7 @@ export async function initializeDatabase(): Promise { // Check if users table exists and has data try { const existingAdmin = await db.get<{ count: number }>( - "SELECT COUNT(*) as count FROM users WHERE role = 'admin'" + "SELECT COUNT(*) as count FROM employees WHERE role = 'admin'" ); if (existingAdmin && existingAdmin.count > 0) { @@ -43,12 +43,14 @@ export async function initializeDatabase(): Promise { // Drop existing tables in reverse order of dependencies if they exist const tablesToDrop = [ - 'employee_availabilities', - 'assigned_shifts', - 'shift_plans', - 'template_shifts', - 'shift_templates', - 'users' + 'employees', + 'time_slots', + 'shifts', + 'scheduled_shifts', + 'shift_assignments', + 'employee_availability', + 'applied_migrations', + 'shift_plans' ]; for (const table of tablesToDrop) { diff --git a/backend/src/server.ts b/backend/src/server.ts index 17dc7f3..0c2427a 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -40,7 +40,7 @@ app.get('/api/initial-setup', async (req: any, res: any) => { const { db } = await import('./services/databaseService.js'); const adminExists = await db.get<{ 'COUNT(*)': number }>( - 'SELECT COUNT(*) FROM users WHERE role = ?', + 'SELECT COUNT(*) FROM employees WHERE role = ?', ['admin'] ); diff --git a/frontend/src/models/ShiftPlan.ts b/frontend/src/models/ShiftPlan.ts index 50d089d..9cfc1f8 100644 --- a/frontend/src/models/ShiftPlan.ts +++ b/frontend/src/models/ShiftPlan.ts @@ -69,6 +69,7 @@ export interface CreateShiftPlanRequest { isTemplate: boolean; timeSlots: Omit[]; shifts: Omit[]; + templateId?: string; } export interface UpdateShiftPlanRequest { diff --git a/frontend/src/models/defaults/employeeDefaults.ts b/frontend/src/models/defaults/employeeDefaults.ts index f6131a0..881934e 100644 --- a/frontend/src/models/defaults/employeeDefaults.ts +++ b/frontend/src/models/defaults/employeeDefaults.ts @@ -19,29 +19,29 @@ export const MANAGER_DEFAULTS = { isActive: true }; -export const EMPLOYEE_TYPE_CONFIG = { - manager: { +export const EMPLOYEE_TYPE_CONFIG = [ + { value: 'manager' as const, label: 'Chef/Administrator', color: '#e74c3c', independent: true, description: 'Vollzugriff auf alle Funktionen und Mitarbeiterverwaltung' }, - experienced: { + { value: 'experienced' as const, label: 'Erfahren', color: '#3498db', independent: true, description: 'Langjährige Erfahrung, kann komplexe Aufgaben übernehmen' }, - trainee: { + { value: 'trainee' as const, label: 'Neuling', color: '#27ae60', independent: false, description: 'Benötigt Einarbeitung und Unterstützung' } -} as const; + ] as const; export const ROLE_CONFIG = [ { value: 'user', label: 'Mitarbeiter', description: 'Kann eigene Schichten einsehen', color: '#27ae60' }, diff --git a/frontend/src/pages/Employees/components/EmployeeForm.tsx b/frontend/src/pages/Employees/components/EmployeeForm.tsx index a6bc039..85f6627 100644 --- a/frontend/src/pages/Employees/components/EmployeeForm.tsx +++ b/frontend/src/pages/Employees/components/EmployeeForm.tsx @@ -382,14 +382,25 @@ const EmployeeForm: React.FC = ({ backgroundColor: formData.role === role.value ? '#fef9e7' : 'white', cursor: 'pointer' }} - onClick={() => setFormData(prev => ({ ...prev, role: role.value }))} + onClick={() => { + // Use a direct setter instead of the function form + setFormData(prev => ({ + ...prev, + role: role.value as 'admin' | 'maintenance' | 'user' + })); + }} > setFormData(prev => ({ ...prev, role: role.value }))} + onChange={(e) => { + setFormData(prev => ({ + ...prev, + role: e.target.value as 'admin' | 'maintenance' | 'user' + })); + }} style={{ marginRight: '10px', marginTop: '2px' diff --git a/frontend/src/pages/Employees/components/EmployeeList.tsx b/frontend/src/pages/Employees/components/EmployeeList.tsx index 3130ce7..0e0f64a 100644 --- a/frontend/src/pages/Employees/components/EmployeeList.tsx +++ b/frontend/src/pages/Employees/components/EmployeeList.tsx @@ -1,7 +1,7 @@ // frontend/src/pages/Employees/components/EmployeeList.tsx - KORRIGIERT import React, { useState } from 'react'; -import { ROLE_CONFIG, EMPLOYEE_TYPE_CONFIG } from '../../../../../backend/src/models/defaults/employeeDefaults'; -import { Employee } from '../../../../../backend/src/models/employee'; +import { ROLE_CONFIG, EMPLOYEE_TYPE_CONFIG } from '../../../models/defaults/employeeDefaults'; +import { Employee } from '../../../models/Employee'; import { useAuth } from '../../../contexts/AuthContext'; interface EmployeeListProps { @@ -55,9 +55,10 @@ const EmployeeList: React.FC = ({ }; // Using shared configuration for consistent styling - const getEmployeeTypeBadge = (type: keyof typeof EMPLOYEE_TYPE_CONFIG) => { - return EMPLOYEE_TYPE_CONFIG[type] || EMPLOYEE_TYPE_CONFIG.trainee; - }; + const getEmployeeTypeBadge = (type: 'manager' | 'trainee' | 'experienced') => { + const config = EMPLOYEE_TYPE_CONFIG.find(t => t.value === type); + return config || EMPLOYEE_TYPE_CONFIG[0]; +}; const getStatusBadge = (isActive: boolean) => { return isActive diff --git a/frontend/src/pages/ShiftPlans/ShiftPlanCreate.tsx b/frontend/src/pages/ShiftPlans/ShiftPlanCreate.tsx index 24ff0d1..d990aa9 100644 --- a/frontend/src/pages/ShiftPlans/ShiftPlanCreate.tsx +++ b/frontend/src/pages/ShiftPlans/ShiftPlanCreate.tsx @@ -4,6 +4,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom'; import { shiftTemplateService } from '../../services/shiftTemplateService'; import { shiftPlanService } from '../../services/shiftPlanService'; import styles from './ShiftPlanCreate.module.css'; +import { TimeSlot, Shift } from '../../models/ShiftPlan'; export interface TemplateShift { id: string; @@ -42,9 +43,8 @@ const ShiftPlanCreate: React.FC = () => { // Wenn keine Template-ID in der URL ist, setze die Standard-Vorlage if (!searchParams.get('template')) { - const defaultTemplate = data.find(t => t.isDefault); - if (defaultTemplate) { - setSelectedTemplate(defaultTemplate.id); + if (!searchParams.get('template') && data.length > 0) { + setSelectedTemplate(data[0].id); } } } catch (error) { @@ -74,11 +74,40 @@ const ShiftPlanCreate: React.FC = () => { return; } + let timeSlots: Omit[] = []; + let shifts: Omit[] = []; + + // If a template is selected, load its data + if (selectedTemplate) { + try { + const template = await shiftTemplateService.getTemplate(selectedTemplate); + timeSlots = template.timeSlots.map(slot => ({ + name: slot.name, + startTime: slot.startTime, + endTime: slot.endTime, + description: slot.description + })); + shifts = template.shifts.map(shift => ({ + timeSlotId: shift.timeSlotId, + dayOfWeek: shift.dayOfWeek, + requiredEmployees: shift.requiredEmployees, + color: shift.color + })); + } catch (error) { + console.error('Fehler beim Laden der Vorlage:', error); + setError('Die ausgewählte Vorlage konnte nicht geladen werden'); + return; + } + } + await shiftPlanService.createShiftPlan({ name: planName, startDate, endDate, - templateId: selectedTemplate || undefined + isTemplate: false, + templateId: selectedTemplate || undefined, + timeSlots, + shifts }); // Nach erfolgreicher Erstellung zur Liste der Schichtpläne navigieren diff --git a/frontend/src/pages/ShiftPlans/ShiftPlanEdit.tsx b/frontend/src/pages/ShiftPlans/ShiftPlanEdit.tsx index 6926e78..c71084c 100644 --- a/frontend/src/pages/ShiftPlans/ShiftPlanEdit.tsx +++ b/frontend/src/pages/ShiftPlans/ShiftPlanEdit.tsx @@ -2,9 +2,8 @@ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { shiftPlanService } from '../../services/shiftPlanService'; -import { ShiftPlan, Shift } from '../../../../backend/src/models/shiftPlan'; +import { ShiftPlan, Shift, ScheduledShift } from '../../models/ShiftPlan'; import { useNotification } from '../../contexts/NotificationContext'; -import { getTimeSlotById } from '../../models/helpers/shiftPlanHelpers'; const ShiftPlanEdit: React.FC = () => { const { id } = useParams<{ id: string }>(); @@ -13,9 +12,9 @@ const ShiftPlanEdit: React.FC = () => { const [shiftPlan, setShiftPlan] = useState(null); const [loading, setLoading] = useState(true); const [editingShift, setEditingShift] = useState(null); - const [newShift, setNewShift] = useState>({ - date: '', + const [newShift, setNewShift] = useState>({ timeSlotId: '', + dayOfWeek: 1, requiredEmployees: 1 }); @@ -45,6 +44,7 @@ const ShiftPlanEdit: React.FC = () => { if (!shiftPlan || !id) return; try { + // Update logic here loadShiftPlan(); setEditingShift(null); } catch (error) { @@ -60,26 +60,25 @@ const ShiftPlanEdit: React.FC = () => { const handleAddShift = async () => { if (!shiftPlan || !id) return; - if (!getTimeSlotById(shiftPlan, newShift.timeSlotId?) || !newShift.name || !newShift.startTime || !newShift.endTime || !newShift.requiredEmployees) { + if (!newShift.timeSlotId || !newShift.requiredEmployees) { showNotification({ type: 'error', title: 'Fehler', message: 'Bitte füllen Sie alle Pflichtfelder aus.' }); return; - } + } try { + // Add shift logic here showNotification({ type: 'success', title: 'Erfolg', message: 'Neue Schicht wurde hinzugefügt.' }); setNewShift({ - date: '', - name: '', - startTime: '', - endTime: '', + timeSlotId: '', + dayOfWeek: 1, requiredEmployees: 1 }); loadShiftPlan(); @@ -99,6 +98,7 @@ const ShiftPlanEdit: React.FC = () => { } try { + // Delete logic here loadShiftPlan(); } catch (error) { console.error('Error deleting shift:', error); @@ -142,14 +142,24 @@ const ShiftPlanEdit: React.FC = () => { return
Schichtplan nicht gefunden
; } - // Group shifts by date - const shiftsByDate = shiftPlan.shifts.reduce((acc, shift) => { - if (!acc[shift.date]) { - acc[shift.date] = []; + // Group shifts by dayOfWeek + const shiftsByDay = shiftPlan.shifts.reduce((acc, shift) => { + if (!acc[shift.dayOfWeek]) { + acc[shift.dayOfWeek] = []; } - acc[shift.date].push(shift); + acc[shift.dayOfWeek].push(shift); return acc; - }, {} as Record); + }, {} as Record); + + const daysOfWeek = [ + { id: 1, name: 'Montag' }, + { id: 2, name: 'Dienstag' }, + { id: 3, name: 'Mittwoch' }, + { id: 4, name: 'Donnerstag' }, + { id: 5, name: 'Freitag' }, + { id: 6, name: 'Samstag' }, + { id: 7, name: 'Sonntag' } + ]; return (
@@ -204,40 +214,31 @@ const ShiftPlanEdit: React.FC = () => {

Neue Schicht hinzufügen

- - setNewShift({ ...newShift, date: e.target.value })} + +
- - setNewShift({ ...newShift, name: e.target.value })} + + setNewShift({ ...newShift, startTime: e.target.value })} - style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }} - /> -
-
- - setNewShift({ ...newShift, endTime: e.target.value })} - style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }} - /> + > + + {shiftPlan.timeSlots.map(slot => ( + + ))} +
@@ -252,7 +253,7 @@ const ShiftPlanEdit: React.FC = () => {
- -
+ {daysOfWeek.map(day => { + const shifts = shiftsByDay[day.id] || []; + if (shifts.length === 0) return null; + + return ( +
+

{day.name}

+
+ {shifts.map(shift => { + const timeSlot = shiftPlan.timeSlots.find(ts => ts.id === shift.timeSlotId); + return ( +
+ {editingShift?.id === shift.id ? ( +
+
+ + +
+
+ + setEditingShift({ ...editingShift, requiredEmployees: parseInt(e.target.value) })} + style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }} + /> +
+
+ + +
+
+ ) : ( + <> +
+ {timeSlot?.name} ({timeSlot?.startTime?.substring(0, 5)} - {timeSlot?.endTime?.substring(0, 5)}) +
+
+
+ Benötigte Mitarbeiter: {shift.requiredEmployees} +
+
+ + +
+
+ + )}
- ) : ( - <> -
- {shift.name} -
-
-
- Zeit: {shift.startTime.substring(0, 5)} - {shift.endTime.substring(0, 5)} - | - Benötigte Mitarbeiter: {shift.requiredEmployees} - | - Zugewiesen: {shift.assignedEmployees.length}/{shift.requiredEmployees} -
-
- - -
-
- - )} -
- ))} + ); + })} +
- - ))} + ); + })} ); diff --git a/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx b/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx index 0a899a1..1440269 100644 --- a/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx +++ b/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx @@ -20,6 +20,16 @@ const ShiftPlanView: React.FC = () => { loadShiftPlan(); }, [id]); + const weekdays = [ + { id: 1, name: 'Mo' }, + { id: 2, name: 'Di' }, + { id: 3, name: 'Mi' }, + { id: 4, name: 'Do' }, + { id: 5, name: 'Fr' }, + { id: 6, name: 'Sa' }, + { id: 7, name: 'So' } + ]; + const loadShiftPlan = async () => { if (!id) return; try { diff --git a/frontend/src/services/shiftPlanService.ts b/frontend/src/services/shiftPlanService.ts index e62e3be..55fb404 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, Shift } from '../../../backend/src/models/shiftPlan.js'; +import { ShiftPlan, CreateShiftPlanRequest, Shift } from '../models/ShiftPlan'; const API_BASE = 'http://localhost:3002/api/shift-plans';