added apiClient class handling api auth and validation response from backend

This commit is contained in:
2025-11-01 13:47:29 +01:00
parent 29c66f0228
commit 41ddad6fa9
7 changed files with 267 additions and 468 deletions

View File

@@ -51,39 +51,26 @@ if (isDevelopment) {
} }
const configureStaticFiles = () => { const configureStaticFiles = () => {
const possiblePaths = [ const staticConfig = {
// Production path (Docker) maxAge: '1y',
'/app/frontend-build', etag: false,
// Development paths immutable: true,
path.resolve(__dirname, '../../frontend/dist'), index: false
path.resolve(__dirname, '../frontend/dist'), // Added for monorepo };
path.resolve(process.cwd(), 'frontend/dist'), // Current directory
path.resolve(process.cwd(), '../frontend/dist'), // Parent directory
// Vite dev server fallback
...(isDevelopment ? [path.resolve(__dirname, '../../frontend')] : [])
];
for (const testPath of possiblePaths) { // Serve frontend build
try { const frontendPath = '/app/frontend-build';
if (fs.existsSync(testPath)) { if (fs.existsSync(frontendPath)) {
// In development, check for dist or direct source console.log('✅ Serving frontend from:', frontendPath);
if (fs.existsSync(path.join(testPath, 'index.html'))) { app.use(express.static(frontendPath, staticConfig));
console.log('✅ Found frontend at:', testPath);
app.use(express.static(testPath));
return testPath;
} }
// For Vite dev server in development
else if (isDevelopment && fs.existsSync(path.join(testPath, 'index.html'))) { // Serve premium assets if available
console.log('🔧 Development: Serving frontend source from:', testPath); const premiumPath = '/app/premium-dist';
app.use(express.static(testPath)); if (fs.existsSync(premiumPath)) {
return testPath; console.log('✅ Serving premium assets from:', premiumPath);
app.use('/premium-assets', express.static(premiumPath, staticConfig));
} }
}
} catch (error) {
// Silent catch
}
}
return null;
}; };
// Security configuration // Security configuration
@@ -225,7 +212,7 @@ const findFrontendBuildPath = (): string | null => {
}; };
const frontendBuildPath = findFrontendBuildPath(); const frontendBuildPath = findFrontendBuildPath();
const staticPath = configureStaticFiles(); configureStaticFiles();
if (frontendBuildPath) { if (frontendBuildPath) {
app.use(express.static(frontendBuildPath)); app.use(express.static(frontendBuildPath));
@@ -264,32 +251,26 @@ app.get('/', async (req, res) => {
}); });
// Client-side routing fallback // Client-side routing fallback
app.get('*', async (req, res, next) => { app.get('*', (req, res, next) => {
// Skip API routes
if (req.path.startsWith('/api/')) { if (req.path.startsWith('/api/')) {
return next(); // API routes handled normally return next();
} }
// Vite dev server handling // Skip file extensions (assets)
if (vite) { if (req.path.match(/\.[a-z0-9]+$/i)) {
try { return next();
const template = fs.readFileSync(
path.resolve(__dirname, '../../frontend/index.html'),
'utf-8'
);
const html = await vite.transformIndexHtml(req.url, template);
return res.send(html);
} catch (error) {
console.error('Vite transformation error:', error);
}
} }
// Static file fallback // Serve React app for all other routes
if (staticPath) { const frontendPath = '/app/frontend-build';
const indexPath = path.join(staticPath, 'index.html'); const indexPath = path.join(frontendPath, 'index.html');
return res.sendFile(indexPath);
}
res.status(500).send('Frontend not available'); if (fs.existsSync(indexPath)) {
res.sendFile(indexPath);
} else {
res.status(404).send('Frontend not available');
}
}); });
// Error handling // Error handling

View File

