mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 15:05:45 +01:00
removed users table relicts
This commit is contained in:
@@ -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']
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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']
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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' },
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,7 +60,7 @@ 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',
|
||||||
@@ -70,16 +70,15 @@ const ShiftPlanEdit: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,16 +270,22 @@ 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] || [];
|
||||||
|
if (shifts.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={day.id} style={{
|
||||||
backgroundColor: 'white',
|
backgroundColor: 'white',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
padding: '20px',
|
padding: '20px',
|
||||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
||||||
}}>
|
}}>
|
||||||
<h3 style={{ marginTop: 0 }}>{new Date(date).toLocaleDateString('de-DE', { weekday: 'long', day: '2-digit', month: '2-digit', year: 'numeric' })}</h3>
|
<h3 style={{ marginTop: 0 }}>{day.name}</h3>
|
||||||
<div style={{ display: 'grid', gap: '15px' }}>
|
<div style={{ display: 'grid', gap: '15px' }}>
|
||||||
{shifts.map(shift => (
|
{shifts.map(shift => {
|
||||||
|
const timeSlot = shiftPlan.timeSlots.find(ts => ts.id === shift.timeSlotId);
|
||||||
|
return (
|
||||||
<div key={shift.id} style={{
|
<div key={shift.id} style={{
|
||||||
backgroundColor: '#f8f9fa',
|
backgroundColor: '#f8f9fa',
|
||||||
padding: '15px',
|
padding: '15px',
|
||||||
@@ -288,31 +295,18 @@ const ShiftPlanEdit: React.FC = () => {
|
|||||||
{editingShift?.id === shift.id ? (
|
{editingShift?.id === shift.id ? (
|
||||||
<div style={{ display: 'grid', gap: '10px', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))' }}>
|
<div style={{ display: 'grid', gap: '10px', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))' }}>
|
||||||
<div>
|
<div>
|
||||||
<label>Name</label>
|
<label>Zeit-Slot</label>
|
||||||
<input
|
<select
|
||||||
type="text"
|
value={editingShift.timeSlotId}
|
||||||
value={editingShift.name}
|
onChange={(e) => setEditingShift({ ...editingShift, timeSlotId: e.target.value })}
|
||||||
onChange={(e) => setEditingShift({ ...editingShift, 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>
|
{shiftPlan.timeSlots.map(slot => (
|
||||||
<div>
|
<option key={slot.id} value={slot.id}>
|
||||||
<label>Startzeit</label>
|
{slot.name} ({slot.startTime}-{slot.endTime})
|
||||||
<input
|
</option>
|
||||||
type="time"
|
))}
|
||||||
value={editingShift.startTime}
|
</select>
|
||||||
onChange={(e) => setEditingShift({ ...editingShift, startTime: e.target.value })}
|
|
||||||
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Endzeit</label>
|
|
||||||
<input
|
|
||||||
type="time"
|
|
||||||
value={editingShift.endTime}
|
|
||||||
onChange={(e) => setEditingShift({ ...editingShift, 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>
|
||||||
@@ -356,15 +350,11 @@ const ShiftPlanEdit: React.FC = () => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div style={{ fontWeight: 'bold', marginBottom: '5px' }}>
|
<div style={{ fontWeight: 'bold', marginBottom: '5px' }}>
|
||||||
{shift.name}
|
{timeSlot?.name} ({timeSlot?.startTime?.substring(0, 5)} - {timeSlot?.endTime?.substring(0, 5)})
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<div style={{ fontSize: '14px', color: '#666' }}>
|
<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>Benötigte Mitarbeiter: {shift.requiredEmployees}</span>
|
||||||
<span style={{ margin: '0 15px' }}>|</span>
|
|
||||||
<span>Zugewiesen: {shift.assignedEmployees.length}/{shift.requiredEmployees}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
@@ -399,10 +389,12 @@ const ShiftPlanEdit: React.FC = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user