mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +01:00
can add and delete shiftplans from pressets
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||||
import { Employee } from '../../../backend/src/models/employee';
|
import { Employee } from '../models/Employee';
|
||||||
|
|
||||||
interface LoginRequest {
|
interface LoginRequest {
|
||||||
email: string;
|
email: string;
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export const TEMPLATE_PRESETS = {
|
|||||||
timeSlots: DEFAULT_ZEBRA_TIME_SLOTS,
|
timeSlots: DEFAULT_ZEBRA_TIME_SLOTS,
|
||||||
shifts: DEFAULT_ZEBRA_SHIFTS
|
shifts: DEFAULT_ZEBRA_SHIFTS
|
||||||
},
|
},
|
||||||
ZEBRA_MINIMAL: {
|
/*ZEBRA_MINIMAL: {
|
||||||
name: 'ZEBRA Minimal',
|
name: 'ZEBRA Minimal',
|
||||||
description: 'ZEBRA mit minimaler Besetzung',
|
description: 'ZEBRA mit minimaler Besetzung',
|
||||||
timeSlots: DEFAULT_ZEBRA_TIME_SLOTS,
|
timeSlots: DEFAULT_ZEBRA_TIME_SLOTS,
|
||||||
@@ -89,14 +89,14 @@ export const TEMPLATE_PRESETS = {
|
|||||||
{ timeSlotId: 'afternoon', dayOfWeek: day, requiredEmployees: 3, color: '#e74c3c' }
|
{ timeSlotId: 'afternoon', dayOfWeek: day, requiredEmployees: 3, color: '#e74c3c' }
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
},
|
},*/
|
||||||
GENERAL_STANDARD: {
|
GENERAL_STANDARD: {
|
||||||
name: 'Standard Wochenplan',
|
name: 'Standard Wochenplan',
|
||||||
description: 'Standard Vorlage: Mo-Fr Vormittag+Nachmittag+Abend',
|
description: 'Standard Vorlage: Mo-Fr Vormittag+Nachmittag+Abend',
|
||||||
timeSlots: DEFAULT_TIME_SLOTS,
|
timeSlots: DEFAULT_TIME_SLOTS,
|
||||||
shifts: DEFAULT_SHIFTS
|
shifts: DEFAULT_SHIFTS
|
||||||
},
|
},
|
||||||
ZEBRA_PART_TIME: {
|
/*ZEBRA_PART_TIME: {
|
||||||
name: 'ZEBRA Teilzeit',
|
name: 'ZEBRA Teilzeit',
|
||||||
description: 'ZEBRA Vorlage mit reduzierten Schichten',
|
description: 'ZEBRA Vorlage mit reduzierten Schichten',
|
||||||
timeSlots: DEFAULT_ZEBRA_TIME_SLOTS,
|
timeSlots: DEFAULT_ZEBRA_TIME_SLOTS,
|
||||||
@@ -106,7 +106,7 @@ export const TEMPLATE_PRESETS = {
|
|||||||
timeSlotId: 'morning', dayOfWeek: day, requiredEmployees: 1, color: '#3498db'
|
timeSlotId: 'morning', dayOfWeek: day, requiredEmployees: 1, color: '#3498db'
|
||||||
}))
|
}))
|
||||||
]
|
]
|
||||||
}
|
} */
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// Helper function to create plan from preset
|
// Helper function to create plan from preset
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
// frontend/src/pages/ShiftPlans/ShiftPlanCreate.tsx
|
// frontend/src/pages/ShiftPlans/ShiftPlanCreate.tsx
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { shiftTemplateService } from '../../services/shiftTemplateService';
|
|
||||||
import { shiftPlanService } from '../../services/shiftPlanService';
|
import { shiftPlanService } from '../../services/shiftPlanService';
|
||||||
import styles from './ShiftPlanCreate.module.css';
|
import styles from './ShiftPlanCreate.module.css';
|
||||||
import { TimeSlot, Shift } from '../../models/ShiftPlan';
|
|
||||||
|
|
||||||
export interface TemplateShift {
|
// Interface für Template Presets
|
||||||
id: string;
|
interface TemplatePreset {
|
||||||
name: string;
|
name: string;
|
||||||
isDefault?: boolean;
|
label: string;
|
||||||
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShiftPlanCreate: React.FC = () => {
|
const ShiftPlanCreate: React.FC = () => {
|
||||||
@@ -19,37 +18,31 @@ const ShiftPlanCreate: React.FC = () => {
|
|||||||
const [planName, setPlanName] = useState('');
|
const [planName, setPlanName] = useState('');
|
||||||
const [startDate, setStartDate] = useState('');
|
const [startDate, setStartDate] = useState('');
|
||||||
const [endDate, setEndDate] = useState('');
|
const [endDate, setEndDate] = useState('');
|
||||||
const [selectedTemplate, setSelectedTemplate] = useState('');
|
const [selectedPreset, setSelectedPreset] = useState('');
|
||||||
const [templates, setTemplates] = useState<TemplateShift[]>([]);
|
const [presets, setPresets] = useState<TemplatePreset[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [success, setSuccess] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadTemplates();
|
loadTemplatePresets();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const loadTemplatePresets = async () => {
|
||||||
// Template aus URL-Parameter setzen, falls vorhanden
|
|
||||||
const templateId = searchParams.get('template');
|
|
||||||
if (templateId) {
|
|
||||||
setSelectedTemplate(templateId);
|
|
||||||
}
|
|
||||||
}, [searchParams]);
|
|
||||||
|
|
||||||
const loadTemplates = async () => {
|
|
||||||
try {
|
try {
|
||||||
const data = await shiftTemplateService.getTemplates();
|
console.log('🔄 Lade verfügbare Vorlagen-Presets...');
|
||||||
setTemplates(data);
|
const data = await shiftPlanService.getTemplatePresets();
|
||||||
|
console.log('✅ Presets geladen:', data);
|
||||||
|
|
||||||
// Wenn keine Template-ID in der URL ist, setze die Standard-Vorlage
|
setPresets(data);
|
||||||
if (!searchParams.get('template')) {
|
|
||||||
if (!searchParams.get('template') && data.length > 0) {
|
// Setze das erste Preset als Standard, falls vorhanden
|
||||||
setSelectedTemplate(data[0].id);
|
if (data.length > 0) {
|
||||||
}
|
setSelectedPreset(data[0].name);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Laden der Vorlagen:', error);
|
console.error('❌ Fehler beim Laden der Vorlagen-Presets:', error);
|
||||||
setError('Vorlagen konnten nicht geladen werden');
|
setError('Vorlagen-Presets konnten nicht geladen werden');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -57,6 +50,7 @@ const ShiftPlanCreate: React.FC = () => {
|
|||||||
|
|
||||||
const handleCreate = async () => {
|
const handleCreate = async () => {
|
||||||
try {
|
try {
|
||||||
|
// Validierung
|
||||||
if (!planName.trim()) {
|
if (!planName.trim()) {
|
||||||
setError('Bitte geben Sie einen Namen für den Schichtplan ein');
|
setError('Bitte geben Sie einen Namen für den Schichtplan ein');
|
||||||
return;
|
return;
|
||||||
@@ -73,53 +67,53 @@ const ShiftPlanCreate: React.FC = () => {
|
|||||||
setError('Das Enddatum muss nach dem Startdatum liegen');
|
setError('Das Enddatum muss nach dem Startdatum liegen');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!selectedPreset) {
|
||||||
let timeSlots: Omit<TimeSlot, 'id' | 'planId'>[] = [];
|
setError('Bitte wählen Sie eine Vorlage aus');
|
||||||
let shifts: Omit<Shift, 'id' | 'planId'>[] = [];
|
|
||||||
|
|
||||||
// If a template is selected, load its data
|
|
||||||
if (selectedTemplate) {
|
|
||||||
try {
|
|
||||||
const template = await shiftTemplateService.getTemplate(selectedTemplate);
|
|
||||||
timeSlots = template.timeSlots.map(slot => ({
|
|
||||||
name: slot.name,
|
|
||||||
startTime: slot.startTime,
|
|
||||||
endTime: slot.endTime,
|
|
||||||
description: slot.description
|
|
||||||
}));
|
|
||||||
shifts = template.shifts.map(shift => ({
|
|
||||||
timeSlotId: shift.timeSlotId,
|
|
||||||
dayOfWeek: shift.dayOfWeek,
|
|
||||||
requiredEmployees: shift.requiredEmployees,
|
|
||||||
color: shift.color
|
|
||||||
}));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Fehler beim Laden der Vorlage:', error);
|
|
||||||
setError('Die ausgewählte Vorlage konnte nicht geladen werden');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await shiftPlanService.createShiftPlan({
|
console.log('🔄 Erstelle Schichtplan aus Preset...', {
|
||||||
|
presetName: selectedPreset,
|
||||||
name: planName,
|
name: planName,
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate
|
||||||
isTemplate: false,
|
|
||||||
templateId: selectedTemplate || undefined,
|
|
||||||
timeSlots,
|
|
||||||
shifts
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Nach erfolgreicher Erstellung zur Liste der Schichtpläne navigieren
|
// Erstelle den Plan aus dem ausgewählten Preset
|
||||||
navigate('/shift-plans');
|
const createdPlan = await shiftPlanService.createFromPreset({
|
||||||
|
presetName: selectedPreset,
|
||||||
|
name: planName,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
isTemplate: false
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ Plan erstellt:', createdPlan);
|
||||||
|
|
||||||
|
// Erfolgsmeldung und Weiterleitung
|
||||||
|
setSuccess('Schichtplan erfolgreich erstellt!');
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate(`/shift-plans/${createdPlan.id}`);
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Erstellen des Schichtplans:', error);
|
const err = error as Error;
|
||||||
setError('Der Schichtplan konnte nicht erstellt werden. Bitte versuchen Sie es später erneut.');
|
console.error('❌ Fehler beim Erstellen des Plans:', err);
|
||||||
|
setError(`Plan konnte nicht erstellt werden: ${err.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSelectedPresetDescription = () => {
|
||||||
|
const preset = presets.find(p => p.name === selectedPreset);
|
||||||
|
return preset ? preset.description : '';
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div>Lade Vorlagen...</div>;
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.loading}>Lade Vorlagen...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -137,6 +131,12 @@ const ShiftPlanCreate: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{success && (
|
||||||
|
<div className={styles.success}>
|
||||||
|
{success}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className={styles.form}>
|
<div className={styles.form}>
|
||||||
<div className={styles.formGroup}>
|
<div className={styles.formGroup}>
|
||||||
<label>Plan Name:</label>
|
<label>Plan Name:</label>
|
||||||
@@ -145,6 +145,7 @@ const ShiftPlanCreate: React.FC = () => {
|
|||||||
value={planName}
|
value={planName}
|
||||||
onChange={(e) => setPlanName(e.target.value)}
|
onChange={(e) => setPlanName(e.target.value)}
|
||||||
placeholder="z.B. KW 42 2025"
|
placeholder="z.B. KW 42 2025"
|
||||||
|
className={styles.input}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -155,6 +156,7 @@ const ShiftPlanCreate: React.FC = () => {
|
|||||||
type="date"
|
type="date"
|
||||||
value={startDate}
|
value={startDate}
|
||||||
onChange={(e) => setStartDate(e.target.value)}
|
onChange={(e) => setStartDate(e.target.value)}
|
||||||
|
className={styles.input}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -164,6 +166,7 @@ const ShiftPlanCreate: React.FC = () => {
|
|||||||
type="date"
|
type="date"
|
||||||
value={endDate}
|
value={endDate}
|
||||||
onChange={(e) => setEndDate(e.target.value)}
|
onChange={(e) => setEndDate(e.target.value)}
|
||||||
|
className={styles.input}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,29 +174,37 @@ const ShiftPlanCreate: React.FC = () => {
|
|||||||
<div className={styles.formGroup}>
|
<div className={styles.formGroup}>
|
||||||
<label>Vorlage verwenden:</label>
|
<label>Vorlage verwenden:</label>
|
||||||
<select
|
<select
|
||||||
value={selectedTemplate}
|
value={selectedPreset}
|
||||||
onChange={(e) => setSelectedTemplate(e.target.value)}
|
onChange={(e) => setSelectedPreset(e.target.value)}
|
||||||
className={templates.length === 0 ? styles.empty : ''}
|
className={`${styles.select} ${presets.length === 0 ? styles.empty : ''}`}
|
||||||
>
|
>
|
||||||
<option value="">Keine Vorlage</option>
|
<option value="">Bitte wählen...</option>
|
||||||
{templates.map(template => (
|
{presets.map(preset => (
|
||||||
<option key={template.id} value={template.id}>
|
<option key={preset.name} value={preset.name}>
|
||||||
{template.name} {template.isDefault ? '(Standard)' : ''}
|
{preset.label}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
{templates.length === 0 && (
|
|
||||||
|
{selectedPreset && (
|
||||||
|
<div className={styles.presetDescription}>
|
||||||
|
{getSelectedPresetDescription()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{presets.length === 0 && (
|
||||||
<p className={styles.noTemplates}>
|
<p className={styles.noTemplates}>
|
||||||
Keine Vorlagen verfügbar.
|
Keine Vorlagen verfügbar.
|
||||||
<button onClick={() => navigate('/shift-templates/new')} className={styles.linkButton}>
|
|
||||||
Neue Vorlage erstellen
|
|
||||||
</button>
|
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
<button onClick={handleCreate} className={styles.createButton} disabled={!selectedTemplate}>
|
<button
|
||||||
|
onClick={handleCreate}
|
||||||
|
className={styles.createButton}
|
||||||
|
disabled={!selectedPreset || !planName.trim() || !startDate || !endDate}
|
||||||
|
>
|
||||||
Schichtplan erstellen
|
Schichtplan erstellen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
import { shiftPlanService } from '../../services/shiftPlanService';
|
import { shiftPlanService } from '../../services/shiftPlanService';
|
||||||
import { ShiftPlan } from '../../../../backend/src/models/shiftPlan';
|
import { ShiftPlan } from '../../models/ShiftPlan';
|
||||||
import { useNotification } from '../../contexts/NotificationContext';
|
import { useNotification } from '../../contexts/NotificationContext';
|
||||||
import { formatDate } from '../../utils/foramatters';
|
import { formatDate } from '../../utils/foramatters';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// frontend/src/services/authService.ts
|
// frontend/src/services/authService.ts
|
||||||
import { Employee } from '../../../backend/src/models/employee';
|
import { Employee } from '../models/Employee';
|
||||||
const API_BASE = 'http://localhost:3002/api';
|
const API_BASE = 'http://localhost:3002/api';
|
||||||
|
|
||||||
export interface LoginRequest {
|
export interface LoginRequest {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// frontend/src/services/employeeService.ts
|
// frontend/src/services/employeeService.ts
|
||||||
import { Employee, CreateEmployeeRequest, UpdateEmployeeRequest, EmployeeAvailability } from '../../../backend/src/models/employee';
|
import { Employee, CreateEmployeeRequest, UpdateEmployeeRequest, EmployeeAvailability } from '../models/Employee';
|
||||||
|
|
||||||
const API_BASE_URL = 'http://localhost:3002/api';
|
const API_BASE_URL = 'http://localhost:3002/api';
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
// frontend/src/services/shiftPlanService.ts
|
// frontend/src/services/shiftPlanService.ts
|
||||||
import { authService } from './authService';
|
import { authService } from './authService';
|
||||||
import { ShiftPlan, CreateShiftPlanRequest, Shift } from '../models/ShiftPlan';
|
import { ShiftPlan, CreateShiftPlanRequest, Shift, CreateShiftFromTemplateRequest } from '../models/ShiftPlan';
|
||||||
|
import { TEMPLATE_PRESETS } from '../models/defaults/shiftPlanDefaults';
|
||||||
|
|
||||||
const API_BASE = 'http://localhost:3002/api/shift-plans';
|
const API_BASE = 'http://localhost:3002/api/shift-plans';
|
||||||
|
|
||||||
|
// Helper function to get auth headers
|
||||||
|
const getAuthHeaders = () => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token && { 'Authorization': `Bearer ${token}` })
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to handle responses
|
||||||
|
const handleResponse = async (response: Response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
|
||||||
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
};
|
||||||
|
|
||||||
export const shiftPlanService = {
|
export const shiftPlanService = {
|
||||||
async getShiftPlans(): Promise<ShiftPlan[]> {
|
async getShiftPlans(): Promise<ShiftPlan[]> {
|
||||||
const response = await fetch(API_BASE, {
|
const response = await fetch(API_BASE, {
|
||||||
@@ -101,5 +120,70 @@ export const shiftPlanService = {
|
|||||||
}
|
}
|
||||||
throw new Error('Fehler beim Löschen des Schichtplans');
|
throw new Error('Fehler beim Löschen des Schichtplans');
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getTemplates: async (): Promise<ShiftPlan[]> => {
|
||||||
|
const response = await fetch(`${API_BASE}/templates`, {
|
||||||
|
headers: getAuthHeaders()
|
||||||
|
});
|
||||||
|
return handleResponse(response);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get specific template or plan
|
||||||
|
getTemplate: async (id: string): Promise<ShiftPlan> => {
|
||||||
|
const response = await fetch(`${API_BASE}/${id}`, {
|
||||||
|
headers: getAuthHeaders()
|
||||||
|
});
|
||||||
|
return handleResponse(response);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create plan from template
|
||||||
|
createFromTemplate: async (data: CreateShiftFromTemplateRequest): Promise<ShiftPlan> => {
|
||||||
|
const response = await fetch(`${API_BASE}/from-template`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getAuthHeaders(),
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
return handleResponse(response);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create new plan
|
||||||
|
createPlan: async (data: CreateShiftPlanRequest): Promise<ShiftPlan> => {
|
||||||
|
const response = await fetch(`${API_BASE}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getAuthHeaders(),
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
return handleResponse(response);
|
||||||
|
},
|
||||||
|
|
||||||
|
createFromPreset: async (data: {
|
||||||
|
presetName: string;
|
||||||
|
name: string;
|
||||||
|
startDate: string;
|
||||||
|
endDate: string;
|
||||||
|
isTemplate?: boolean;
|
||||||
|
}): Promise<ShiftPlan> => {
|
||||||
|
const response = await fetch(`${API_BASE}/from-preset`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getAuthHeaders(),
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
|
||||||
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
getTemplatePresets: async (): Promise<{name: string, label: string, description: string}[]> => {
|
||||||
|
// name = label
|
||||||
|
return Object.entries(TEMPLATE_PRESETS).map(([key, preset]) => ({
|
||||||
|
name: key,
|
||||||
|
label: preset.name,
|
||||||
|
description: preset.description
|
||||||
|
}));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
// frontend/src/services/shiftTemplateService.ts
|
|
||||||
import { ShiftPlan } from '../../../backend/src/models/shiftPlan.js';
|
|
||||||
import { authService } from './authService';
|
|
||||||
|
|
||||||
const API_BASE = 'http://localhost:3002/api/shift-templates';
|
|
||||||
|
|
||||||
export const shiftTemplateService = {
|
|
||||||
async getTemplates(): Promise<ShiftPlan[]> {
|
|
||||||
const response = await fetch(API_BASE, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...authService.getAuthHeaders()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 401) {
|
|
||||||
authService.logout();
|
|
||||||
throw new Error('Nicht authorisiert - bitte erneut anmelden');
|
|
||||||
}
|
|
||||||
throw new Error('Fehler beim Laden der Vorlagen');
|
|
||||||
}
|
|
||||||
|
|
||||||
const templates = await response.json();
|
|
||||||
return templates;
|
|
||||||
},
|
|
||||||
|
|
||||||
async getTemplate(id: string): Promise<ShiftPlan> {
|
|
||||||
const response = await fetch(`${API_BASE}/${id}`, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...authService.getAuthHeaders()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 401) {
|
|
||||||
authService.logout();
|
|
||||||
throw new Error('Nicht authorisiert - bitte erneut anmelden');
|
|
||||||
}
|
|
||||||
throw new Error('Vorlage nicht gefunden');
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
},
|
|
||||||
|
|
||||||
async createTemplate(template: Omit<ShiftPlan, 'id' | 'createdAt' | 'createdBy'>): Promise<ShiftPlan> {
|
|
||||||
const response = await fetch(API_BASE, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...authService.getAuthHeaders()
|
|
||||||
},
|
|
||||||
body: JSON.stringify(template)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 401) {
|
|
||||||
authService.logout();
|
|
||||||
throw new Error('Nicht authorisiert - bitte erneut anmelden');
|
|
||||||
}
|
|
||||||
throw new Error('Fehler beim Erstellen der Vorlage');
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
},
|
|
||||||
|
|
||||||
async updateTemplate(id: string, template: Partial<ShiftPlan>): Promise<ShiftPlan> {
|
|
||||||
const response = await fetch(`${API_BASE}/${id}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...authService.getAuthHeaders()
|
|
||||||
},
|
|
||||||
body: JSON.stringify(template)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 401) {
|
|
||||||
authService.logout();
|
|
||||||
throw new Error('Nicht authorisiert - bitte erneut anmelden');
|
|
||||||
}
|
|
||||||
throw new Error('Fehler beim Aktualisieren der Vorlage');
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
},
|
|
||||||
|
|
||||||
async deleteTemplate(id: string): Promise<void> {
|
|
||||||
const response = await fetch(`${API_BASE}/${id}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
...authService.getAuthHeaders()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 401) {
|
|
||||||
authService.logout();
|
|
||||||
throw new Error('Nicht authorisiert - bitte erneut anmelden');
|
|
||||||
}
|
|
||||||
throw new Error('Fehler beim Löschen der Vorlage');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
// frontend/src/shared/utils.ts
|
// frontend/src/shared/utils.ts
|
||||||
// import { ScheduledShift } from '../../../backend/src/models/shiftPlan.js';
|
|
||||||
|
|
||||||
// Shared date and time formatting utilities
|
// Shared date and time formatting utilities
|
||||||
export const formatDate = (dateString: string | undefined): string => {
|
export const formatDate = (dateString: string | undefined): string => {
|
||||||
if (!dateString) return 'Kein Datum';
|
if (!dateString) return 'Kein Datum';
|
||||||
|
|||||||
Reference in New Issue
Block a user