added listing shift plans

This commit is contained in:
2025-10-10 19:49:49 +02:00
parent 541af0afee
commit 9cc0778384
5 changed files with 176 additions and 24 deletions

View File

@@ -96,6 +96,7 @@ export const getShiftPlan = async (req: AuthRequest, res: Response): Promise<voi
shifts: assignedShifts.map(shift => ({
id: shift.id,
date: shift.date,
name: shift.name,
startTime: shift.start_time,
endTime: shift.end_time,
requiredEmployees: shift.required_employees,
@@ -163,6 +164,7 @@ export const createShiftPlan = async (req: AuthRequest, res: Response): Promise<
shifts: assignedShifts.map(shift => ({
id: shift.id,
date: shift.date,
name: shift.name,
startTime: shift.start_time,
endTime: shift.end_time,
requiredEmployees: shift.required_employees,
@@ -308,7 +310,8 @@ async function generateShiftsFromTemplate(shiftPlanId: string, templateId: strin
// Generate shifts for each day in the date range
for (let date = new Date(start); date <= end; date.setDate(date.getDate() + 1)) {
const dayOfWeek = date.getDay(); // 0 = Sunday, 1 = Monday, etc.
// Convert JS day (0=Sunday) to our format (1=Monday, 7=Sunday)
const dayOfWeek = date.getDay() === 0 ? 7 : date.getDay();
// Find template shifts for this day of week
const shiftsForDay = templateShifts.filter(shift => shift.day_of_week === dayOfWeek);
@@ -317,12 +320,13 @@ async function generateShiftsFromTemplate(shiftPlanId: string, templateId: strin
const shiftId = uuidv4();
await db.run(
`INSERT INTO assigned_shifts (id, shift_plan_id, date, start_time, end_time, required_employees, assigned_employees)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
`INSERT INTO assigned_shifts (id, shift_plan_id, date, name, start_time, end_time, required_employees, assigned_employees)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[
shiftId,
shiftPlanId,
date.toISOString().split('T')[0],
templateShift.name,
templateShift.start_time,
templateShift.end_time,
templateShift.required_employees,

View File

@@ -0,0 +1,2 @@
-- backend/src/database/migrations/003_add_shift_name.sql
ALTER TABLE assigned_shifts ADD COLUMN name TEXT;

View File

@@ -27,7 +27,7 @@ CREATE TABLE IF NOT EXISTS shift_templates (
CREATE TABLE IF NOT EXISTS template_shifts (
id TEXT PRIMARY KEY,
template_id TEXT NOT NULL,
day_of_week INTEGER NOT NULL CHECK (day_of_week >= 1 AND day_of_week <= 7),
day_of_week INTEGER NOT NULL CHECK (day_of_week >= 1 AND day_of_week <= 5),
name TEXT NOT NULL,
start_time TEXT NOT NULL,
end_time TEXT NOT NULL,
@@ -64,7 +64,7 @@ CREATE TABLE IF NOT EXISTS assigned_shifts (
CREATE TABLE IF NOT EXISTS employee_availabilities (
id TEXT PRIMARY KEY,
employee_id TEXT NOT NULL,
day_of_week INTEGER NOT NULL CHECK (day_of_week >= 0 AND day_of_week <= 6),
day_of_week INTEGER NOT NULL CHECK (day_of_week >= 0 AND day_of_week <= 4),
start_time TEXT NOT NULL,
end_time TEXT NOT NULL,
is_available BOOLEAN DEFAULT FALSE,

View File

@@ -1,14 +1,80 @@
// frontend/src/pages/ShiftPlans/ShiftPlanList.tsx
import React from 'react';
import { Link } from 'react-router-dom';
import React, { useState, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext';
import { shiftPlanService, ShiftPlan } from '../../services/shiftPlanService';
import { useNotification } from '../../contexts/NotificationContext';
const ShiftPlanList: React.FC = () => {
const { hasRole } = useAuth();
const navigate = useNavigate();
const { showNotification } = useNotification();
const [shiftPlans, setShiftPlans] = useState<ShiftPlan[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadShiftPlans();
}, []);
const loadShiftPlans = async () => {
try {
const plans = await shiftPlanService.getShiftPlans();
setShiftPlans(plans);
} catch (error) {
console.error('Error loading shift plans:', error);
showNotification({
type: 'error',
title: 'Fehler',
message: 'Die Schichtpläne konnten nicht geladen werden.'
});
} finally {
setLoading(false);
}
};
const handleDelete = async (id: string) => {
if (!window.confirm('Möchten Sie diesen Schichtplan wirklich löschen?')) {
return;
}
try {
await shiftPlanService.deleteShiftPlan(id);
showNotification({
type: 'success',
title: 'Erfolg',
message: 'Der Schichtplan wurde erfolgreich gelöscht.'
});
loadShiftPlans();
} catch (error) {
console.error('Error deleting shift plan:', error);
showNotification({
type: 'error',
title: 'Fehler',
message: 'Der Schichtplan konnte nicht gelöscht werden.'
});
}
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
};
if (loading) {
return <div>Lade Schichtpläne...</div>;
}
return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '30px' }}>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '30px'
}}>
<h1>📅 Schichtpläne</h1>
{hasRole(['admin', 'instandhalter']) && (
<Link to="/shift-plans/new">
@@ -17,7 +83,8 @@ const ShiftPlanList: React.FC = () => {
backgroundColor: '#3498db',
color: 'white',
border: 'none',
borderRadius: '4px'
borderRadius: '4px',
cursor: 'pointer'
}}>
+ Neuen Plan
</button>
@@ -25,20 +92,98 @@ const ShiftPlanList: React.FC = () => {
)}
</div>
<div style={{
padding: '40px',
textAlign: 'center',
backgroundColor: '#f8f9fa',
borderRadius: '8px',
border: '2px dashed #dee2e6'
}}>
<div style={{ fontSize: '48px', marginBottom: '20px' }}>📋</div>
<h3>Schichtpläne Übersicht</h3>
<p>Hier werden alle Schichtpläne angezeigt.</p>
<p style={{ fontSize: '14px', color: '#6c757d' }}>
Diese Seite wird demnächst mit Funktionen gefüllt.
</p>
</div>
{shiftPlans.length === 0 ? (
<div style={{
padding: '40px',
textAlign: 'center',
backgroundColor: '#f8f9fa',
borderRadius: '8px',
border: '2px dashed #dee2e6'
}}>
<div style={{ fontSize: '48px', marginBottom: '20px' }}>📋</div>
<h3>Keine Schichtpläne vorhanden</h3>
<p>Erstellen Sie Ihren ersten Schichtplan!</p>
</div>
) : (
<div style={{ display: 'grid', gap: '20px' }}>
{shiftPlans.map(plan => (
<div
key={plan.id}
style={{
padding: '20px',
backgroundColor: 'white',
borderRadius: '8px',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}
>
<div>
<h3 style={{ margin: '0 0 10px 0' }}>{plan.name}</h3>
<div style={{ color: '#666', fontSize: '14px' }}>
<p style={{ margin: '0' }}>
Zeitraum: {formatDate(plan.startDate)} - {formatDate(plan.endDate)}
</p>
<p style={{ margin: '5px 0 0 0' }}>
Status: <span style={{
color: plan.status === 'published' ? '#2ecc71' : '#f1c40f',
fontWeight: 'bold'
}}>
{plan.status === 'published' ? 'Veröffentlicht' : 'Entwurf'}
</span>
</p>
</div>
</div>
<div style={{ display: 'flex', gap: '10px' }}>
<button
onClick={() => navigate(`/shift-plans/${plan.id}`)}
style={{
padding: '8px 16px',
backgroundColor: '#2ecc71',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Anzeigen
</button>
{hasRole(['admin', 'instandhalter']) && (
<>
<button
onClick={() => navigate(`/shift-plans/${plan.id}/edit`)}
style={{
padding: '8px 16px',
backgroundColor: '#f1c40f',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Bearbeiten
</button>
<button
onClick={() => handleDelete(plan.id)}
style={{
padding: '8px 16px',
backgroundColor: '#e74c3c',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Löschen
</button>
</>
)}
</div>
</div>
))}
</div>
)}
</div>
);
};

View File

@@ -26,6 +26,7 @@ export interface ShiftPlanShift {
id: string;
shiftPlanId: string;
date: string;
name: string;
startTime: string;
endTime: string;
requiredEmployees: number;