removed users table relicts

This commit is contained in:
2025-10-12 17:27:07 +02:00
parent 90d8ae5140
commit 142bee1cf9
13 changed files with 254 additions and 209 deletions

View File

@@ -8,7 +8,7 @@ import { db } from '../services/databaseService.js';
export const checkSetupStatus = async (req: Request, res: Response): Promise<void> => { export const checkSetupStatus = async (req: Request, res: Response): Promise<void> => {
try { try {
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']
); );

View File

@@ -44,9 +44,9 @@ export const EMPLOYEE_TYPE_CONFIG = {
} as const; } as const;
export const ROLE_CONFIG = [ export const ROLE_CONFIG = [
{ value: 'user', label: 'Mitarbeiter', description: 'Kann eigene Schichten einsehen', color: '#27ae60' }, { value: 'user' as const, label: 'Mitarbeiter', description: 'Kann eigene Schichten einsehen', color: '#27ae60' },
{ value: 'instandhalter', label: 'Instandhalter', description: 'Kann Schichtpläne erstellen und Mitarbeiter verwalten', color: '#3498db' }, { value: 'maintenance' as const, 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: 'admin' as const, label: 'Administrator', description: 'Voller Zugriff auf alle Funktionen', color: '#e74c3c' }
] as const; ] as const;
// Contract type descriptions // Contract type descriptions

View File

@@ -3,7 +3,6 @@ import { db } from '../services/databaseService.js';
async function checkTemplates() { async function checkTemplates() {
try { try {
// KORREKTUR: employees statt users verwenden
const templates = await db.all<any>( const templates = await db.all<any>(
`SELECT sp.*, e.name as created_by_name `SELECT sp.*, e.name as created_by_name
FROM shift_plans sp FROM shift_plans sp

View File

@@ -18,7 +18,7 @@ export async function initializeDatabase(): Promise<void> {
// Check if users table exists and has data // Check if users table exists and has data
try { try {
const existingAdmin = await db.get<{ count: number }>( 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) { if (existingAdmin && existingAdmin.count > 0) {
@@ -43,12 +43,14 @@ export async function initializeDatabase(): Promise<void> {
// Drop existing tables in reverse order of dependencies if they exist // Drop existing tables in reverse order of dependencies if they exist
const tablesToDrop = [ const tablesToDrop = [
'employee_availabilities', 'employees',
'assigned_shifts', 'time_slots',
'shift_plans', 'shifts',
'template_shifts', 'scheduled_shifts',
'shift_templates', 'shift_assignments',
'users' 'employee_availability',
'applied_migrations',
'shift_plans'
]; ];
for (const table of tablesToDrop) { for (const table of tablesToDrop) {

View File

@@ -40,7 +40,7 @@ app.get('/api/initial-setup', async (req: any, res: any) => {
const { db } = await import('./services/databaseService.js'); const { db } = await import('./services/databaseService.js');
const adminExists = await db.get<{ 'COUNT(*)': number }>( const adminExists = await db.get<{ 'COUNT(*)': number }>(
'SELECT COUNT(*) FROM users WHERE role = ?', 'SELECT COUNT(*) FROM employees WHERE role = ?',
['admin'] ['admin']
); );

View File

@@ -69,6 +69,7 @@ export interface CreateShiftPlanRequest {
isTemplate: boolean; isTemplate: boolean;
timeSlots: Omit<TimeSlot, 'id' | 'planId'>[]; timeSlots: Omit<TimeSlot, 'id' | 'planId'>[];
shifts: Omit<Shift, 'id' | 'planId'>[]; shifts: Omit<Shift, 'id' | 'planId'>[];
templateId?: string;
} }
export interface UpdateShiftPlanRequest { export interface UpdateShiftPlanRequest {

View File

@@ -19,29 +19,29 @@ export const MANAGER_DEFAULTS = {
isActive: true isActive: true
}; };
export const EMPLOYEE_TYPE_CONFIG = { export const EMPLOYEE_TYPE_CONFIG = [
manager: { {
value: 'manager' as const, value: 'manager' as const,
label: 'Chef/Administrator', label: 'Chef/Administrator',
color: '#e74c3c', color: '#e74c3c',
independent: true, independent: true,
description: 'Vollzugriff auf alle Funktionen und Mitarbeiterverwaltung' description: 'Vollzugriff auf alle Funktionen und Mitarbeiterverwaltung'
}, },
experienced: { {
value: 'experienced' as const, value: 'experienced' as const,
label: 'Erfahren', label: 'Erfahren',
color: '#3498db', color: '#3498db',
independent: true, independent: true,
description: 'Langjährige Erfahrung, kann komplexe Aufgaben übernehmen' description: 'Langjährige Erfahrung, kann komplexe Aufgaben übernehmen'
}, },
trainee: { {
value: 'trainee' as const, value: 'trainee' as const,
label: 'Neuling', label: 'Neuling',
color: '#27ae60', color: '#27ae60',
independent: false, independent: false,
description: 'Benötigt Einarbeitung und Unterstützung' description: 'Benötigt Einarbeitung und Unterstützung'
} }
} as const; ] as const;
export const ROLE_CONFIG = [ export const ROLE_CONFIG = [
{ value: 'user', label: 'Mitarbeiter', description: 'Kann eigene Schichten einsehen', color: '#27ae60' }, { value: 'user', label: 'Mitarbeiter', description: 'Kann eigene Schichten einsehen', color: '#27ae60' },

View File

@@ -382,14 +382,25 @@ const EmployeeForm: React.FC<EmployeeFormProps> = ({
backgroundColor: formData.role === role.value ? '#fef9e7' : 'white', backgroundColor: formData.role === role.value ? '#fef9e7' : 'white',
cursor: 'pointer' 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'
}));
}}
> >
<input <input
type="radio" type="radio"
name="role" name="role"
value={role.value} value={role.value}
checked={formData.role === role.value} checked={formData.role === role.value}
onChange={() => setFormData(prev => ({ ...prev, role: role.value }))} onChange={(e) => {
setFormData(prev => ({
...prev,
role: e.target.value as 'admin' | 'maintenance' | 'user'
}));
}}
style={{ style={{
marginRight: '10px', marginRight: '10px',
marginTop: '2px' marginTop: '2px'

View File

@@ -1,7 +1,7 @@
// frontend/src/pages/Employees/components/EmployeeList.tsx - KORRIGIERT // frontend/src/pages/Employees/components/EmployeeList.tsx - KORRIGIERT
import React, { useState } from 'react'; import React, { useState } from 'react';
import { ROLE_CONFIG, EMPLOYEE_TYPE_CONFIG } from '../../../../../backend/src/models/defaults/employeeDefaults'; import { ROLE_CONFIG, EMPLOYEE_TYPE_CONFIG } from '../../../models/defaults/employeeDefaults';
import { Employee } from '../../../../../backend/src/models/employee'; import { Employee } from '../../../models/Employee';
import { useAuth } from '../../../contexts/AuthContext'; import { useAuth } from '../../../contexts/AuthContext';
interface EmployeeListProps { interface EmployeeListProps {
@@ -55,9 +55,10 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
}; };
// Using shared configuration for consistent styling // Using shared configuration for consistent styling
const getEmployeeTypeBadge = (type: keyof typeof EMPLOYEE_TYPE_CONFIG) => { const getEmployeeTypeBadge = (type: 'manager' | 'trainee' | 'experienced') => {
return EMPLOYEE_TYPE_CONFIG[type] || EMPLOYEE_TYPE_CONFIG.trainee; const config = EMPLOYEE_TYPE_CONFIG.find(t => t.value === type);
}; return config || EMPLOYEE_TYPE_CONFIG[0];
};
const getStatusBadge = (isActive: boolean) => { const getStatusBadge = (isActive: boolean) => {
return isActive return isActive

View File

@@ -4,6 +4,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
import { shiftTemplateService } from '../../services/shiftTemplateService'; import { shiftTemplateService } from '../../services/shiftTemplateService';
import { shiftPlanService } from '../../services/shiftPlanService'; import { shiftPlanService } from '../../services/shiftPlanService';
import styles from './ShiftPlanCreate.module.css'; import styles from './ShiftPlanCreate.module.css';
import { TimeSlot, Shift } from '../../models/ShiftPlan';
export interface TemplateShift { export interface TemplateShift {
id: string; id: string;
@@ -42,9 +43,8 @@ const ShiftPlanCreate: React.FC = () => {
// Wenn keine Template-ID in der URL ist, setze die Standard-Vorlage // Wenn keine Template-ID in der URL ist, setze die Standard-Vorlage
if (!searchParams.get('template')) { if (!searchParams.get('template')) {
const defaultTemplate = data.find(t => t.isDefault); if (!searchParams.get('template') && data.length > 0) {
if (defaultTemplate) { setSelectedTemplate(data[0].id);
setSelectedTemplate(defaultTemplate.id);
} }
} }
} catch (error) { } catch (error) {
@@ -74,11 +74,40 @@ const ShiftPlanCreate: React.FC = () => {
return; return;
} }
let timeSlots: Omit<TimeSlot, 'id' | 'planId'>[] = [];
let shifts: Omit<Shift, 'id' | 'planId'>[] = [];
// 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({ await shiftPlanService.createShiftPlan({
name: planName, name: planName,
startDate, startDate,
endDate, endDate,
templateId: selectedTemplate || undefined isTemplate: false,
templateId: selectedTemplate || undefined,
timeSlots,
shifts
}); });
// Nach erfolgreicher Erstellung zur Liste der Schichtpläne navigieren // Nach erfolgreicher Erstellung zur Liste der Schichtpläne navigieren

View File

@@ -2,9 +2,8 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { shiftPlanService } from '../../services/shiftPlanService'; 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 { useNotification } from '../../contexts/NotificationContext';
import { getTimeSlotById } from '../../models/helpers/shiftPlanHelpers';
const ShiftPlanEdit: React.FC = () => { const ShiftPlanEdit: React.FC = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
@@ -13,9 +12,9 @@ const ShiftPlanEdit: React.FC = () => {
const [shiftPlan, setShiftPlan] = useState<ShiftPlan | null>(null); const [shiftPlan, setShiftPlan] = useState<ShiftPlan | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [editingShift, setEditingShift] = useState<Shift | null>(null); const [editingShift, setEditingShift] = useState<Shift | null>(null);
const [newShift, setNewShift] = useState<Partial<ScheduledShift>>({ const [newShift, setNewShift] = useState<Partial<Shift>>({
date: '',
timeSlotId: '', timeSlotId: '',
dayOfWeek: 1,
requiredEmployees: 1 requiredEmployees: 1
}); });
@@ -45,6 +44,7 @@ const ShiftPlanEdit: React.FC = () => {
if (!shiftPlan || !id) return; if (!shiftPlan || !id) return;
try { try {
// Update logic here
loadShiftPlan(); loadShiftPlan();
setEditingShift(null); setEditingShift(null);
} catch (error) { } catch (error) {
@@ -60,26 +60,25 @@ const ShiftPlanEdit: React.FC = () => {
const handleAddShift = async () => { const handleAddShift = async () => {
if (!shiftPlan || !id) return; if (!shiftPlan || !id) return;
if (!getTimeSlotById(shiftPlan, newShift.timeSlotId?) || !newShift.name || !newShift.startTime || !newShift.endTime || !newShift.requiredEmployees) { if (!newShift.timeSlotId || !newShift.requiredEmployees) {
showNotification({ showNotification({
type: 'error', type: 'error',
title: 'Fehler', title: 'Fehler',
message: 'Bitte füllen Sie alle Pflichtfelder aus.' message: 'Bitte füllen Sie alle Pflichtfelder aus.'
}); });
return; return;
} }
try { try {
// Add shift logic here
showNotification({ showNotification({
type: 'success', type: 'success',
title: 'Erfolg', title: 'Erfolg',
message: 'Neue Schicht wurde hinzugefügt.' message: 'Neue Schicht wurde hinzugefügt.'
}); });
setNewShift({ setNewShift({
date: '', timeSlotId: '',
name: '', dayOfWeek: 1,
startTime: '',
endTime: '',
requiredEmployees: 1 requiredEmployees: 1
}); });
loadShiftPlan(); loadShiftPlan();
@@ -99,6 +98,7 @@ const ShiftPlanEdit: React.FC = () => {
} }
try { try {
// Delete logic here
loadShiftPlan(); loadShiftPlan();
} catch (error) { } catch (error) {
console.error('Error deleting shift:', error); console.error('Error deleting shift:', error);
@@ -142,14 +142,24 @@ const ShiftPlanEdit: React.FC = () => {
return <div>Schichtplan nicht gefunden</div>; return <div>Schichtplan nicht gefunden</div>;
} }
// Group shifts by date // Group shifts by dayOfWeek
const shiftsByDate = shiftPlan.shifts.reduce((acc, shift) => { const shiftsByDay = shiftPlan.shifts.reduce((acc, shift) => {
if (!acc[shift.date]) { if (!acc[shift.dayOfWeek]) {
acc[shift.date] = []; acc[shift.dayOfWeek] = [];
} }
acc[shift.date].push(shift); acc[shift.dayOfWeek].push(shift);
return acc; return acc;
}, {} as Record<string, typeof shiftPlan.shifts>); }, {} as Record<number, typeof shiftPlan.shifts>);
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 ( return (
<div style={{ padding: '20px' }}> <div style={{ padding: '20px' }}>
@@ -204,40 +214,31 @@ const ShiftPlanEdit: React.FC = () => {
<h3>Neue Schicht hinzufügen</h3> <h3>Neue Schicht hinzufügen</h3>
<div style={{ display: 'grid', gap: '15px', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))' }}> <div style={{ display: 'grid', gap: '15px', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))' }}>
<div> <div>
<label>Datum</label> <label>Wochentag</label>
<input <select
type="date" value={newShift.dayOfWeek}
value={newShift.date} onChange={(e) => setNewShift({ ...newShift, dayOfWeek: parseInt(e.target.value) })}
onChange={(e) => setNewShift({ ...newShift, date: e.target.value })}
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }} style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
/> >
{daysOfWeek.map(day => (
<option key={day.id} value={day.id}>{day.name}</option>
))}
</select>
</div> </div>
<div> <div>
<label>Name</label> <label>Zeit-Slot</label>
<input <select
type="text" value={newShift.timeSlotId}
value={newShift.name} onChange={(e) => setNewShift({ ...newShift, timeSlotId: e.target.value })}
onChange={(e) => setNewShift({ ...newShift, name: e.target.value })}
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }} style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
/> >
</div> <option value="">Bitte auswählen...</option>
<div> {shiftPlan.timeSlots.map(slot => (
<label>Startzeit</label> <option key={slot.id} value={slot.id}>
<input {slot.name} ({slot.startTime}-{slot.endTime})
type="time" </option>
value={newShift.startTime} ))}
onChange={(e) => setNewShift({ ...newShift, startTime: e.target.value })} </select>
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
/>
</div>
<div>
<label>Endzeit</label>
<input
type="time"
value={newShift.endTime}
onChange={(e) => setNewShift({ ...newShift, endTime: e.target.value })}
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
/>
</div> </div>
<div> <div>
<label>Benötigte Mitarbeiter</label> <label>Benötigte Mitarbeiter</label>
@@ -252,7 +253,7 @@ const ShiftPlanEdit: React.FC = () => {
</div> </div>
<button <button
onClick={handleAddShift} onClick={handleAddShift}
disabled={!newShift.date || !newShift.name || !newShift.startTime || !newShift.endTime} disabled={!newShift.timeSlotId || !newShift.requiredEmployees}
style={{ style={{
marginTop: '15px', marginTop: '15px',
padding: '8px 16px', padding: '8px 16px',
@@ -269,140 +270,131 @@ const ShiftPlanEdit: React.FC = () => {
{/* Existing shifts */} {/* Existing shifts */}
<div style={{ display: 'grid', gap: '20px' }}> <div style={{ display: 'grid', gap: '20px' }}>
{Object.entries(shiftsByDate).map(([date, shifts]) => ( {daysOfWeek.map(day => {
<div key={date} style={{ const shifts = shiftsByDay[day.id] || [];
backgroundColor: 'white', if (shifts.length === 0) return null;
borderRadius: '8px',
padding: '20px', return (
boxShadow: '0 2px 4px rgba(0,0,0,0.1)' <div key={day.id} style={{
}}> backgroundColor: 'white',
<h3 style={{ marginTop: 0 }}>{new Date(date).toLocaleDateString('de-DE', { weekday: 'long', day: '2-digit', month: '2-digit', year: 'numeric' })}</h3> borderRadius: '8px',
<div style={{ display: 'grid', gap: '15px' }}> padding: '20px',
{shifts.map(shift => ( boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
<div key={shift.id} style={{ }}>
backgroundColor: '#f8f9fa', <h3 style={{ marginTop: 0 }}>{day.name}</h3>
padding: '15px', <div style={{ display: 'grid', gap: '15px' }}>
borderRadius: '6px', {shifts.map(shift => {
boxShadow: '0 1px 3px rgba(0,0,0,0.05)' const timeSlot = shiftPlan.timeSlots.find(ts => ts.id === shift.timeSlotId);
}}> return (
{editingShift?.id === shift.id ? ( <div key={shift.id} style={{
<div style={{ display: 'grid', gap: '10px', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))' }}> backgroundColor: '#f8f9fa',
<div> padding: '15px',
<label>Name</label> borderRadius: '6px',
<input boxShadow: '0 1px 3px rgba(0,0,0,0.05)'
type="text" }}>
value={editingShift.name} {editingShift?.id === shift.id ? (
onChange={(e) => setEditingShift({ ...editingShift, name: e.target.value })} <div style={{ display: 'grid', gap: '10px', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))' }}>
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }} <div>
/> <label>Zeit-Slot</label>
</div> <select
<div> value={editingShift.timeSlotId}
<label>Startzeit</label> onChange={(e) => setEditingShift({ ...editingShift, timeSlotId: e.target.value })}
<input style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
type="time" >
value={editingShift.startTime} {shiftPlan.timeSlots.map(slot => (
onChange={(e) => setEditingShift({ ...editingShift, startTime: e.target.value })} <option key={slot.id} value={slot.id}>
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }} {slot.name} ({slot.startTime}-{slot.endTime})
/> </option>
</div> ))}
<div> </select>
<label>Endzeit</label> </div>
<input <div>
type="time" <label>Benötigte Mitarbeiter</label>
value={editingShift.endTime} <input
onChange={(e) => setEditingShift({ ...editingShift, endTime: e.target.value })} type="number"
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }} min="1"
/> value={editingShift.requiredEmployees}
</div> onChange={(e) => setEditingShift({ ...editingShift, requiredEmployees: parseInt(e.target.value) })}
<div> style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
<label>Benötigte Mitarbeiter</label> />
<input </div>
type="number" <div style={{ display: 'flex', gap: '10px', alignItems: 'flex-end' }}>
min="1" <button
value={editingShift.requiredEmployees} onClick={() => handleUpdateShift(editingShift)}
onChange={(e) => setEditingShift({ ...editingShift, requiredEmployees: parseInt(e.target.value) })} style={{
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }} padding: '8px 16px',
/> backgroundColor: '#2ecc71',
</div> color: 'white',
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-end' }}> border: 'none',
<button borderRadius: '4px',
onClick={() => handleUpdateShift(editingShift)} cursor: 'pointer'
style={{ }}
padding: '8px 16px', >
backgroundColor: '#2ecc71', Speichern
color: 'white', </button>
border: 'none', <button
borderRadius: '4px', onClick={() => setEditingShift(null)}
cursor: 'pointer' style={{
}} padding: '8px 16px',
> backgroundColor: '#95a5a6',
Speichern color: 'white',
</button> border: 'none',
<button borderRadius: '4px',
onClick={() => setEditingShift(null)} cursor: 'pointer'
style={{ }}
padding: '8px 16px', >
backgroundColor: '#95a5a6', Abbrechen
color: 'white', </button>
border: 'none', </div>
borderRadius: '4px', </div>
cursor: 'pointer' ) : (
}} <>
> <div style={{ fontWeight: 'bold', marginBottom: '5px' }}>
Abbrechen {timeSlot?.name} ({timeSlot?.startTime?.substring(0, 5)} - {timeSlot?.endTime?.substring(0, 5)})
</button> </div>
</div> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ fontSize: '14px', color: '#666' }}>
<span>Benötigte Mitarbeiter: {shift.requiredEmployees}</span>
</div>
<div>
<button
onClick={() => setEditingShift(shift)}
style={{
padding: '6px 12px',
backgroundColor: '#f1c40f',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
marginRight: '8px'
}}
>
Bearbeiten
</button>
<button
onClick={() => handleDeleteShift(shift.id)}
style={{
padding: '6px 12px',
backgroundColor: '#e74c3c',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Löschen
</button>
</div>
</div>
</>
)}
</div> </div>
) : ( );
<> })}
<div style={{ fontWeight: 'bold', marginBottom: '5px' }}> </div>
{shift.name}
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ fontSize: '14px', color: '#666' }}>
<span>Zeit: {shift.startTime.substring(0, 5)} - {shift.endTime.substring(0, 5)}</span>
<span style={{ margin: '0 15px' }}>|</span>
<span>Benötigte Mitarbeiter: {shift.requiredEmployees}</span>
<span style={{ margin: '0 15px' }}>|</span>
<span>Zugewiesen: {shift.assignedEmployees.length}/{shift.requiredEmployees}</span>
</div>
<div>
<button
onClick={() => setEditingShift(shift)}
style={{
padding: '6px 12px',
backgroundColor: '#f1c40f',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
marginRight: '8px'
}}
>
Bearbeiten
</button>
<button
onClick={() => handleDeleteShift(shift.id)}
style={{
padding: '6px 12px',
backgroundColor: '#e74c3c',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Löschen
</button>
</div>
</div>
</>
)}
</div>
))}
</div> </div>
</div> );
))} })}
</div> </div>
</div> </div>
); );

View File

@@ -20,6 +20,16 @@ const ShiftPlanView: React.FC = () => {
loadShiftPlan(); loadShiftPlan();
}, [id]); }, [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 () => { const loadShiftPlan = async () => {
if (!id) return; if (!id) return;
try { try {

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, Shift } from '../../../backend/src/models/shiftPlan.js'; import { ShiftPlan, CreateShiftPlanRequest, Shift } from '../models/ShiftPlan';
const API_BASE = 'http://localhost:3002/api/shift-plans'; const API_BASE = 'http://localhost:3002/api/shift-plans';