mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 15:05:45 +01:00
added shiftemplates
This commit is contained in:
@@ -86,6 +86,57 @@ export const getTemplate = async (req: Request, res: Response): Promise<void> =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createDefaultTemplate = async (userId: string): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const templateId = uuidv4();
|
||||||
|
|
||||||
|
await db.run('BEGIN TRANSACTION');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Erstelle die Standard-Vorlage
|
||||||
|
await db.run(
|
||||||
|
`INSERT INTO shift_templates (id, name, description, is_default, created_by)
|
||||||
|
VALUES (?, ?, ?, ?, ?)`,
|
||||||
|
[templateId, 'Standardwoche', 'Mo-Do: 2 Schichten, Fr: 1 Schicht', true, userId]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Vormittagsschicht Mo-Do
|
||||||
|
for (let day = 1; day <= 4; day++) {
|
||||||
|
await db.run(
|
||||||
|
`INSERT INTO template_shifts (id, template_id, day_of_week, name, start_time, end_time, required_employees)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
[uuidv4(), templateId, day, 'Vormittagsschicht', '08:00', '12:00', 1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nachmittagsschicht Mo-Do
|
||||||
|
for (let day = 1; day <= 4; day++) {
|
||||||
|
await db.run(
|
||||||
|
`INSERT INTO template_shifts (id, template_id, day_of_week, name, start_time, end_time, required_employees)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
[uuidv4(), templateId, day, 'Nachmittagsschicht', '11:30', '15:30', 1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Freitag nur Vormittagsschicht
|
||||||
|
await db.run(
|
||||||
|
`INSERT INTO template_shifts (id, template_id, day_of_week, name, start_time, end_time, required_employees)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
[uuidv4(), templateId, 5, 'Vormittagsschicht', '08:00', '12:00', 1]
|
||||||
|
);
|
||||||
|
|
||||||
|
await db.run('COMMIT');
|
||||||
|
return templateId;
|
||||||
|
} catch (error) {
|
||||||
|
await db.run('ROLLBACK');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating default template:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const createTemplate = async (req: Request, res: Response): Promise<void> => {
|
export const createTemplate = async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { name, description, isDefault, shifts }: CreateShiftTemplateRequest = req.body;
|
const { name, description, isDefault, shifts }: CreateShiftTemplateRequest = req.body;
|
||||||
@@ -96,6 +147,12 @@ export const createTemplate = async (req: Request, res: Response): Promise<void>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wenn diese Vorlage als Standard markiert werden soll,
|
||||||
|
// zuerst alle anderen Vorlagen auf nicht-Standard setzen
|
||||||
|
if (isDefault) {
|
||||||
|
await db.run('UPDATE shift_templates SET is_default = 0');
|
||||||
|
}
|
||||||
|
|
||||||
const templateId = uuidv4();
|
const templateId = uuidv4();
|
||||||
|
|
||||||
// Start transaction
|
// Start transaction
|
||||||
@@ -182,6 +239,12 @@ export const updateTemplate = async (req: Request, res: Response): Promise<void>
|
|||||||
await db.run('BEGIN TRANSACTION');
|
await db.run('BEGIN TRANSACTION');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Wenn diese Vorlage als Standard markiert werden soll,
|
||||||
|
// zuerst alle anderen Vorlagen auf nicht-Standard setzen
|
||||||
|
if (isDefault) {
|
||||||
|
await db.run('UPDATE shift_templates SET is_default = 0');
|
||||||
|
}
|
||||||
|
|
||||||
// Update template
|
// Update template
|
||||||
if (name !== undefined || description !== undefined || isDefault !== undefined) {
|
if (name !== undefined || description !== undefined || isDefault !== undefined) {
|
||||||
await db.run(
|
await db.run(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -4,8 +4,15 @@ import { useParams, useNavigate } from 'react-router-dom';
|
|||||||
import { ShiftTemplate, TemplateShift, DEFAULT_DAYS } from '../../types/shiftTemplate';
|
import { ShiftTemplate, TemplateShift, DEFAULT_DAYS } from '../../types/shiftTemplate';
|
||||||
import { shiftTemplateService } from '../../services/shiftTemplateService';
|
import { shiftTemplateService } from '../../services/shiftTemplateService';
|
||||||
import ShiftDayEditor from './components/ShiftDayEditor';
|
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
|
dayOfWeek: 1, // Montag
|
||||||
name: '',
|
name: '',
|
||||||
startTime: '08:00',
|
startTime: '08:00',
|
||||||
@@ -27,6 +34,7 @@ const ShiftTemplateEditor: React.FC = () => {
|
|||||||
});
|
});
|
||||||
const [loading, setLoading] = useState(isEditing);
|
const [loading, setLoading] = useState(isEditing);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [showPreview, setShowPreview] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
@@ -104,72 +112,79 @@ 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>;
|
if (loading) return <div>Lade Vorlage...</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '20px', maxWidth: '1200px', margin: '0 auto' }}>
|
<div className={styles.editorContainer}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '30px' }}>
|
<div className={styles.header}>
|
||||||
<h1>{isEditing ? 'Vorlage bearbeiten' : 'Neue Vorlage erstellen'}</h1>
|
<h1 className={styles.title}>{isEditing ? 'Vorlage bearbeiten' : 'Neue Vorlage erstellen'}</h1>
|
||||||
<div style={{ display: 'flex', gap: '10px' }}>
|
<div className={styles.buttons}>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/shift-templates')}
|
className={styles.previewButton}
|
||||||
style={{ padding: '10px 20px', border: '1px solid #6c757d', background: 'white', color: '#6c757d' }}
|
onClick={() => setShowPreview(!showPreview)}
|
||||||
>
|
>
|
||||||
Abbrechen
|
{showPreview ? 'Editor anzeigen' : 'Vorschau'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
className={styles.saveButton}
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
style={{ padding: '10px 20px', backgroundColor: saving ? '#6c757d' : '#007bff', color: 'white', border: 'none' }}
|
|
||||||
>
|
>
|
||||||
{saving ? 'Speichern...' : 'Speichern'}
|
{saving ? 'Speichern...' : 'Speichern'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Template Meta Information */}
|
{showPreview ? (
|
||||||
<div style={{ marginBottom: '30px', padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
|
<DefaultTemplateView template={previewTemplate} />
|
||||||
<div style={{ marginBottom: '15px' }}>
|
) : (
|
||||||
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
|
<>
|
||||||
Vorlagenname *
|
<div className={styles.formGroup}>
|
||||||
</label>
|
<label>Vorlagenname *</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={template.name}
|
value={template.name}
|
||||||
onChange={(e) => setTemplate(prev => ({ ...prev, name: e.target.value }))}
|
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."
|
placeholder="z.B. Standard Woche, Teilzeit Modell, etc."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: '15px' }}>
|
<div className={styles.formGroup}>
|
||||||
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
|
<label>Beschreibung</label>
|
||||||
Beschreibung
|
|
||||||
</label>
|
|
||||||
<textarea
|
<textarea
|
||||||
value={template.description || ''}
|
value={template.description || ''}
|
||||||
onChange={(e) => setTemplate(prev => ({ ...prev, description: e.target.value }))}
|
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)"
|
placeholder="Beschreibung der Vorlage (optional)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className={styles.defaultCheckbox}>
|
||||||
<label style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
id="isDefault"
|
||||||
checked={template.isDefault}
|
checked={template.isDefault}
|
||||||
onChange={(e) => setTemplate(prev => ({ ...prev, isDefault: e.target.checked }))}
|
onChange={(e) => setTemplate(prev => ({ ...prev, isDefault: e.target.checked }))}
|
||||||
/>
|
/>
|
||||||
Als Standardvorlage festlegen
|
<label htmlFor="isDefault">Als Standardvorlage festlegen</label>
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Schichten pro Tag */}
|
<div style={{ marginTop: '30px' }}>
|
||||||
<div>
|
<h2>Schichten pro Wochentag</h2>
|
||||||
<h2 style={{ marginBottom: '20px' }}>Schichten pro Wochentag</h2>
|
<div style={{ display: 'grid', gap: '20px', marginTop: '20px' }}>
|
||||||
<div style={{ display: 'grid', gap: '20px' }}>
|
|
||||||
{DEFAULT_DAYS.map(day => (
|
{DEFAULT_DAYS.map(day => (
|
||||||
<ShiftDayEditor
|
<ShiftDayEditor
|
||||||
key={day.id}
|
key={day.id}
|
||||||
@@ -182,6 +197,8 @@ const ShiftTemplateEditor: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
101
frontend/src/pages/ShiftTemplates/ShiftTemplateList.module.css
Normal file
101
frontend/src/pages/ShiftTemplates/ShiftTemplateList.module.css
Normal 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;
|
||||||
|
}
|
||||||
@@ -4,11 +4,14 @@ import { Link } from 'react-router-dom';
|
|||||||
import { ShiftTemplate } from '../../types/shiftTemplate';
|
import { ShiftTemplate } from '../../types/shiftTemplate';
|
||||||
import { shiftTemplateService } from '../../services/shiftTemplateService';
|
import { shiftTemplateService } from '../../services/shiftTemplateService';
|
||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
|
import DefaultTemplateView from './components/DefaultTemplateView';
|
||||||
|
import styles from './ShiftTemplateList.module.css';
|
||||||
|
|
||||||
const ShiftTemplateList: React.FC = () => {
|
const ShiftTemplateList: React.FC = () => {
|
||||||
const [templates, setTemplates] = useState<ShiftTemplate[]>([]);
|
const [templates, setTemplates] = useState<ShiftTemplate[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const { hasRole } = useAuth();
|
const { hasRole } = useAuth();
|
||||||
|
const [selectedTemplate, setSelectedTemplate] = useState<ShiftTemplate | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadTemplates();
|
loadTemplates();
|
||||||
@@ -18,6 +21,11 @@ const ShiftTemplateList: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const data = await shiftTemplateService.getTemplates();
|
const data = await shiftTemplateService.getTemplates();
|
||||||
setTemplates(data);
|
setTemplates(data);
|
||||||
|
// Setze die Standard-Vorlage als ausgewählt
|
||||||
|
const defaultTemplate = data.find(t => t.isDefault);
|
||||||
|
if (defaultTemplate) {
|
||||||
|
setSelectedTemplate(defaultTemplate);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler:', error);
|
console.error('Fehler:', error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -31,6 +39,9 @@ const ShiftTemplateList: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
await shiftTemplateService.deleteTemplate(id);
|
await shiftTemplateService.deleteTemplate(id);
|
||||||
setTemplates(templates.filter(t => t.id !== id));
|
setTemplates(templates.filter(t => t.id !== id));
|
||||||
|
if (selectedTemplate?.id === id) {
|
||||||
|
setSelectedTemplate(null);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Löschen fehlgeschlagen:', error);
|
console.error('Löschen fehlgeschlagen:', error);
|
||||||
}
|
}
|
||||||
@@ -39,55 +50,68 @@ const ShiftTemplateList: React.FC = () => {
|
|||||||
if (loading) return <div>Lade Vorlagen...</div>;
|
if (loading) return <div>Lade Vorlagen...</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '20px' }}>
|
<div className={styles.templateList}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
|
<div className={styles.header}>
|
||||||
<h1>Schichtplan Vorlagen</h1>
|
<h1>Schichtplan Vorlagen</h1>
|
||||||
{hasRole(['admin', 'instandhalter']) && (
|
{hasRole(['admin', 'instandhalter']) && (
|
||||||
<Link to="/shift-templates/new">
|
<Link to="/shift-templates/new">
|
||||||
<button style={{ padding: '10px 20px', backgroundColor: '#007bff', color: 'white', border: 'none', borderRadius: '4px' }}>
|
<button className={styles.createButton}>
|
||||||
Neue Vorlage
|
Neue Vorlage
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'grid', gap: '15px' }}>
|
<div className={styles.templateGrid}>
|
||||||
{templates.map(template => (
|
{templates.length === 0 ? (
|
||||||
<div key={template.id} style={{
|
<div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
|
||||||
border: '1px solid #ddd',
|
<p>Noch keine Vorlagen vorhanden.</p>
|
||||||
padding: '15px',
|
{hasRole(['admin', 'instandhalter']) && (
|
||||||
borderRadius: '8px',
|
<Link to="/shift-templates/new">
|
||||||
backgroundColor: '#f9f9f9'
|
<button className={styles.createButton}>
|
||||||
}}>
|
Erste Vorlage erstellen
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
</button>
|
||||||
<div>
|
</Link>
|
||||||
<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' }}>
|
</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.shifts.length} Schichttypen • Erstellt am {new Date(template.createdAt).toLocaleDateString('de-DE')}
|
||||||
{template.isDefault && <span style={{ color: 'green', marginLeft: '10px' }}>• Standard</span>}
|
{template.isDefault && <span className={styles.defaultBadge}>• Standard</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: '10px' }}>
|
<div className={styles.actionButtons}>
|
||||||
<Link to={`/shift-templates/${template.id}`}>
|
<button
|
||||||
<button style={{ padding: '5px 10px', border: '1px solid #007bff', color: '#007bff', background: 'white' }}>
|
className={styles.viewButton}
|
||||||
{hasRole(['admin', 'instandhalter']) ? 'Bearbeiten' : 'Ansehen'}
|
onClick={() => setSelectedTemplate(template)}
|
||||||
|
>
|
||||||
|
Vorschau
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
|
||||||
|
|
||||||
{hasRole(['admin', 'instandhalter']) && (
|
{hasRole(['admin', 'instandhalter']) && (
|
||||||
<>
|
<>
|
||||||
|
<Link to={`/shift-templates/${template.id}`}>
|
||||||
|
<button className={styles.viewButton}>
|
||||||
|
Bearbeiten
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
<Link to={`/shift-plans/new?template=${template.id}`}>
|
<Link to={`/shift-plans/new?template=${template.id}`}>
|
||||||
<button style={{ padding: '5px 10px', backgroundColor: '#28a745', color: 'white', border: 'none' }}>
|
<button className={styles.useButton}>
|
||||||
Verwenden
|
Verwenden
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDelete(template.id)}
|
onClick={() => handleDelete(template.id)}
|
||||||
style={{ padding: '5px 10px', backgroundColor: '#dc3545', color: 'white', border: 'none' }}
|
className={styles.deleteButton}
|
||||||
>
|
>
|
||||||
Löschen
|
Löschen
|
||||||
</button>
|
</button>
|
||||||
@@ -96,22 +120,16 @@ const ShiftTemplateList: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{templates.length === 0 && (
|
{selectedTemplate && (
|
||||||
<div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
|
<div style={{ marginTop: '30px' }}>
|
||||||
<p>Noch keine Vorlagen vorhanden.</p>
|
<DefaultTemplateView template={selectedTemplate} />
|
||||||
{hasRole(['admin', 'instandhalter']) && (
|
|
||||||
<Link to="/shift-templates/new">
|
|
||||||
<button style={{ padding: '10px 20px', backgroundColor: '#007bff', color: 'white', border: 'none', borderRadius: '4px' }}>
|
|
||||||
Erste Vorlage erstellen
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// frontend/src/pages/ShiftTemplates/components/ShiftDayEditor.tsx
|
// frontend/src/pages/ShiftTemplates/components/ShiftDayEditor.tsx
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { TemplateShift } from '../../../types/shiftTemplate';
|
import { TemplateShift } from '../../../types/shiftTemplate';
|
||||||
|
import styles from './ShiftDayEditor.module.css';
|
||||||
|
|
||||||
interface ShiftDayEditorProps {
|
interface ShiftDayEditorProps {
|
||||||
day: { id: number; name: string };
|
day: { id: number; name: string };
|
||||||
@@ -18,13 +19,13 @@ const ShiftDayEditor: React.FC<ShiftDayEditorProps> = ({
|
|||||||
onRemoveShift
|
onRemoveShift
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ border: '1px solid #e0e0e0', borderRadius: '8px', padding: '20px' }}>
|
<div className={styles.dayEditor}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}>
|
<div className={styles.dayHeader}>
|
||||||
<h3 style={{ margin: 0 }}>{day.name}</h3>
|
<h3 className={styles.dayName}>{day.name}</h3>
|
||||||
<button
|
<button className={styles.addButton} onClick={onAddShift}>
|
||||||
onClick={onAddShift}
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="16" height="16">
|
||||||
style={{ padding: '8px 16px', backgroundColor: '#28a745', color: 'white', border: 'none', borderRadius: '4px' }}
|
<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
|
Schicht hinzufügen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,74 +35,86 @@ const ShiftDayEditor: React.FC<ShiftDayEditorProps> = ({
|
|||||||
Keine Schichten für {day.name}
|
Keine Schichten für {day.name}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ display: 'grid', gap: '15px' }}>
|
<div className={styles.shiftsGrid}>
|
||||||
{shifts.map((shift, index) => (
|
{shifts.map(shift => (
|
||||||
<div key={shift.id} style={{
|
<div key={shift.id} className={styles.shiftCard}>
|
||||||
display: 'grid',
|
<div className={styles.shiftHeader}>
|
||||||
gridTemplateColumns: '2fr 1fr 1fr 1fr auto',
|
<h4 className={styles.shiftTitle}>Schicht bearbeiten</h4>
|
||||||
gap: '10px',
|
<button
|
||||||
alignItems: 'center',
|
className={styles.deleteButton}
|
||||||
padding: '15px',
|
onClick={() => onRemoveShift(shift.id)}
|
||||||
border: '1px solid #f0f0f0',
|
title="Schicht löschen"
|
||||||
borderRadius: '4px',
|
>
|
||||||
backgroundColor: '#fafafa'
|
<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" />
|
||||||
{/* Schicht Name */}
|
</svg>
|
||||||
<div>
|
Löschen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.formGroup}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={shift.name}
|
value={shift.name}
|
||||||
onChange={(e) => onUpdateShift(shift.id, { name: e.target.value })}
|
onChange={(e) => onUpdateShift(shift.id, { name: e.target.value })}
|
||||||
placeholder="Schichtname"
|
placeholder="Schichtname"
|
||||||
style={{ width: '100%', padding: '8px', border: '1px solid #ccc', borderRadius: '4px' }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Startzeit */}
|
<div className={styles.timeInputs}>
|
||||||
<div>
|
<div className={styles.formGroup}>
|
||||||
<label style={{ fontSize: '12px', display: 'block', marginBottom: '2px' }}>Start</label>
|
<label>Start</label>
|
||||||
<input
|
<input
|
||||||
type="time"
|
type="time"
|
||||||
value={shift.startTime}
|
value={shift.startTime}
|
||||||
onChange={(e) => onUpdateShift(shift.id, { startTime: e.target.value })}
|
onChange={(e) => onUpdateShift(shift.id, { startTime: e.target.value })}
|
||||||
style={{ width: '100%', padding: '8px', border: '1px solid #ccc', borderRadius: '4px' }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Endzeit */}
|
<div className={styles.formGroup}>
|
||||||
<div>
|
<label>Ende</label>
|
||||||
<label style={{ fontSize: '12px', display: 'block', marginBottom: '2px' }}>Ende</label>
|
|
||||||
<input
|
<input
|
||||||
type="time"
|
type="time"
|
||||||
value={shift.endTime}
|
value={shift.endTime}
|
||||||
onChange={(e) => onUpdateShift(shift.id, { endTime: e.target.value })}
|
onChange={(e) => onUpdateShift(shift.id, { endTime: e.target.value })}
|
||||||
style={{ width: '100%', padding: '8px', border: '1px solid #ccc', borderRadius: '4px' }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Benötigte Mitarbeiter */}
|
<div className={styles.formGroup}>
|
||||||
<div>
|
<label>Benötigte Mitarbeiter</label>
|
||||||
<label style={{ fontSize: '12px', display: 'block', marginBottom: '2px' }}>Mitarbeiter</label>
|
<div className={styles.requiredEmployees}>
|
||||||
|
<button
|
||||||
|
onClick={() => onUpdateShift(shift.id, { requiredEmployees: Math.max(1, shift.requiredEmployees - 1) })}
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</button>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
value={shift.requiredEmployees}
|
value={shift.requiredEmployees}
|
||||||
onChange={(e) => onUpdateShift(shift.id, { requiredEmployees: parseInt(e.target.value) || 1 })}
|
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
|
<button
|
||||||
onClick={() => onRemoveShift(shift.id)}
|
onClick={() => onUpdateShift(shift.id, { requiredEmployees: shift.requiredEmployees + 1 })}
|
||||||
style={{ padding: '8px 12px', backgroundColor: '#dc3545', color: 'white', border: 'none', borderRadius: '4px' }}
|
|
||||||
title="Schicht löschen"
|
|
||||||
>
|
>
|
||||||
×
|
+
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -21,7 +21,13 @@ export const shiftTemplateService = {
|
|||||||
throw new Error('Fehler beim Laden der Vorlagen');
|
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> {
|
async getTemplate(id: string): Promise<ShiftTemplate> {
|
||||||
@@ -44,6 +50,17 @@ export const shiftTemplateService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async createTemplate(template: Omit<ShiftTemplate, 'id' | 'createdAt' | 'createdBy'>): Promise<ShiftTemplate> {
|
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, {
|
const response = await fetch(API_BASE, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
Reference in New Issue
Block a user