@@ -0,0 +1,130 @@
import { ValidationError, ErrorService } from './errorService';
export class ApiError extends Error {
public validationErrors: ValidationError[];
public statusCode: number;
public originalError?: any;
constructor(message: string, validationErrors: ValidationError[] = [], statusCode: number = 0, originalError?: any) {
super(message);
this.name = 'ApiError';
this.validationErrors = validationErrors;
this.statusCode = statusCode;
this.originalError = originalError;
}
}
export class ApiClient {
private baseURL: string;
constructor() {
this.baseURL = import.meta.env.VITE_API_URL || '/api';
}
private getAuthHeaders(): HeadersInit {
const token = localStorage.getItem('token');
return token ? { 'Authorization': `Bearer ${token}` } : {};
}
private async handleApiResponse<T>(response: Response): Promise<T> {
if (!response.ok) {
let errorData;
try {
// Try to parse error response as JSON
const responseText = await response.text();
errorData = responseText ? JSON.parse(responseText) : {};
} catch {
// If not JSON, create a generic error object
errorData = { error: `HTTP ${response.status}: ${response.statusText}` };
}
// Extract validation errors using your existing ErrorService
const validationErrors = ErrorService.extractValidationErrors(errorData);
if (validationErrors.length > 0) {
// Throw error with validationErrors property for useBackendValidation hook
throw new ApiError(
errorData.error || 'Validation failed',
validationErrors,
response.status,
errorData
);
}
// Throw regular error for non-validation errors
throw new ApiError(
errorData.error || errorData.message || `HTTP error! status: ${response.status}`,
[],
response.status,
errorData
);
}
// For successful responses, try to parse as JSON
try {
const responseText = await response.text();
return responseText ? JSON.parse(responseText) : {} as T;
} catch (error) {
// If response is not JSON but request succeeded (e.g., 204 No Content)
return {} as T;
}
}
async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const url = `${this.baseURL}${endpoint}`;
const config: RequestInit = {
headers: {
'Content-Type': 'application/json',
...this.getAuthHeaders(),
...options.headers,
},
...options,
};
try {
const response = await fetch(url, config);
return await this.handleApiResponse<T>(response);
} catch (error) {
// Re-throw the error to be caught by useBackendValidation
if (error instanceof ApiError) {
throw error;
}
// Wrap non-ApiError errors
throw new ApiError(
error instanceof Error ? error.message : 'Unknown error occurred',
[],
0,
error
);
}
}
// Standardized HTTP methods
get = <T>(endpoint: string) => this.request<T>(endpoint);
post = <T>(endpoint: string, data?: any) =>
this.request<T>(endpoint, {
method: 'POST',
body: data ? JSON.stringify(data) : undefined
});
put = <T>(endpoint: string, data?: any) =>
this.request<T>(endpoint, {
method: 'PUT',
body: data ? JSON.stringify(data) : undefined
});
patch = <T>(endpoint: string, data?: any) =>
this.request<T>(endpoint, {
method: 'PATCH',
body: data ? JSON.stringify(data) : undefined
});
delete = <T>(endpoint: string) =>
this.request<T>(endpoint, { method: 'DELETE' });
}
export const apiClient = new ApiClient();

View File

