mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +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 => ({
|
||||
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,
|
||||
|
||||
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 (
|
||||
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,
|
||||
|
||||
@@ -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,28 +83,107 @@ const ShiftPlanList: React.FC = () => {
|
||||
backgroundColor: '#3498db',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px'
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer'
|
||||
}}>
|
||||
+ Neuen Plan
|
||||
</button>
|
||||
</Link>
|
||||
)}
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface ShiftPlanShift {
|
||||
id: string;
|
||||
shiftPlanId: string;
|
||||
date: string;
|
||||
name: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
requiredEmployees: number;
|
||||
|
||||
Reference in New Issue
Block a user