added shiftemplates

This commit is contained in:
2025-10-08 22:14:46 +02:00
parent bc15e644b8
commit ceb7058d0b
10 changed files with 737 additions and 171 deletions

View File

@@ -0,0 +1,97 @@
.editorContainer {
padding: 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
}
.title {
font-size: 24px;
color: #2c3e50;
margin: 0;
}
.buttons {
display: flex;
gap: 10px;
}
.previewButton {
padding: 8px 16px;
background-color: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.previewButton:hover {
background-color: #2980b9;
}
.saveButton {
padding: 8px 16px;
background-color: #2ecc71;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.saveButton:hover {
background-color: #27ae60;
}
.formGroup {
margin-bottom: 20px;
}
.formGroup label {
display: block;
margin-bottom: 8px;
color: #34495e;
font-weight: 500;
}
.formGroup input[type="text"],
.formGroup textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #bdc3c7;
border-radius: 4px;
font-size: 14px;
}
.formGroup textarea {
min-height: 100px;
resize: vertical;
}
.defaultCheckbox {
margin-top: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.defaultCheckbox input[type="checkbox"] {
width: 16px;
height: 16px;
}
.defaultCheckbox label {
margin: 0;
font-size: 14px;
}
.previewContainer {
margin-top: 30px;
border-top: 1px solid #ddd;
padding-top: 20px;
}

View File

@@ -4,8 +4,15 @@ import { useParams, useNavigate } from 'react-router-dom';
import { ShiftTemplate, TemplateShift, DEFAULT_DAYS } from '../../types/shiftTemplate';
import { shiftTemplateService } from '../../services/shiftTemplateService';
import ShiftDayEditor from './components/ShiftDayEditor';
import DefaultTemplateView from './components/DefaultTemplateView';
import styles from './ShiftTemplateEditor.module.css';
const defaultShift: Omit<TemplateShift, 'id'> = {
interface ExtendedTemplateShift extends Omit<TemplateShift, 'id'> {
id?: string;
isPreview?: boolean;
}
const defaultShift: ExtendedTemplateShift = {
dayOfWeek: 1, // Montag
name: '',
startTime: '08:00',
@@ -27,6 +34,7 @@ const ShiftTemplateEditor: React.FC = () => {
});
const [loading, setLoading] = useState(isEditing);
const [saving, setSaving] = useState(false);
const [showPreview, setShowPreview] = useState(false);
useEffect(() => {
if (isEditing) {
@@ -104,84 +112,93 @@ const ShiftTemplateEditor: React.FC = () => {
}));
};
// Preview-Daten für die DefaultTemplateView vorbereiten
const previewTemplate: ShiftTemplate = {
id: 'preview',
name: template.name || 'Vorschau',
description: template.description,
shifts: template.shifts.map(shift => ({
...shift,
id: shift.id || 'preview-' + Date.now()
})),
createdBy: 'preview',
createdAt: new Date().toISOString(),
isDefault: template.isDefault
};
if (loading) return <div>Lade Vorlage...</div>;
return (
<div style={{ padding: '20px', maxWidth: '1200px', margin: '0 auto' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '30px' }}>
<h1>{isEditing ? 'Vorlage bearbeiten' : 'Neue Vorlage erstellen'}</h1>
<div style={{ display: 'flex', gap: '10px' }}>
<div className={styles.editorContainer}>
<div className={styles.header}>
<h1 className={styles.title}>{isEditing ? 'Vorlage bearbeiten' : 'Neue Vorlage erstellen'}</h1>
<div className={styles.buttons}>
<button
onClick={() => navigate('/shift-templates')}
style={{ padding: '10px 20px', border: '1px solid #6c757d', background: 'white', color: '#6c757d' }}
className={styles.previewButton}
onClick={() => setShowPreview(!showPreview)}
>
Abbrechen
{showPreview ? 'Editor anzeigen' : 'Vorschau'}
</button>
<button
className={styles.saveButton}
onClick={handleSave}
disabled={saving}
style={{ padding: '10px 20px', backgroundColor: saving ? '#6c757d' : '#007bff', color: 'white', border: 'none' }}
>
{saving ? 'Speichern...' : 'Speichern'}
</button>
</div>
</div>
{/* Template Meta Information */}
<div style={{ marginBottom: '30px', padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
<div style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
Vorlagenname *
</label>
<input
type="text"
value={template.name}
onChange={(e) => setTemplate(prev => ({ ...prev, name: e.target.value }))}
style={{ width: '100%', padding: '8px', border: '1px solid #ccc', borderRadius: '4px' }}
placeholder="z.B. Standard Woche, Teilzeit Modell, etc."
/>
</div>
{showPreview ? (
<DefaultTemplateView template={previewTemplate} />
) : (
<>
<div className={styles.formGroup}>
<label>Vorlagenname *</label>
<input
type="text"
value={template.name}
onChange={(e) => setTemplate(prev => ({ ...prev, name: e.target.value }))}
placeholder="z.B. Standard Woche, Teilzeit Modell, etc."
/>
</div>
<div style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
Beschreibung
</label>
<textarea
value={template.description || ''}
onChange={(e) => setTemplate(prev => ({ ...prev, description: e.target.value }))}
style={{ width: '100%', padding: '8px', border: '1px solid #ccc', borderRadius: '4px', minHeight: '60px' }}
placeholder="Beschreibung der Vorlage (optional)"
/>
</div>
<div className={styles.formGroup}>
<label>Beschreibung</label>
<textarea
value={template.description || ''}
onChange={(e) => setTemplate(prev => ({ ...prev, description: e.target.value }))}
placeholder="Beschreibung der Vorlage (optional)"
/>
</div>
<div>
<label style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<div className={styles.defaultCheckbox}>
<input
type="checkbox"
id="isDefault"
checked={template.isDefault}
onChange={(e) => setTemplate(prev => ({ ...prev, isDefault: e.target.checked }))}
/>
Als Standardvorlage festlegen
</label>
</div>
</div>
<label htmlFor="isDefault">Als Standardvorlage festlegen</label>
</div>
{/* Schichten pro Tag */}
<div>
<h2 style={{ marginBottom: '20px' }}>Schichten pro Wochentag</h2>
<div style={{ display: 'grid', gap: '20px' }}>
{DEFAULT_DAYS.map(day => (
<ShiftDayEditor
key={day.id}
day={day}
shifts={template.shifts.filter(s => s.dayOfWeek === day.id)}
onAddShift={() => addShift(day.id)}
onUpdateShift={updateShift}
onRemoveShift={removeShift}
/>
))}
</div>
</div>
<div style={{ marginTop: '30px' }}>
<h2>Schichten pro Wochentag</h2>
<div style={{ display: 'grid', gap: '20px', marginTop: '20px' }}>
{DEFAULT_DAYS.map(day => (
<ShiftDayEditor
key={day.id}
day={day}
shifts={template.shifts.filter(s => s.dayOfWeek === day.id)}
onAddShift={() => addShift(day.id)}
onUpdateShift={updateShift}
onRemoveShift={removeShift}
/>
))}
</div>
</div>
</>
)}
</div>
);
};

View File

@@ -0,0 +1,101 @@
.templateList {
padding: 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.createButton {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.createButton:hover {
background-color: #0056b3;
}
.templateGrid {
display: grid;
gap: 15px;
}
.templateCard {
border: 1px solid #ddd;
padding: 15px;
border-radius: 8px;
background-color: #f9f9f9;
}
.templateHeader {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.templateInfo h3 {
margin: 0 0 5px 0;
}
.templateInfo p {
margin: 0 0 10px 0;
color: #666;
}
.templateMeta {
font-size: 14px;
color: #888;
}
.defaultBadge {
color: green;
margin-left: 10px;
}
.actionButtons {
display: flex;
gap: 10px;
}
.viewButton {
padding: 5px 10px;
border: 1px solid #007bff;
color: #007bff;
background: white;
cursor: pointer;
}
.useButton {
padding: 5px 10px;
background-color: #28a745;
color: white;
border: none;
cursor: pointer;
}
.deleteButton {
padding: 5px 10px;
background-color: #dc3545;
color: white;
border: none;
cursor: pointer;
}
.viewButton:hover {
background-color: #e6f0ff;
}
.useButton:hover {
background-color: #218838;
}
.deleteButton:hover {
background-color: #c82333;
}

View File

@@ -4,11 +4,14 @@ import { Link } from 'react-router-dom';
import { ShiftTemplate } from '../../types/shiftTemplate';
import { shiftTemplateService } from '../../services/shiftTemplateService';
import { useAuth } from '../../contexts/AuthContext';
import DefaultTemplateView from './components/DefaultTemplateView';
import styles from './ShiftTemplateList.module.css';
const ShiftTemplateList: React.FC = () => {
const [templates, setTemplates] = useState<ShiftTemplate[]>([]);
const [loading, setLoading] = useState(true);
const { hasRole } = useAuth();
const [selectedTemplate, setSelectedTemplate] = useState<ShiftTemplate | null>(null);
useEffect(() => {
loadTemplates();
@@ -18,6 +21,11 @@ const ShiftTemplateList: React.FC = () => {
try {
const data = await shiftTemplateService.getTemplates();
setTemplates(data);
// Setze die Standard-Vorlage als ausgewählt
const defaultTemplate = data.find(t => t.isDefault);
if (defaultTemplate) {
setSelectedTemplate(defaultTemplate);
}
} catch (error) {
console.error('Fehler:', error);
} finally {
@@ -31,6 +39,9 @@ const ShiftTemplateList: React.FC = () => {
try {
await shiftTemplateService.deleteTemplate(id);
setTemplates(templates.filter(t => t.id !== id));
if (selectedTemplate?.id === id) {
setSelectedTemplate(null);
}
} catch (error) {
console.error('Löschen fehlgeschlagen:', error);
}
@@ -39,78 +50,85 @@ const ShiftTemplateList: React.FC = () => {
if (loading) return <div>Lade Vorlagen...</div>;
return (
<div style={{ padding: '20px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
<div className={styles.templateList}>
<div className={styles.header}>
<h1>Schichtplan Vorlagen</h1>
{hasRole(['admin', 'instandhalter']) && (
<Link to="/shift-templates/new">
<button style={{ padding: '10px 20px', backgroundColor: '#007bff', color: 'white', border: 'none', borderRadius: '4px' }}>
<button className={styles.createButton}>
Neue Vorlage
</button>
</Link>
)}
</div>
<div style={{ display: 'grid', gap: '15px' }}>
{templates.map(template => (
<div key={template.id} style={{
border: '1px solid #ddd',
padding: '15px',
borderRadius: '8px',
backgroundColor: '#f9f9f9'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<h3 style={{ margin: '0 0 5px 0' }}>{template.name}</h3>
{template.description && (
<p style={{ margin: '0 0 10px 0', color: '#666' }}>{template.description}</p>
)}
<div style={{ fontSize: '14px', color: '#888' }}>
{template.shifts.length} Schichttypen Erstellt am {new Date(template.createdAt).toLocaleDateString('de-DE')}
{template.isDefault && <span style={{ color: 'green', marginLeft: '10px' }}> Standard</span>}
</div>
</div>
<div style={{ display: 'flex', gap: '10px' }}>
<Link to={`/shift-templates/${template.id}`}>
<button style={{ padding: '5px 10px', border: '1px solid #007bff', color: '#007bff', background: 'white' }}>
{hasRole(['admin', 'instandhalter']) ? 'Bearbeiten' : 'Ansehen'}
</button>
</Link>
{hasRole(['admin', 'instandhalter']) && (
<>
<Link to={`/shift-plans/new?template=${template.id}`}>
<button style={{ padding: '5px 10px', backgroundColor: '#28a745', color: 'white', border: 'none' }}>
Verwenden
</button>
</Link>
<button
onClick={() => handleDelete(template.id)}
style={{ padding: '5px 10px', backgroundColor: '#dc3545', color: 'white', border: 'none' }}
>
Löschen
</button>
</>
)}
</div>
</div>
</div>
))}
{templates.length === 0 && (
<div className={styles.templateGrid}>
{templates.length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
<p>Noch keine Vorlagen vorhanden.</p>
{hasRole(['admin', 'instandhalter']) && (
<Link to="/shift-templates/new">
<button style={{ padding: '10px 20px', backgroundColor: '#007bff', color: 'white', border: 'none', borderRadius: '4px' }}>
<button className={styles.createButton}>
Erste Vorlage erstellen
</button>
</Link>
)}
</div>
) : (
templates.map(template => (
<div key={template.id} className={styles.templateCard}>
<div className={styles.templateHeader}>
<div className={styles.templateInfo}>
<h3>{template.name}</h3>
{template.description && (
<p>{template.description}</p>
)}
<div className={styles.templateMeta}>
{template.shifts.length} Schichttypen Erstellt am {new Date(template.createdAt).toLocaleDateString('de-DE')}
{template.isDefault && <span className={styles.defaultBadge}> Standard</span>}
</div>
</div>
<div className={styles.actionButtons}>
<button
className={styles.viewButton}
onClick={() => setSelectedTemplate(template)}
>
Vorschau
</button>
{hasRole(['admin', 'instandhalter']) && (
<>
<Link to={`/shift-templates/${template.id}`}>
<button className={styles.viewButton}>
Bearbeiten
</button>
</Link>
<Link to={`/shift-plans/new?template=${template.id}`}>
<button className={styles.useButton}>
Verwenden
</button>
</Link>
<button
onClick={() => handleDelete(template.id)}
className={styles.deleteButton}
>
Löschen
</button>
</>
)}
</div>
</div>
</div>
))
)}
</div>
{selectedTemplate && (
<div style={{ marginTop: '30px' }}>
<DefaultTemplateView template={selectedTemplate} />
</div>
)}
</div>
);
};

View File

@@ -0,0 +1,48 @@
.defaultTemplateView {
padding: 20px;
}
.weekView {
display: flex;
gap: 20px;
margin-top: 20px;
overflow-x: auto;
}
.dayColumn {
min-width: 200px;
background: #f5f6fa;
padding: 15px;
border-radius: 8px;
}
.dayColumn h3 {
margin: 0 0 15px 0;
color: #2c3e50;
text-align: center;
}
.shiftsContainer {
display: flex;
flex-direction: column;
gap: 10px;
}
.shiftCard {
background: white;
padding: 12px;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.shiftCard h4 {
margin: 0 0 8px 0;
color: #34495e;
font-size: 0.9em;
}
.shiftCard p {
margin: 0;
color: #7f8c8d;
font-size: 0.85em;
}

View File

@@ -0,0 +1,55 @@
// frontend/src/pages/ShiftTemplates/components/DefaultTemplateView.tsx
import React from 'react';
import { ShiftTemplate } from '../../../types/shiftTemplate';
import styles from './DefaultTemplateView.module.css';
interface DefaultTemplateViewProps {
template: ShiftTemplate;
}
const DefaultTemplateView: React.FC<DefaultTemplateViewProps> = ({ template }) => {
// Gruppiere Schichten nach Wochentag
const shiftsByDay = template.shifts.reduce((acc, shift) => {
const day = shift.dayOfWeek;
if (!acc[day]) {
acc[day] = [];
}
acc[day].push(shift);
return acc;
}, {} as Record<number, typeof template.shifts>);
// Funktion zum Formatieren der Zeit
const formatTime = (time: string) => {
return time.substring(0, 5); // Zeigt nur HH:MM
};
// Wochentagsnamen
const dayNames = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
return (
<div className={styles.defaultTemplateView}>
<h2>{template.name}</h2>
{template.description && <p>{template.description}</p>}
<div className={styles.weekView}>
{[1, 2, 3, 4, 5].map(dayIndex => (
<div key={dayIndex} className={styles.dayColumn}>
<h3>{dayNames[dayIndex]}</h3>
<div className={styles.shiftsContainer}>
{shiftsByDay[dayIndex]?.map(shift => (
<div key={shift.id} className={styles.shiftCard}>
<h4>{shift.name}</h4>
<p>
{formatTime(shift.startTime)} - {formatTime(shift.endTime)}
</p>
</div>
))}
</div>
</div>
))}
</div>
</div>
);
};
export default DefaultTemplateView;

View File

@@ -0,0 +1,137 @@
.dayEditor {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.dayHeader {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.dayName {
font-size: 18px;
color: #2c3e50;
margin: 0;
}
.addButton {
padding: 6px 12px;
background-color: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
gap: 6px;
}
.addButton:hover {
background-color: #2980b9;
}
.addButton svg {
width: 16px;
height: 16px;
}
.shiftsGrid {
display: grid;
gap: 15px;
}
.shiftCard {
background: white;
padding: 15px;
border-radius: 6px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.shiftHeader {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.shiftTitle {
color: #2c3e50;
font-size: 16px;
font-weight: 500;
margin: 0;
}
.deleteButton {
padding: 4px 8px;
background-color: #e74c3c;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.deleteButton:hover {
background-color: #c0392b;
}
.formGroup {
margin-bottom: 12px;
}
.formGroup label {
display: block;
margin-bottom: 4px;
color: #34495e;
font-size: 14px;
}
.formGroup input {
width: 100%;
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.formGroup input[type="time"] {
width: auto;
}
.timeInputs {
display: flex;
gap: 15px;
align-items: center;
}
.colorPicker {
width: 100px;
}
.requiredEmployees {
display: flex;
gap: 10px;
align-items: center;
}
.requiredEmployees input {
width: 60px;
text-align: center;
}
.requiredEmployees button {
padding: 4px 8px;
border: 1px solid #ddd;
background: white;
border-radius: 4px;
cursor: pointer;
}
.requiredEmployees button:hover {
background: #f5f6fa;
}

View File

@@ -1,6 +1,7 @@
// frontend/src/pages/ShiftTemplates/components/ShiftDayEditor.tsx
import React from 'react';
import { TemplateShift } from '../../../types/shiftTemplate';
import styles from './ShiftDayEditor.module.css';
interface ShiftDayEditorProps {
day: { id: number; name: string };
@@ -18,13 +19,13 @@ const ShiftDayEditor: React.FC<ShiftDayEditorProps> = ({
onRemoveShift
}) => {
return (
<div style={{ border: '1px solid #e0e0e0', borderRadius: '8px', padding: '20px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}>
<h3 style={{ margin: 0 }}>{day.name}</h3>
<button
onClick={onAddShift}
style={{ padding: '8px 16px', backgroundColor: '#28a745', color: 'white', border: 'none', borderRadius: '4px' }}
>
<div className={styles.dayEditor}>
<div className={styles.dayHeader}>
<h3 className={styles.dayName}>{day.name}</h3>
<button className={styles.addButton} onClick={onAddShift}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="16" height="16">
<path fillRule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clipRule="evenodd" />
</svg>
Schicht hinzufügen
</button>
</div>
@@ -34,73 +35,85 @@ const ShiftDayEditor: React.FC<ShiftDayEditorProps> = ({
Keine Schichten für {day.name}
</div>
) : (
<div style={{ display: 'grid', gap: '15px' }}>
{shifts.map((shift, index) => (
<div key={shift.id} style={{
display: 'grid',
gridTemplateColumns: '2fr 1fr 1fr 1fr auto',
gap: '10px',
alignItems: 'center',
padding: '15px',
border: '1px solid #f0f0f0',
borderRadius: '4px',
backgroundColor: '#fafafa'
}}>
{/* Schicht Name */}
<div>
<div className={styles.shiftsGrid}>
{shifts.map(shift => (
<div key={shift.id} className={styles.shiftCard}>
<div className={styles.shiftHeader}>
<h4 className={styles.shiftTitle}>Schicht bearbeiten</h4>
<button
className={styles.deleteButton}
onClick={() => onRemoveShift(shift.id)}
title="Schicht löschen"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="16" height="16">
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
Löschen
</button>
</div>
<div className={styles.formGroup}>
<input
type="text"
value={shift.name}
onChange={(e) => onUpdateShift(shift.id, { name: e.target.value })}
placeholder="Schichtname"
style={{ width: '100%', padding: '8px', border: '1px solid #ccc', borderRadius: '4px' }}
/>
</div>
{/* Startzeit */}
<div>
<label style={{ fontSize: '12px', display: 'block', marginBottom: '2px' }}>Start</label>
<input
type="time"
value={shift.startTime}
onChange={(e) => onUpdateShift(shift.id, { startTime: e.target.value })}
style={{ width: '100%', padding: '8px', border: '1px solid #ccc', borderRadius: '4px' }}
/>
<div className={styles.timeInputs}>
<div className={styles.formGroup}>
<label>Start</label>
<input
type="time"
value={shift.startTime}
onChange={(e) => onUpdateShift(shift.id, { startTime: e.target.value })}
/>
</div>
<div className={styles.formGroup}>
<label>Ende</label>
<input
type="time"
value={shift.endTime}
onChange={(e) => onUpdateShift(shift.id, { endTime: e.target.value })}
/>
</div>
</div>
{/* Endzeit */}
<div>
<label style={{ fontSize: '12px', display: 'block', marginBottom: '2px' }}>Ende</label>
<input
type="time"
value={shift.endTime}
onChange={(e) => onUpdateShift(shift.id, { endTime: e.target.value })}
style={{ width: '100%', padding: '8px', border: '1px solid #ccc', borderRadius: '4px' }}
/>
<div className={styles.formGroup}>
<label>Benötigte Mitarbeiter</label>
<div className={styles.requiredEmployees}>
<button
onClick={() => onUpdateShift(shift.id, { requiredEmployees: Math.max(1, shift.requiredEmployees - 1) })}
>
-
</button>
<input
type="number"
min="1"
value={shift.requiredEmployees}
onChange={(e) => onUpdateShift(shift.id, { requiredEmployees: parseInt(e.target.value) || 1 })}
/>
<button
onClick={() => onUpdateShift(shift.id, { requiredEmployees: shift.requiredEmployees + 1 })}
>
+
</button>
</div>
</div>
{/* Benötigte Mitarbeiter */}
<div>
<label style={{ fontSize: '12px', display: 'block', marginBottom: '2px' }}>Mitarbeiter</label>
<input
type="number"
min="1"
value={shift.requiredEmployees}
onChange={(e) => onUpdateShift(shift.id, { requiredEmployees: parseInt(e.target.value) || 1 })}
style={{ width: '100%', padding: '8px', border: '1px solid #ccc', borderRadius: '4px' }}
/>
</div>
{/* Löschen Button */}
<div>
<button
onClick={() => onRemoveShift(shift.id)}
style={{ padding: '8px 12px', backgroundColor: '#dc3545', color: 'white', border: 'none', borderRadius: '4px' }}
title="Schicht löschen"
>
×
</button>
</div>
{shift.color && (
<div className={styles.formGroup}>
<label>Farbe</label>
<input
type="color"
value={shift.color}
onChange={(e) => onUpdateShift(shift.id, { color: e.target.value })}
className={styles.colorPicker}
/>
</div>
)}
</div>
))}
</div>

View File

@@ -21,7 +21,13 @@ export const shiftTemplateService = {
throw new Error('Fehler beim Laden der Vorlagen');
}
return response.json();
const templates = await response.json();
// Sortiere die Vorlagen so, dass die Standard-Vorlage immer zuerst kommt
return templates.sort((a: ShiftTemplate, b: ShiftTemplate) => {
if (a.isDefault && !b.isDefault) return -1;
if (!a.isDefault && b.isDefault) return 1;
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
});
},
async getTemplate(id: string): Promise<ShiftTemplate> {
@@ -44,6 +50,17 @@ export const shiftTemplateService = {
},
async createTemplate(template: Omit<ShiftTemplate, 'id' | 'createdAt' | 'createdBy'>): Promise<ShiftTemplate> {
// Wenn diese Vorlage als Standard markiert ist,
// fragen wir den Benutzer, ob er wirklich die Standard-Vorlage ändern möchte
if (template.isDefault) {
const confirm = window.confirm(
'Diese Vorlage wird als neue Standard-Vorlage festgelegt. Die bisherige Standard-Vorlage wird dadurch zu einer normalen Vorlage. Möchten Sie fortfahren?'
);
if (!confirm) {
throw new Error('Operation abgebrochen');
}
}
const response = await fetch(API_BASE, {
method: 'POST',
headers: {