mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 06:55:45 +01:00
set template shift struc
This commit is contained in:
@@ -3,7 +3,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { shiftTemplateService } from '../../services/shiftTemplateService';
|
||||
import { shiftPlanService } from '../../services/shiftPlanService';
|
||||
import { ShiftTemplate } from '../../types/shiftTemplate';
|
||||
import { TemplateShift } from '../../types/shiftTemplate';
|
||||
import styles from './ShiftPlanCreate.module.css';
|
||||
|
||||
const ShiftPlanCreate: React.FC = () => {
|
||||
@@ -14,7 +14,7 @@ const ShiftPlanCreate: React.FC = () => {
|
||||
const [startDate, setStartDate] = useState('');
|
||||
const [endDate, setEndDate] = useState('');
|
||||
const [selectedTemplate, setSelectedTemplate] = useState('');
|
||||
const [templates, setTemplates] = useState<ShiftTemplate[]>([]);
|
||||
const [templates, setTemplates] = useState<TemplateShift[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
|
||||
430
frontend/src/pages/ShiftPlans/ShiftPlanEdit.tsx
Normal file
430
frontend/src/pages/ShiftPlans/ShiftPlanEdit.tsx
Normal file
@@ -0,0 +1,430 @@
|
||||
// frontend/src/pages/ShiftPlans/ShiftPlanEdit.tsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { shiftPlanService, ShiftPlan, ShiftPlanShift } from '../../services/shiftPlanService';
|
||||
import { useNotification } from '../../contexts/NotificationContext';
|
||||
|
||||
const ShiftPlanEdit: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { showNotification } = useNotification();
|
||||
const [shiftPlan, setShiftPlan] = useState<ShiftPlan | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [editingShift, setEditingShift] = useState<ShiftPlanShift | null>(null);
|
||||
const [newShift, setNewShift] = useState<Partial<ShiftPlanShift>>({
|
||||
date: '',
|
||||
name: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
requiredEmployees: 1
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
loadShiftPlan();
|
||||
}, [id]);
|
||||
|
||||
const loadShiftPlan = async () => {
|
||||
if (!id) return;
|
||||
try {
|
||||
const plan = await shiftPlanService.getShiftPlan(id);
|
||||
setShiftPlan(plan);
|
||||
} catch (error) {
|
||||
console.error('Error loading shift plan:', error);
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: 'Der Schichtplan konnte nicht geladen werden.'
|
||||
});
|
||||
navigate('/shift-plans');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateShift = async (shift: ShiftPlanShift) => {
|
||||
if (!shiftPlan || !id) return;
|
||||
|
||||
try {
|
||||
await shiftPlanService.updateShiftPlanShift(id, shift);
|
||||
showNotification({
|
||||
type: 'success',
|
||||
title: 'Erfolg',
|
||||
message: 'Schicht wurde aktualisiert.'
|
||||
});
|
||||
loadShiftPlan();
|
||||
setEditingShift(null);
|
||||
} catch (error) {
|
||||
console.error('Error updating shift:', error);
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: 'Die Schicht konnte nicht aktualisiert werden.'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddShift = async () => {
|
||||
if (!shiftPlan || !id) return;
|
||||
|
||||
if (!newShift.date || !newShift.name || !newShift.startTime || !newShift.endTime || !newShift.requiredEmployees) {
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: 'Bitte füllen Sie alle Pflichtfelder aus.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await shiftPlanService.addShiftPlanShift(id, {
|
||||
date: newShift.date,
|
||||
name: newShift.name,
|
||||
startTime: newShift.startTime,
|
||||
endTime: newShift.endTime,
|
||||
requiredEmployees: Number(newShift.requiredEmployees)
|
||||
});
|
||||
showNotification({
|
||||
type: 'success',
|
||||
title: 'Erfolg',
|
||||
message: 'Neue Schicht wurde hinzugefügt.'
|
||||
});
|
||||
setNewShift({
|
||||
date: '',
|
||||
name: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
requiredEmployees: 1
|
||||
});
|
||||
loadShiftPlan();
|
||||
} catch (error) {
|
||||
console.error('Error adding shift:', error);
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: 'Die Schicht konnte nicht hinzugefügt werden.'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteShift = async (shiftId: string) => {
|
||||
if (!window.confirm('Möchten Sie diese Schicht wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await shiftPlanService.deleteShiftPlanShift(id!, shiftId);
|
||||
showNotification({
|
||||
type: 'success',
|
||||
title: 'Erfolg',
|
||||
message: 'Schicht wurde gelöscht.'
|
||||
});
|
||||
loadShiftPlan();
|
||||
} catch (error) {
|
||||
console.error('Error deleting shift:', error);
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: 'Die Schicht konnte nicht gelöscht werden.'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handlePublish = async () => {
|
||||
if (!shiftPlan || !id) return;
|
||||
|
||||
try {
|
||||
await shiftPlanService.updateShiftPlan(id, {
|
||||
...shiftPlan,
|
||||
status: 'published'
|
||||
});
|
||||
showNotification({
|
||||
type: 'success',
|
||||
title: 'Erfolg',
|
||||
message: 'Schichtplan wurde veröffentlicht.'
|
||||
});
|
||||
loadShiftPlan();
|
||||
} catch (error) {
|
||||
console.error('Error publishing shift plan:', error);
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: 'Der Schichtplan konnte nicht veröffentlicht werden.'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <div>Lade Schichtplan...</div>;
|
||||
}
|
||||
|
||||
if (!shiftPlan) {
|
||||
return <div>Schichtplan nicht gefunden</div>;
|
||||
}
|
||||
|
||||
// Group shifts by date
|
||||
const shiftsByDate = shiftPlan.shifts.reduce((acc, shift) => {
|
||||
if (!acc[shift.date]) {
|
||||
acc[shift.date] = [];
|
||||
}
|
||||
acc[shift.date].push(shift);
|
||||
return acc;
|
||||
}, {} as Record<string, typeof shiftPlan.shifts>);
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: '30px'
|
||||
}}>
|
||||
<h1>{shiftPlan.name} bearbeiten</h1>
|
||||
<div>
|
||||
{shiftPlan.status === 'draft' && (
|
||||
<button
|
||||
onClick={handlePublish}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#2ecc71',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
marginRight: '10px'
|
||||
}}
|
||||
>
|
||||
Veröffentlichen
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => navigate('/shift-plans')}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#95a5a6',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Zurück
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Add new shift form */}
|
||||
<div style={{
|
||||
backgroundColor: 'white',
|
||||
borderRadius: '8px',
|
||||
padding: '20px',
|
||||
marginBottom: '20px',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
||||
}}>
|
||||
<h3>Neue Schicht hinzufügen</h3>
|
||||
<div style={{ display: 'grid', gap: '15px', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))' }}>
|
||||
<div>
|
||||
<label>Datum</label>
|
||||
<input
|
||||
type="date"
|
||||
value={newShift.date}
|
||||
onChange={(e) => setNewShift({ ...newShift, date: e.target.value })}
|
||||
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newShift.name}
|
||||
onChange={(e) => setNewShift({ ...newShift, name: e.target.value })}
|
||||
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Startzeit</label>
|
||||
<input
|
||||
type="time"
|
||||
value={newShift.startTime}
|
||||
onChange={(e) => setNewShift({ ...newShift, startTime: e.target.value })}
|
||||
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>
|
||||
<label>Benötigte Mitarbeiter</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
value={newShift.requiredEmployees}
|
||||
onChange={(e) => setNewShift({ ...newShift, requiredEmployees: parseInt(e.target.value) })}
|
||||
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleAddShift}
|
||||
disabled={!newShift.date || !newShift.name || !newShift.startTime || !newShift.endTime}
|
||||
style={{
|
||||
marginTop: '15px',
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#3498db',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Schicht hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Existing shifts */}
|
||||
<div style={{ display: 'grid', gap: '20px' }}>
|
||||
{Object.entries(shiftsByDate).map(([date, shifts]) => (
|
||||
<div key={date} style={{
|
||||
backgroundColor: 'white',
|
||||
borderRadius: '8px',
|
||||
padding: '20px',
|
||||
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>
|
||||
<div style={{ display: 'grid', gap: '15px' }}>
|
||||
{shifts.map(shift => (
|
||||
<div key={shift.id} style={{
|
||||
backgroundColor: '#f8f9fa',
|
||||
padding: '15px',
|
||||
borderRadius: '6px',
|
||||
boxShadow: '0 1px 3px rgba(0,0,0,0.05)'
|
||||
}}>
|
||||
{editingShift?.id === shift.id ? (
|
||||
<div style={{ display: 'grid', gap: '10px', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))' }}>
|
||||
<div>
|
||||
<label>Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editingShift.name}
|
||||
onChange={(e) => setEditingShift({ ...editingShift, name: e.target.value })}
|
||||
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Startzeit</label>
|
||||
<input
|
||||
type="time"
|
||||
value={editingShift.startTime}
|
||||
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>
|
||||
<label>Benötigte Mitarbeiter</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
value={editingShift.requiredEmployees}
|
||||
onChange={(e) => setEditingShift({ ...editingShift, requiredEmployees: parseInt(e.target.value) })}
|
||||
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-end' }}>
|
||||
<button
|
||||
onClick={() => handleUpdateShift(editingShift)}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#2ecc71',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setEditingShift(null)}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#95a5a6',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div style={{ fontWeight: 'bold', marginBottom: '5px' }}>
|
||||
{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>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShiftPlanEdit;
|
||||
292
frontend/src/pages/ShiftPlans/ShiftPlanView.tsx
Normal file
292
frontend/src/pages/ShiftPlans/ShiftPlanView.tsx
Normal file
@@ -0,0 +1,292 @@
|
||||
// frontend/src/pages/ShiftPlans/ShiftPlanView.tsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
import { shiftPlanService, ShiftPlan } from '../../services/shiftPlanService';
|
||||
import { useNotification } from '../../contexts/NotificationContext';
|
||||
|
||||
const ShiftPlanView: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { hasRole } = useAuth();
|
||||
const { showNotification } = useNotification();
|
||||
const [shiftPlan, setShiftPlan] = useState<ShiftPlan | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadShiftPlan();
|
||||
}, [id]);
|
||||
|
||||
const loadShiftPlan = async () => {
|
||||
if (!id) return;
|
||||
try {
|
||||
const plan = await shiftPlanService.getShiftPlan(id);
|
||||
setShiftPlan(plan);
|
||||
} catch (error) {
|
||||
console.error('Error loading shift plan:', error);
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: 'Der Schichtplan konnte nicht geladen werden.'
|
||||
});
|
||||
navigate('/shift-plans');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string | undefined): string => {
|
||||
if (!dateString) return 'Kein Datum';
|
||||
|
||||
const date = new Date(dateString);
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
return 'Ungültiges Datum';
|
||||
}
|
||||
|
||||
return date.toLocaleDateString('de-DE', {
|
||||
weekday: 'long',
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
const formatTime = (timeString: string) => {
|
||||
return timeString.substring(0, 5);
|
||||
};
|
||||
|
||||
// Get unique shift types and their staffing per weekday
|
||||
const getTimetableData = () => {
|
||||
if (!shiftPlan) return { shifts: [], weekdays: [] };
|
||||
|
||||
// Get all unique shift types (name + time combination)
|
||||
const shiftTypes = Array.from(new Set(
|
||||
shiftPlan.shifts.map(shift =>
|
||||
`${shift.name}|${shift.startTime}|${shift.endTime}`
|
||||
)
|
||||
)).map(shiftKey => {
|
||||
const [name, startTime, endTime] = shiftKey.split('|');
|
||||
return { name, startTime, endTime };
|
||||
});
|
||||
|
||||
// Weekdays (1=Monday, 7=Sunday)
|
||||
const weekdays = [1, 2, 3, 4, 5, 6, 7];
|
||||
|
||||
// For each shift type and weekday, calculate staffing
|
||||
const timetableShifts = shiftTypes.map(shiftType => {
|
||||
const weekdayData: Record<number, string> = {};
|
||||
|
||||
weekdays.forEach(weekday => {
|
||||
// Find all shifts of this type on this weekday
|
||||
const shiftsOnDay = shiftPlan.shifts.filter(shift => {
|
||||
const date = new Date(shift.date);
|
||||
const dayOfWeek = date.getDay() === 0 ? 7 : date.getDay(); // Convert to 1-7 (Mon-Sun)
|
||||
return dayOfWeek === weekday &&
|
||||
shift.name === shiftType.name &&
|
||||
shift.startTime === shiftType.startTime &&
|
||||
shift.endTime === shiftType.endTime;
|
||||
});
|
||||
|
||||
if (shiftsOnDay.length === 0) {
|
||||
weekdayData[weekday] = '';
|
||||
} else {
|
||||
const totalAssigned = shiftsOnDay.reduce((sum, shift) => sum + shift.assignedEmployees.length, 0);
|
||||
const totalRequired = shiftsOnDay.reduce((sum, shift) => sum + shift.requiredEmployees, 0);
|
||||
weekdayData[weekday] = `${totalAssigned}/${totalRequired}`;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...shiftType,
|
||||
displayName: `${shiftType.name} (${formatTime(shiftType.startTime)}–${formatTime(shiftType.endTime)})`,
|
||||
weekdayData
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
shifts: timetableShifts,
|
||||
weekdays: weekdays.map(day => ({
|
||||
id: day,
|
||||
name: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'][day === 7 ? 0 : day]
|
||||
}))
|
||||
};
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <div>Lade Schichtplan...</div>;
|
||||
}
|
||||
|
||||
if (!shiftPlan) {
|
||||
return <div>Schichtplan nicht gefunden</div>;
|
||||
}
|
||||
|
||||
const timetableData = getTimetableData();
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: '30px'
|
||||
}}>
|
||||
<div>
|
||||
<h1>{shiftPlan.name}</h1>
|
||||
<p style={{ color: '#666', marginTop: '5px' }}>
|
||||
Zeitraum: {formatDate(shiftPlan.startDate)} - {formatDate(shiftPlan.endDate)}
|
||||
</p>
|
||||
<p style={{ color: '#666', marginTop: '5px' }}>
|
||||
Status: <span style={{
|
||||
color: shiftPlan.status === 'published' ? '#2ecc71' : '#f1c40f',
|
||||
fontWeight: 'bold'
|
||||
}}>
|
||||
{shiftPlan.status === 'published' ? 'Veröffentlicht' : 'Entwurf'}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
{hasRole(['admin', 'instandhalter']) && (
|
||||
<button
|
||||
onClick={() => navigate(`/shift-plans/${id}/edit`)}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#f1c40f',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
marginRight: '10px'
|
||||
}}
|
||||
>
|
||||
Bearbeiten
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => navigate('/shift-plans')}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#95a5a6',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Zurück
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
backgroundColor: 'white',
|
||||
borderRadius: '8px',
|
||||
padding: '20px',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
||||
}}>
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<div>Zeitraum: {formatDate(shiftPlan.startDate)} - {formatDate(shiftPlan.endDate)}</div>
|
||||
<div>Status: <span style={{
|
||||
color: shiftPlan.status === 'published' ? '#2ecc71' : '#f1c40f',
|
||||
fontWeight: 'bold'
|
||||
}}>
|
||||
{shiftPlan.status === 'published' ? 'Veröffentlicht' : 'Entwurf'}
|
||||
</span></div>
|
||||
</div>
|
||||
|
||||
{/* Timetable */}
|
||||
<div style={{ marginTop: '30px' }}>
|
||||
<h3>Schichtplan</h3>
|
||||
|
||||
{timetableData.shifts.length === 0 ? (
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
padding: '40px',
|
||||
color: '#666',
|
||||
fontStyle: 'italic'
|
||||
}}>
|
||||
Keine Schichten für diesen Zeitraum konfiguriert
|
||||
</div>
|
||||
) : (
|
||||
<div style={{
|
||||
overflowX: 'auto',
|
||||
marginTop: '20px'
|
||||
}}>
|
||||
<table style={{
|
||||
width: '100%',
|
||||
borderCollapse: 'collapse',
|
||||
backgroundColor: 'white'
|
||||
}}>
|
||||
<thead>
|
||||
<tr style={{ backgroundColor: '#f8f9fa' }}>
|
||||
<th style={{
|
||||
padding: '12px 16px',
|
||||
textAlign: 'left',
|
||||
border: '1px solid #dee2e6',
|
||||
fontWeight: 'bold',
|
||||
minWidth: '200px'
|
||||
}}>
|
||||
Schicht (Zeit)
|
||||
</th>
|
||||
{timetableData.weekdays.map(weekday => (
|
||||
<th key={weekday.id} style={{
|
||||
padding: '12px 16px',
|
||||
textAlign: 'center',
|
||||
border: '1px solid #dee2e6',
|
||||
fontWeight: 'bold',
|
||||
minWidth: '80px'
|
||||
}}>
|
||||
{weekday.name}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{timetableData.shifts.map((shift, index) => (
|
||||
<tr key={index} style={{
|
||||
backgroundColor: index % 2 === 0 ? 'white' : '#f8f9fa'
|
||||
}}>
|
||||
<td style={{
|
||||
padding: '12px 16px',
|
||||
border: '1px solid #dee2e6',
|
||||
fontWeight: '500'
|
||||
}}>
|
||||
{shift.displayName}
|
||||
</td>
|
||||
{timetableData.weekdays.map(weekday => (
|
||||
<td key={weekday.id} style={{
|
||||
padding: '12px 16px',
|
||||
border: '1px solid #dee2e6',
|
||||
textAlign: 'center',
|
||||
color: shift.weekdayData[weekday.id] ? '#2c3e50' : '#bdc3c7'
|
||||
}}>
|
||||
{shift.weekdayData[weekday.id] || '–'}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Summary */}
|
||||
{timetableData.shifts.length > 0 && (
|
||||
<div style={{
|
||||
marginTop: '20px',
|
||||
padding: '12px 16px',
|
||||
backgroundColor: '#e8f4fd',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid #b8d4f0',
|
||||
fontSize: '14px'
|
||||
}}>
|
||||
<strong>Legende:</strong> Angezeigt wird "zugewiesene/benötigte Mitarbeiter" pro Schicht und Wochentag
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShiftPlanView;
|
||||
Reference in New Issue
Block a user