@@ -1,8 +1,5 @@
// frontend/src/services/authService.ts - UPDATED
import { Employee } from '../models/Employee'; import { Employee } from '../models/Employee';
import { ErrorService } from './errorService'; import { apiClient } from './apiClient';
const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
export interface LoginRequest { export interface LoginRequest {
email: string; email: string;
@@ -25,31 +22,8 @@ export interface AuthResponse {
class AuthService { class AuthService {
private token: string | null = null; private token: string | null = null;
private async handleApiResponse<T>(response: Response): Promise<T> {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const validationErrors = ErrorService.extractValidationErrors(errorData);
if (validationErrors.length > 0) {
const error = new Error('Validation failed');
(error as any).validationErrors = validationErrors;
throw error;
}
throw new Error(errorData.error || errorData.message || 'Authentication failed');
}
return response.json();
}
async login(credentials: LoginRequest): Promise<AuthResponse> { async login(credentials: LoginRequest): Promise<AuthResponse> {
const response = await fetch(`${API_BASE_URL}/auth/login`, { const data = await apiClient.post<AuthResponse>('/auth/login', credentials);
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
});
const data = await this.handleApiResponse<AuthResponse>(response);
this.token = data.token; this.token = data.token;
localStorage.setItem('token', data.token); localStorage.setItem('token', data.token);
localStorage.setItem('employee', JSON.stringify(data.employee)); localStorage.setItem('employee', JSON.stringify(data.employee));
@@ -57,13 +31,7 @@ class AuthService {
} }
async register(userData: RegisterRequest): Promise<AuthResponse> { async register(userData: RegisterRequest): Promise<AuthResponse> {
const response = await fetch(`${API_BASE_URL}/employees`, { await apiClient.post('/employees', userData);
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
const data = await this.handleApiResponse<AuthResponse>(response);
return this.login({ return this.login({
email: userData.email, email: userData.email,
password: userData.password password: userData.password
@@ -77,29 +45,17 @@ class AuthService {
async fetchCurrentEmployee(): Promise<Employee | null> { async fetchCurrentEmployee(): Promise<Employee | null> {
const token = this.getToken(); const token = this.getToken();
if (!token) { if (!token) return null;
return null;
}
try { try {
const response = await fetch(`${API_BASE_URL}/auth/me`, { const data = await apiClient.get<{ user: Employee }>('/auth/me');
headers: { localStorage.setItem('user', JSON.stringify(data.user));
'Authorization': `Bearer ${token}` return data.user;
}
});
if (response.ok) {
const data = await response.json();
const user = data.user;
localStorage.setItem('user', JSON.stringify(user));
return user;
}
} catch (error) { } catch (error) {
console.error('Error fetching current user:', error); console.error('Error fetching current user:', error);
}
return null; return null;
} }
}
logout(): void { logout(): void {
this.token = null; this.token = null;

View File

@@ -1,138 +1,58 @@
// frontend/src/services/employeeService.ts
import { Employee, CreateEmployeeRequest, UpdateEmployeeRequest, EmployeeAvailability } from '../models/Employee'; import { Employee, CreateEmployeeRequest, UpdateEmployeeRequest, EmployeeAvailability } from '../models/Employee';
import { ErrorService, ValidationError } from './errorService'; import { apiClient } from './apiClient';
const API_BASE_URL = '/api';
const getAuthHeaders = () => {
const token = localStorage.getItem('token');
return {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
};
};
export class EmployeeService { export class EmployeeService {
private async handleApiResponse<T>(response: Response): Promise<T> {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const validationErrors = ErrorService.extractValidationErrors(errorData);
if (validationErrors.length > 0) {
const error = new Error('Validation failed');
(error as any).validationErrors = validationErrors;
throw error;
}
throw new Error(errorData.error || errorData.message || `HTTP error! status: ${response.status}`);
}
return response.json();
}
async getEmployees(includeInactive: boolean = false): Promise<Employee[]> { async getEmployees(includeInactive: boolean = false): Promise<Employee[]> {
console.log('🔄 Fetching employees from API...'); console.log('🔄 Fetching employees from API...');
const token = localStorage.getItem('token'); try {
console.log('🔑 Token exists:', !!token); const employees = await apiClient.get<Employee[]>(`/employees?includeInactive=${includeInactive}`);
const response = await fetch(`${API_BASE_URL}/employees?includeInactive=${includeInactive}`, {
headers: getAuthHeaders(),
});
console.log('📡 Response status:', response.status);
if (!response.ok) {
const errorText = await response.text();
console.error('❌ API Error:', errorText);
throw new Error('Failed to fetch employees');
}
const employees = await response.json();
console.log('✅ Employees received:', employees.length); console.log('✅ Employees received:', employees.length);
return employees; return employees;
} catch (error) {
console.error('❌ Error fetching employees:', error);
throw error; // Let useBackendValidation handle this
}
} }
async getEmployee(id: string): Promise<Employee> { async getEmployee(id: string): Promise<Employee> {
const response = await fetch(`${API_BASE_URL}/employees/${id}`, { return apiClient.get<Employee>(`/employees/${id}`);
headers: getAuthHeaders(),
});
return this.handleApiResponse<Employee>(response);
} }
async createEmployee(employee: CreateEmployeeRequest): Promise<Employee> { async createEmployee(employee: CreateEmployeeRequest): Promise<Employee> {
const response = await fetch(`${API_BASE_URL}/employees`, { return apiClient.post<Employee>('/employees', employee);
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(employee),
});
return this.handleApiResponse<Employee>(response);
} }
async updateEmployee(id: string, employee: UpdateEmployeeRequest): Promise<Employee> { async updateEmployee(id: string, employee: UpdateEmployeeRequest): Promise<Employee> {
const response = await fetch(`${API_BASE_URL}/employees/${id}`, { return apiClient.put<Employee>(`/employees/${id}`, employee);
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(employee),
});
return this.handleApiResponse<Employee>(response);
} }
async deleteEmployee(id: string): Promise<void> { async deleteEmployee(id: string): Promise<void> {
const response = await fetch(`${API_BASE_URL}/employees/${id}`, { await apiClient.delete(`/employees/${id}`);
method: 'DELETE',
headers: getAuthHeaders(),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to delete employee');
}
} }
async getAvailabilities(employeeId: string): Promise<EmployeeAvailability[]> { async getAvailabilities(employeeId: string): Promise<EmployeeAvailability[]> {
const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, { return apiClient.get<EmployeeAvailability[]>(`/employees/${employeeId}/availabilities`);
headers: getAuthHeaders(),
});
return this.handleApiResponse<EmployeeAvailability[]>(response);
} }
async updateAvailabilities(employeeId: string, data: { planId: string, availabilities: Omit<EmployeeAvailability, 'id' | 'employeeId'>[] }): Promise<EmployeeAvailability[]> { async updateAvailabilities(
employeeId: string,
data: { planId: string, availabilities: Omit<EmployeeAvailability, 'id' | 'employeeId'>[] }
): Promise<EmployeeAvailability[]> {
console.log('🔄 Updating availabilities for employee:', employeeId); console.log('🔄 Updating availabilities for employee:', employeeId);
const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, { return apiClient.put<EmployeeAvailability[]>(`/employees/${employeeId}/availabilities`, data);
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(data),
});
return this.handleApiResponse<EmployeeAvailability[]>(response);
} }
async changePassword(id: string, data: { currentPassword: string, newPassword: string, confirmPassword: string }): Promise<void> { async changePassword(
const response = await fetch(`${API_BASE_URL}/employees/${id}/password`, { id: string,
method: 'PUT', data: { currentPassword: string, newPassword: string, confirmPassword: string }
headers: getAuthHeaders(), ): Promise<void> {
body: JSON.stringify(data), return apiClient.put<void>(`/employees/${id}/password`, data);
});
return this.handleApiResponse<void>(response);
} }
async updateLastLogin(employeeId: string): Promise<void> { async updateLastLogin(employeeId: string): Promise<void> {
try { try {
const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/last-login`, { await apiClient.patch(`/employees/${employeeId}/last-login`);
method: 'PATCH',
headers: getAuthHeaders(),
});
if (!response.ok) {
throw new Error('Failed to update last login');
}
} catch (error) { } catch (error) {
console.error('Error updating last login:', error); console.error('Error updating last login:', error);
throw error; throw error;

View File

@@ -1,65 +1,15 @@
// frontend/src/services/shiftAssignmentService.ts - WEEKLY PATTERN VERSION
import { ShiftPlan, ScheduledShift } from '../models/ShiftPlan'; import { ShiftPlan, ScheduledShift } from '../models/ShiftPlan';
import { Employee, EmployeeAvailability } from '../models/Employee'; import { Employee, EmployeeAvailability } from '../models/Employee';
import { authService } from './authService';
import { AssignmentResult, ScheduleRequest } from '../models/scheduling'; import { AssignmentResult, ScheduleRequest } from '../models/scheduling';
import { apiClient } from './apiClient';
const API_BASE_URL = '/api';
// Helper function to get auth headers
const getAuthHeaders = () => {
const token = localStorage.getItem('token');
return {
'Content-Type': 'application/json',
...(token && { 'Authorization': `Bearer ${token}` })
};
};
export class ShiftAssignmentService { export class ShiftAssignmentService {
async updateScheduledShift(id: string, updates: { assignedEmployees: string[] }): Promise<void> { async updateScheduledShift(id: string, updates: { assignedEmployees: string[] }): Promise<void> {
try { try {
//console.log('🔄 Updating scheduled shift via API:', { id, updates }); console.log('🔄 Updating scheduled shift via API:', { id, updates });
const response = await fetch(`${API_BASE_URL}/scheduled-shifts/${id}`, { await apiClient.put(`/scheduled-shifts/${id}`, updates);
method: 'PUT', console.log('✅ Scheduled shift updated successfully');
headers: {
'Content-Type': 'application/json',
...authService.getAuthHeaders()
},
body: JSON.stringify(updates)
});
// First, check if we got any response
if (!response.ok) {
// Try to get error message from response
const responseText = await response.text();
console.error('❌ Server response:', responseText);
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
// Try to parse as JSON if possible
try {
const errorData = JSON.parse(responseText);
errorMessage = errorData.error || errorMessage;
} catch (e) {
// If not JSON, use the text as is
errorMessage = responseText || errorMessage;
}
throw new Error(errorMessage);
}
// Try to parse successful response
const responseText = await response.text();
let result;
try {
result = responseText ? JSON.parse(responseText) : {};
} catch (e) {
console.warn('⚠️ Response was not JSON, but request succeeded');
result = { message: 'Update successful' };
}
console.log('✅ Scheduled shift updated successfully:', result);
} catch (error) { } catch (error) {
console.error('❌ Error updating scheduled shift:', error); console.error('❌ Error updating scheduled shift:', error);
@@ -69,48 +19,16 @@ export class ShiftAssignmentService {
async getScheduledShift(id: string): Promise<any> { async getScheduledShift(id: string): Promise<any> {
try { try {
const response = await fetch(`${API_BASE_URL}/scheduled-shifts/${id}`, { return await apiClient.get(`/scheduled-shifts/${id}`);
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
if (!response.ok) {
const responseText = await response.text();
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
try {
const errorData = JSON.parse(responseText);
errorMessage = errorData.error || errorMessage;
} catch (e) {
errorMessage = responseText || errorMessage;
}
throw new Error(errorMessage);
}
const responseText = await response.text();
return responseText ? JSON.parse(responseText) : {};
} catch (error) { } catch (error) {
console.error('Error fetching scheduled shift:', error); console.error('Error fetching scheduled shift:', error);
throw error; throw error;
} }
} }
// New method to get all scheduled shifts for a plan
async getScheduledShiftsForPlan(planId: string): Promise<ScheduledShift[]> { async getScheduledShiftsForPlan(planId: string): Promise<ScheduledShift[]> {
try { try {
const response = await fetch(`${API_BASE_URL}/scheduled-shifts/plan/${planId}`, { const shifts = await apiClient.get<ScheduledShift[]>(`/scheduled-shifts/plan/${planId}`);
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
if (!response.ok) {
throw new Error(`Failed to fetch scheduled shifts: ${response.status}`);
}
const shifts = await response.json();
// DEBUG: Check the structure of returned shifts // DEBUG: Check the structure of returned shifts
console.log('🔍 SCHEDULED SHIFTS STRUCTURE:', shifts.slice(0, 3)); console.log('🔍 SCHEDULED SHIFTS STRUCTURE:', shifts.slice(0, 3));
@@ -132,21 +50,7 @@ export class ShiftAssignmentService {
} }
private async callSchedulingAPI(request: ScheduleRequest): Promise<AssignmentResult> { private async callSchedulingAPI(request: ScheduleRequest): Promise<AssignmentResult> {
const response = await fetch(`${API_BASE_URL}/scheduling/generate-schedule`, { return await apiClient.post<AssignmentResult>('/scheduling/generate-schedule', request);
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authService.getAuthHeaders()
},
body: JSON.stringify(request)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Scheduling failed');
}
return response.json();
} }
async assignShifts( async assignShifts(

View File

@@ -1,198 +1,114 @@
// frontend/src/services/shiftPlanService.ts
import { authService } from './authService';
import { ShiftPlan, CreateShiftPlanRequest } from '../models/ShiftPlan'; import { ShiftPlan, CreateShiftPlanRequest } from '../models/ShiftPlan';
import { TEMPLATE_PRESETS } from '../models/defaults/shiftPlanDefaults'; import { TEMPLATE_PRESETS } from '../models/defaults/shiftPlanDefaults';
import { apiClient } from './apiClient';
const API_BASE_URL = '/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_URL, { try {
headers: { const plans = await apiClient.get<ShiftPlan[]>('/shift-plans');
'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 Schichtpläne');
}
const plans = await response.json();
// Ensure scheduledShifts is always an array // Ensure scheduledShifts is always an array
return plans.map((plan: any) => ({ return plans.map((plan: any) => ({
...plan, ...plan,
scheduledShifts: plan.scheduledShifts || [] scheduledShifts: plan.scheduledShifts || []
})); }));
} catch (error: any) {
if (error.statusCode === 401) {
// You might want to import and use authService here if needed
localStorage.removeItem('token');
localStorage.removeItem('employee');
throw new Error('Nicht authorisiert - bitte erneut anmelden');
}
throw new Error('Fehler beim Laden der Schichtpläne');
}
}, },
async getShiftPlan(id: string): Promise<ShiftPlan> { async getShiftPlan(id: string): Promise<ShiftPlan> {
const response = await fetch(`${API_BASE_URL}/${id}`, { try {
headers: { return await apiClient.get<ShiftPlan>(`/shift-plans/${id}`);
'Content-Type': 'application/json', } catch (error: any) {
...authService.getAuthHeaders() if (error.statusCode === 401) {
} localStorage.removeItem('token');
}); localStorage.removeItem('employee');
if (!response.ok) {
if (response.status === 401) {
authService.logout();
throw new Error('Nicht authorisiert - bitte erneut anmelden'); throw new Error('Nicht authorisiert - bitte erneut anmelden');
} }
throw new Error('Schichtplan nicht gefunden'); throw new Error('Schichtplan nicht gefunden');
} }
return await response.json();
}, },
async createShiftPlan(plan: CreateShiftPlanRequest): Promise<ShiftPlan> { async createShiftPlan(plan: CreateShiftPlanRequest): Promise<ShiftPlan> {
const response = await fetch(API_BASE_URL, { try {
method: 'POST', return await apiClient.post<ShiftPlan>('/shift-plans', plan);
headers: { } catch (error: any) {
'Content-Type': 'application/json', if (error.statusCode === 401) {
...authService.getAuthHeaders() localStorage.removeItem('token');
}, localStorage.removeItem('employee');
body: JSON.stringify(plan)
});
if (!response.ok) {
if (response.status === 401) {
authService.logout();
throw new Error('Nicht authorisiert - bitte erneut anmelden'); throw new Error('Nicht authorisiert - bitte erneut anmelden');
} }
throw new Error('Fehler beim Erstellen des Schichtplans'); throw new Error('Fehler beim Erstellen des Schichtplans');
} }
return response.json();
}, },
async updateShiftPlan(id: string, plan: Partial<ShiftPlan>): Promise<ShiftPlan> { async updateShiftPlan(id: string, plan: Partial<ShiftPlan>): Promise<ShiftPlan> {
const response = await fetch(`${API_BASE_URL}/${id}`, { try {
method: 'PUT', return await apiClient.put<ShiftPlan>(`/shift-plans/${id}`, plan);
headers: { } catch (error: any) {
'Content-Type': 'application/json', if (error.statusCode === 401) {
...authService.getAuthHeaders() localStorage.removeItem('token');
}, localStorage.removeItem('employee');
body: JSON.stringify(plan)
});
if (!response.ok) {
if (response.status === 401) {
authService.logout();
throw new Error('Nicht authorisiert - bitte erneut anmelden'); throw new Error('Nicht authorisiert - bitte erneut anmelden');
} }
throw new Error('Fehler beim Aktualisieren des Schichtplans'); throw new Error('Fehler beim Aktualisieren des Schichtplans');
} }
return response.json();
}, },
async deleteShiftPlan(id: string): Promise<void> { async deleteShiftPlan(id: string): Promise<void> {
const response = await fetch(`${API_BASE_URL}/${id}`, { try {
method: 'DELETE', await apiClient.delete(`/shift-plans/${id}`);
headers: { } catch (error: any) {
'Content-Type': 'application/json', if (error.statusCode === 401) {
...authService.getAuthHeaders() localStorage.removeItem('token');
} localStorage.removeItem('employee');
});
if (!response.ok) {
if (response.status === 401) {
authService.logout();
throw new Error('Nicht authorisiert - bitte erneut anmelden'); throw new Error('Nicht authorisiert - bitte erneut anmelden');
} }
throw new Error('Fehler beim Löschen des Schichtplans'); throw new Error('Fehler beim Löschen des Schichtplans');
} }
}, },
// Get specific template or plan async getTemplate(id: string): Promise<ShiftPlan> {
getTemplate: async (id: string): Promise<ShiftPlan> => { return await apiClient.get<ShiftPlan>(`/shift-plans/${id}`);
const response = await fetch(`${API_BASE_URL}/${id}`, {
headers: getAuthHeaders()
});
return handleResponse(response);
}, },
async regenerateScheduledShifts(planId: string): Promise<void> {
async regenerateScheduledShifts(planId: string):Promise<void> {
try { try {
console.log('🔄 Attempting to regenerate scheduled shifts...'); console.log('🔄 Attempting to regenerate scheduled shifts...');
await apiClient.post(`/shift-plans/${planId}/regenerate-shifts`);
// You'll need to add this API endpoint to your backend
const response = await fetch(`${API_BASE_URL}/${planId}/regenerate-shifts`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
if (response.ok) {
console.log('✅ Scheduled shifts regenerated'); console.log('✅ Scheduled shifts regenerated');
} else {
console.error('❌ Failed to regenerate shifts');
}
} catch (error) { } catch (error) {
console.error('❌ Error regenerating shifts:', error); console.error('❌ Error regenerating shifts:', error);
throw error;
} }
}, },
// Create new plan async createPlan(data: CreateShiftPlanRequest): Promise<ShiftPlan> {
createPlan: async (data: CreateShiftPlanRequest): Promise<ShiftPlan> => { return await apiClient.post<ShiftPlan>('/shift-plans', data);
const response = await fetch(`${API_BASE_URL}`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(data),
});
return handleResponse(response);
}, },
createFromPreset: async (data: { async createFromPreset(data: {
presetName: string; presetName: string;
name: string; name: string;
startDate: string; startDate: string;
endDate: string; endDate: string;
isTemplate?: boolean; isTemplate?: boolean;
}): Promise<ShiftPlan> => { }): Promise<ShiftPlan> {
const response = await fetch(`${API_BASE_URL}/from-preset`, { try {
method: 'POST', return await apiClient.post<ShiftPlan>('/shift-plans/from-preset', data);
headers: getAuthHeaders(), } catch (error: any) {
body: JSON.stringify(data), throw new Error(error.message || `HTTP error! status: ${error.statusCode}`);
});
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}[]> => { async getTemplatePresets(): Promise<{name: string, label: string, description: string}[]> {
// name = label
return Object.entries(TEMPLATE_PRESETS).map(([key, preset]) => ({ return Object.entries(TEMPLATE_PRESETS).map(([key, preset]) => ({
name: key, name: key,
label: preset.name, label: preset.name,
@@ -203,22 +119,8 @@ export const shiftPlanService = {
async clearAssignments(planId: string): Promise<void> { async clearAssignments(planId: string): Promise<void> {
try { try {
console.log('🔄 Clearing assignments for plan:', planId); console.log('🔄 Clearing assignments for plan:', planId);
await apiClient.post(`/shift-plans/${planId}/clear-assignments`);
const response = await fetch(`${API_BASE_URL}/${planId}/clear-assignments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authService.getAuthHeaders()
}
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
throw new Error(errorData.error || `Failed to clear assignments: ${response.status}`);
}
console.log('✅ Assignments cleared successfully'); console.log('✅ Assignments cleared successfully');
} catch (error) { } catch (error) {
console.error('❌ Error clearing assignments:', error); console.error('❌ Error clearing assignments:', error);
throw error; throw error;

View File

@@ -59,7 +59,13 @@ export default defineConfig(({ mode }) => {
resolve: { resolve: {
alias: { alias: {
'@': resolve(__dirname, './src'), '@': resolve(__dirname, './src'),
// ... other aliases '@/components': resolve(__dirname, './src/components'),
'@/pages': resolve(__dirname, './src/pages'),
'@/contexts': resolve(__dirname, './src/contexts'),
'@/models': resolve(__dirname, './src/models'),
'@/utils': resolve(__dirname, './src/utils'),
'@/services': resolve(__dirname, './src/services'),
'@/design': resolve(__dirname, './src/design')
} }
}, },