mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 06:55:45 +01:00
added listing shift plans
This commit is contained in:
@@ -96,6 +96,7 @@ export const getShiftPlan = async (req: AuthRequest, res: Response): Promise<voi
|
|||||||
shifts: assignedShifts.map(shift => ({
|
shifts: assignedShifts.map(shift => ({
|
||||||
id: shift.id,
|
id: shift.id,
|
||||||
date: shift.date,
|
date: shift.date,
|
||||||
|
name: shift.name,
|
||||||
startTime: shift.start_time,
|
startTime: shift.start_time,
|
||||||
endTime: shift.end_time,
|
endTime: shift.end_time,
|
||||||
requiredEmployees: shift.required_employees,
|
requiredEmployees: shift.required_employees,
|
||||||
@@ -163,6 +164,7 @@ export const createShiftPlan = async (req: AuthRequest, res: Response): Promise<
|
|||||||
shifts: assignedShifts.map(shift => ({
|
shifts: assignedShifts.map(shift => ({
|
||||||
id: shift.id,
|
id: shift.id,
|
||||||
date: shift.date,
|
date: shift.date,
|
||||||
|
name: shift.name,
|
||||||
startTime: shift.start_time,
|
startTime: shift.start_time,
|
||||||
endTime: shift.end_time,
|
endTime: shift.end_time,
|
||||||
requiredEmployees: shift.required_employees,
|
requiredEmployees: shift.required_employees,
|
||||||
@@ -308,7 +310,8 @@ async function generateShiftsFromTemplate(shiftPlanId: string, templateId: strin
|
|||||||
|
|
||||||
// Generate shifts for each day in the date range
|
// Generate shifts for each day in the date range
|
||||||
for (let date = new Date(start); date <= end; date.setDate(date.getDate() + 1)) {
|
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
|
// Find template shifts for this day of week
|
||||||
const shiftsForDay = templateShifts.filter(shift => shift.day_of_week === dayOfWeek);
|
const shiftsForDay = templateShifts.filter(shift => shift.day_of_week === dayOfWeek);
|
||||||
@@ -317,12 +320,13 @@ async function generateShiftsFromTemplate(shiftPlanId: string, templateId: strin
|
|||||||
const shiftId = uuidv4();
|
const shiftId = uuidv4();
|
||||||
|
|
||||||
await db.run(
|
await db.run(
|
||||||
`INSERT INTO assigned_shifts (id, shift_plan_id, date, start_time, end_time, required_employees, assigned_employees)
|
`INSERT INTO assigned_shifts (id, shift_plan_id, date, name, start_time, end_time, required_employees, assigned_employees)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[
|
[
|
||||||
shiftId,
|
shiftId,
|
||||||
shiftPlanId,
|
shiftPlanId,
|
||||||
date.toISOString().split('T')[0],
|
date.toISOString().split('T')[0],
|
||||||
|
templateShift.name,
|
||||||
templateShift.start_time,
|
templateShift.start_time,
|
||||||
templateShift.end_time,
|
templateShift.end_time,
|
||||||
templateShift.required_employees,
|
templateShift.required_employees,
|
||||||
|
|||||||
2
backend/src/database/migrations/003_add_shift_name.sql
Normal file
2
backend/src/database/migrations/003_add_shift_name.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- backend/src/database/migrations/003_add_shift_name.sql
|
||||||
|
ALTER TABLE assigned_shifts ADD COLUMN name TEXT;
|
||||||
@@ -27,7 +27,7 @@ CREATE TABLE IF NOT EXISTS shift_templates (
|
|||||||
CREATE TABLE IF NOT EXISTS template_shifts (
|
CREATE TABLE IF NOT EXISTS template_shifts (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
template_id TEXT NOT NULL,
|
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,
|
name TEXT NOT NULL,
|
||||||
start_time TEXT NOT NULL,
|
start_time TEXT NOT NULL,
|
||||||
end_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 (
|
CREATE TABLE IF NOT EXISTS employee_availabilities (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
employee_id TEXT NOT NULL,
|
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,
|
start_time TEXT NOT NULL,
|
||||||
end_time TEXT NOT NULL,
|
end_time TEXT NOT NULL,
|
||||||
is_available BOOLEAN DEFAULT FALSE,
|
is_available BOOLEAN DEFAULT FALSE,
|
||||||
|
|||||||
@@ -1,14 +1,80 @@
|
|||||||
// frontend/src/pages/ShiftPlans/ShiftPlanList.tsx
|
// frontend/src/pages/ShiftPlans/ShiftPlanList.tsx
|
||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
|
import { shiftPlanService, ShiftPlan } from '../../services/shiftPlanService';
|
||||||
|
import { useNotification } from '../../contexts/NotificationContext';
|
||||||
|
|
||||||
const ShiftPlanList: React.FC = () => {
|
const ShiftPlanList: React.FC = () => {
|
||||||
const { hasRole } = useAuth();
|
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 (
|
return (
|
||||||
<div>
|
<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>
|
<h1>📅 Schichtpläne</h1>
|
||||||
{hasRole(['admin', 'instandhalter']) && (
|
{hasRole(['admin', 'instandhalter']) && (
|
||||||
<Link to="/shift-plans/new">
|
<Link to="/shift-plans/new">
|
||||||
@@ -17,7 +83,8 @@ const ShiftPlanList: React.FC = () => {
|
|||||||
backgroundColor: '#3498db',
|
backgroundColor: '#3498db',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '4px'
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer'
|
||||||
}}>
|
}}>
|
||||||
+ Neuen Plan
|
+ Neuen Plan
|
||||||
</button>
|
</button>
|
||||||
@@ -25,20 +92,98 @@ const ShiftPlanList: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{
|
{shiftPlans.length === 0 ? (
|
||||||
padding: '40px',
|
<div style={{
|
||||||
textAlign: 'center',
|
padding: '40px',
|
||||||
backgroundColor: '#f8f9fa',
|
textAlign: 'center',
|
||||||
borderRadius: '8px',
|
backgroundColor: '#f8f9fa',
|
||||||
border: '2px dashed #dee2e6'
|
borderRadius: '8px',
|
||||||
}}>
|
border: '2px dashed #dee2e6'
|
||||||
<div style={{ fontSize: '48px', marginBottom: '20px' }}>📋</div>
|
}}>
|
||||||
<h3>Schichtpläne Übersicht</h3>
|
<div style={{ fontSize: '48px', marginBottom: '20px' }}>📋</div>
|
||||||
<p>Hier werden alle Schichtpläne angezeigt.</p>
|
<h3>Keine Schichtpläne vorhanden</h3>
|
||||||
<p style={{ fontSize: '14px', color: '#6c757d' }}>
|
<p>Erstellen Sie Ihren ersten Schichtplan!</p>
|
||||||
Diese Seite wird demnächst mit Funktionen gefüllt.
|
</div>
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface ShiftPlanShift {
|
|||||||
id: string;
|
id: string;
|
||||||
shiftPlanId: string;
|
shiftPlanId: string;
|
||||||
date: string;
|
date: string;
|
||||||
|
name: string;
|
||||||
startTime: string;
|
startTime: string;
|
||||||
endTime: string;
|
endTime: string;
|
||||||
requiredEmployees: number;
|
requiredEmployees: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user