From 41ddad6fa995d5e4de5e3884fbdd37059dbca757 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 1 Nov 2025 13:47:29 +0100 Subject: [PATCH] added apiClient class handling api auth and validation response from backend --- backend/src/server.ts | 85 +++---- frontend/src/services/apiClient.ts | 130 +++++++++++ frontend/src/services/authService.ts | 60 +---- frontend/src/services/employeeService.ts | 128 ++--------- .../src/services/shiftAssignmentService.ts | 110 +-------- frontend/src/services/shiftPlanService.ts | 214 +++++------------- frontend/vite.config.ts | 8 +- 7 files changed, 267 insertions(+), 468 deletions(-) create mode 100644 frontend/src/services/apiClient.ts diff --git a/backend/src/server.ts b/backend/src/server.ts index 1cb3c22..bf07187 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -51,39 +51,26 @@ if (isDevelopment) { } const configureStaticFiles = () => { - const possiblePaths = [ - // Production path (Docker) - '/app/frontend-build', - // Development paths - path.resolve(__dirname, '../../frontend/dist'), - 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')] : []) - ]; + const staticConfig = { + maxAge: '1y', + etag: false, + immutable: true, + index: false + }; - for (const testPath of possiblePaths) { - try { - if (fs.existsSync(testPath)) { - // In development, check for dist or direct source - if (fs.existsSync(path.join(testPath, 'index.html'))) { - 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'))) { - console.log('πŸ”§ Development: Serving frontend source from:', testPath); - app.use(express.static(testPath)); - return testPath; - } - } - } catch (error) { - // Silent catch - } + // Serve frontend build + const frontendPath = '/app/frontend-build'; + if (fs.existsSync(frontendPath)) { + console.log('βœ… Serving frontend from:', frontendPath); + app.use(express.static(frontendPath, staticConfig)); + } + + // Serve premium assets if available + const premiumPath = '/app/premium-dist'; + if (fs.existsSync(premiumPath)) { + console.log('βœ… Serving premium assets from:', premiumPath); + app.use('/premium-assets', express.static(premiumPath, staticConfig)); } - return null; }; // Security configuration @@ -225,7 +212,7 @@ const findFrontendBuildPath = (): string | null => { }; const frontendBuildPath = findFrontendBuildPath(); -const staticPath = configureStaticFiles(); +configureStaticFiles(); if (frontendBuildPath) { app.use(express.static(frontendBuildPath)); @@ -264,32 +251,26 @@ app.get('/', async (req, res) => { }); // Client-side routing fallback -app.get('*', async (req, res, next) => { +app.get('*', (req, res, next) => { + // Skip API routes if (req.path.startsWith('/api/')) { - return next(); // API routes handled normally + return next(); } - // Vite dev server handling - if (vite) { - try { - 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); - } + // Skip file extensions (assets) + if (req.path.match(/\.[a-z0-9]+$/i)) { + return next(); } - // Static file fallback - if (staticPath) { - const indexPath = path.join(staticPath, 'index.html'); - return res.sendFile(indexPath); + // Serve React app for all other routes + const frontendPath = '/app/frontend-build'; + const indexPath = path.join(frontendPath, 'index.html'); + + if (fs.existsSync(indexPath)) { + res.sendFile(indexPath); + } else { + res.status(404).send('Frontend not available'); } - - res.status(500).send('Frontend not available'); }); // Error handling diff --git a/frontend/src/services/apiClient.ts b/frontend/src/services/apiClient.ts new file mode 100644 index 0000000..ffb1889 --- /dev/null +++ b/frontend/src/services/apiClient.ts @@ -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(response: Response): Promise { + 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(endpoint: string, options: RequestInit = {}): Promise { + 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(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 = (endpoint: string) => this.request(endpoint); + + post = (endpoint: string, data?: any) => + this.request(endpoint, { + method: 'POST', + body: data ? JSON.stringify(data) : undefined + }); + + put = (endpoint: string, data?: any) => + this.request(endpoint, { + method: 'PUT', + body: data ? JSON.stringify(data) : undefined + }); + + patch = (endpoint: string, data?: any) => + this.request(endpoint, { + method: 'PATCH', + body: data ? JSON.stringify(data) : undefined + }); + + delete = (endpoint: string) => + this.request(endpoint, { method: 'DELETE' }); +} + +export const apiClient = new ApiClient(); \ No newline at end of file diff --git a/frontend/src/services/authService.ts b/frontend/src/services/authService.ts index 55bd5b6..d1e7631 100644 --- a/frontend/src/services/authService.ts +++ b/frontend/src/services/authService.ts @@ -1,8 +1,5 @@ -// frontend/src/services/authService.ts - UPDATED import { Employee } from '../models/Employee'; -import { ErrorService } from './errorService'; - -const API_BASE_URL = import.meta.env.VITE_API_URL || '/api'; +import { apiClient } from './apiClient'; export interface LoginRequest { email: string; @@ -25,31 +22,8 @@ export interface AuthResponse { class AuthService { private token: string | null = null; - private async handleApiResponse(response: Response): Promise { - 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 { - const response = await fetch(`${API_BASE_URL}/auth/login`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(credentials) - }); - - const data = await this.handleApiResponse(response); + const data = await apiClient.post('/auth/login', credentials); this.token = data.token; localStorage.setItem('token', data.token); localStorage.setItem('employee', JSON.stringify(data.employee)); @@ -57,13 +31,7 @@ class AuthService { } async register(userData: RegisterRequest): Promise { - const response = await fetch(`${API_BASE_URL}/employees`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(userData) - }); - - const data = await this.handleApiResponse(response); + await apiClient.post('/employees', userData); return this.login({ email: userData.email, password: userData.password @@ -77,28 +45,16 @@ class AuthService { async fetchCurrentEmployee(): Promise { const token = this.getToken(); - if (!token) { - return null; - } + if (!token) return null; try { - const response = await fetch(`${API_BASE_URL}/auth/me`, { - headers: { - 'Authorization': `Bearer ${token}` - } - }); - - if (response.ok) { - const data = await response.json(); - const user = data.user; - localStorage.setItem('user', JSON.stringify(user)); - return user; - } + const data = await apiClient.get<{ user: Employee }>('/auth/me'); + localStorage.setItem('user', JSON.stringify(data.user)); + return data.user; } catch (error) { console.error('Error fetching current user:', error); + return null; } - - return null; } logout(): void { diff --git a/frontend/src/services/employeeService.ts b/frontend/src/services/employeeService.ts index 3ebbd3b..4ba817f 100644 --- a/frontend/src/services/employeeService.ts +++ b/frontend/src/services/employeeService.ts @@ -1,138 +1,58 @@ -// frontend/src/services/employeeService.ts import { Employee, CreateEmployeeRequest, UpdateEmployeeRequest, EmployeeAvailability } from '../models/Employee'; -import { ErrorService, ValidationError } from './errorService'; - -const API_BASE_URL = '/api'; - -const getAuthHeaders = () => { - const token = localStorage.getItem('token'); - return { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}` - }; -}; +import { apiClient } from './apiClient'; export class EmployeeService { - private async handleApiResponse(response: Response): Promise { - 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 { console.log('πŸ”„ Fetching employees from API...'); - const token = localStorage.getItem('token'); - console.log('πŸ”‘ Token exists:', !!token); - - 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'); + try { + const employees = await apiClient.get(`/employees?includeInactive=${includeInactive}`); + console.log('βœ… Employees received:', employees.length); + return employees; + } catch (error) { + console.error('❌ Error fetching employees:', error); + throw error; // Let useBackendValidation handle this } - - const employees = await response.json(); - console.log('βœ… Employees received:', employees.length); - - return employees; } async getEmployee(id: string): Promise { - const response = await fetch(`${API_BASE_URL}/employees/${id}`, { - headers: getAuthHeaders(), - }); - - return this.handleApiResponse(response); + return apiClient.get(`/employees/${id}`); } async createEmployee(employee: CreateEmployeeRequest): Promise { - const response = await fetch(`${API_BASE_URL}/employees`, { - method: 'POST', - headers: getAuthHeaders(), - body: JSON.stringify(employee), - }); - - return this.handleApiResponse(response); + return apiClient.post('/employees', employee); } async updateEmployee(id: string, employee: UpdateEmployeeRequest): Promise { - const response = await fetch(`${API_BASE_URL}/employees/${id}`, { - method: 'PUT', - headers: getAuthHeaders(), - body: JSON.stringify(employee), - }); - - return this.handleApiResponse(response); + return apiClient.put(`/employees/${id}`, employee); } async deleteEmployee(id: string): Promise { - const response = await fetch(`${API_BASE_URL}/employees/${id}`, { - method: 'DELETE', - headers: getAuthHeaders(), - }); - - if (!response.ok) { - const error = await response.json(); - throw new Error(error.error || 'Failed to delete employee'); - } + await apiClient.delete(`/employees/${id}`); } async getAvailabilities(employeeId: string): Promise { - const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, { - headers: getAuthHeaders(), - }); - - return this.handleApiResponse(response); + return apiClient.get(`/employees/${employeeId}/availabilities`); } - async updateAvailabilities(employeeId: string, data: { planId: string, availabilities: Omit[] }): Promise { + async updateAvailabilities( + employeeId: string, + data: { planId: string, availabilities: Omit[] } + ): Promise { console.log('πŸ”„ Updating availabilities for employee:', employeeId); - const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, { - method: 'PUT', - headers: getAuthHeaders(), - body: JSON.stringify(data), - }); - - return this.handleApiResponse(response); + return apiClient.put(`/employees/${employeeId}/availabilities`, data); } - async changePassword(id: string, data: { currentPassword: string, newPassword: string, confirmPassword: string }): Promise { - const response = await fetch(`${API_BASE_URL}/employees/${id}/password`, { - method: 'PUT', - headers: getAuthHeaders(), - body: JSON.stringify(data), - }); - - return this.handleApiResponse(response); + async changePassword( + id: string, + data: { currentPassword: string, newPassword: string, confirmPassword: string } + ): Promise { + return apiClient.put(`/employees/${id}/password`, data); } async updateLastLogin(employeeId: string): Promise { try { - const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/last-login`, { - method: 'PATCH', - headers: getAuthHeaders(), - }); - - if (!response.ok) { - throw new Error('Failed to update last login'); - } + await apiClient.patch(`/employees/${employeeId}/last-login`); } catch (error) { console.error('Error updating last login:', error); throw error; diff --git a/frontend/src/services/shiftAssignmentService.ts b/frontend/src/services/shiftAssignmentService.ts index 119ba69..ead059f 100644 --- a/frontend/src/services/shiftAssignmentService.ts +++ b/frontend/src/services/shiftAssignmentService.ts @@ -1,65 +1,15 @@ -// frontend/src/services/shiftAssignmentService.ts - WEEKLY PATTERN VERSION import { ShiftPlan, ScheduledShift } from '../models/ShiftPlan'; import { Employee, EmployeeAvailability } from '../models/Employee'; -import { authService } from './authService'; import { AssignmentResult, ScheduleRequest } from '../models/scheduling'; - -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}` }) - }; -}; +import { apiClient } from './apiClient'; export class ShiftAssignmentService { async updateScheduledShift(id: string, updates: { assignedEmployees: string[] }): Promise { 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}`, { - method: 'PUT', - 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); + await apiClient.put(`/scheduled-shifts/${id}`, updates); + console.log('βœ… Scheduled shift updated successfully'); } catch (error) { console.error('❌ Error updating scheduled shift:', error); @@ -69,48 +19,16 @@ export class ShiftAssignmentService { async getScheduledShift(id: string): Promise { try { - const response = await fetch(`${API_BASE_URL}/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) : {}; + return await apiClient.get(`/scheduled-shifts/${id}`); } catch (error) { console.error('Error fetching scheduled shift:', error); throw error; } } - // New method to get all scheduled shifts for a plan async getScheduledShiftsForPlan(planId: string): Promise { try { - const response = await fetch(`${API_BASE_URL}/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(); + const shifts = await apiClient.get(`/scheduled-shifts/plan/${planId}`); // DEBUG: Check the structure of returned shifts console.log('πŸ” SCHEDULED SHIFTS STRUCTURE:', shifts.slice(0, 3)); @@ -132,21 +50,7 @@ export class ShiftAssignmentService { } private async callSchedulingAPI(request: ScheduleRequest): Promise { - const response = await fetch(`${API_BASE_URL}/scheduling/generate-schedule`, { - 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(); + return await apiClient.post('/scheduling/generate-schedule', request); } async assignShifts( diff --git a/frontend/src/services/shiftPlanService.ts b/frontend/src/services/shiftPlanService.ts index 3b2b84f..ae85dec 100644 --- a/frontend/src/services/shiftPlanService.ts +++ b/frontend/src/services/shiftPlanService.ts @@ -1,199 +1,115 @@ -// frontend/src/services/shiftPlanService.ts -import { authService } from './authService'; import { ShiftPlan, CreateShiftPlanRequest } from '../models/ShiftPlan'; -import { TEMPLATE_PRESETS } from '../models/defaults/shiftPlanDefaults'; - -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(); -}; +import { TEMPLATE_PRESETS } from '../models/defaults/shiftPlanDefaults'; +import { apiClient } from './apiClient'; export const shiftPlanService = { async getShiftPlans(): Promise { - const response = await fetch(API_BASE_URL, { - headers: { - 'Content-Type': 'application/json', - ...authService.getAuthHeaders() - } - }); - - if (!response.ok) { - if (response.status === 401) { - authService.logout(); + try { + const plans = await apiClient.get('/shift-plans'); + + // Ensure scheduledShifts is always an array + return plans.map((plan: any) => ({ + ...plan, + 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'); } - - const plans = await response.json(); - - // Ensure scheduledShifts is always an array - return plans.map((plan: any) => ({ - ...plan, - scheduledShifts: plan.scheduledShifts || [] - })); }, async getShiftPlan(id: string): Promise { - const response = await fetch(`${API_BASE_URL}/${id}`, { - headers: { - 'Content-Type': 'application/json', - ...authService.getAuthHeaders() - } - }); - - if (!response.ok) { - if (response.status === 401) { - authService.logout(); + try { + return await apiClient.get(`/shift-plans/${id}`); + } catch (error: any) { + if (error.statusCode === 401) { + localStorage.removeItem('token'); + localStorage.removeItem('employee'); throw new Error('Nicht authorisiert - bitte erneut anmelden'); } throw new Error('Schichtplan nicht gefunden'); } - - return await response.json(); }, async createShiftPlan(plan: CreateShiftPlanRequest): Promise { - const response = await fetch(API_BASE_URL, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...authService.getAuthHeaders() - }, - body: JSON.stringify(plan) - }); - - if (!response.ok) { - if (response.status === 401) { - authService.logout(); + try { + return await apiClient.post('/shift-plans', plan); + } catch (error: any) { + if (error.statusCode === 401) { + localStorage.removeItem('token'); + localStorage.removeItem('employee'); throw new Error('Nicht authorisiert - bitte erneut anmelden'); } throw new Error('Fehler beim Erstellen des Schichtplans'); } - - return response.json(); }, async updateShiftPlan(id: string, plan: Partial): Promise { - const response = await fetch(`${API_BASE_URL}/${id}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - ...authService.getAuthHeaders() - }, - body: JSON.stringify(plan) - }); - - if (!response.ok) { - if (response.status === 401) { - authService.logout(); + try { + return await apiClient.put(`/shift-plans/${id}`, plan); + } catch (error: any) { + if (error.statusCode === 401) { + localStorage.removeItem('token'); + localStorage.removeItem('employee'); throw new Error('Nicht authorisiert - bitte erneut anmelden'); } throw new Error('Fehler beim Aktualisieren des Schichtplans'); } - - return response.json(); }, async deleteShiftPlan(id: string): Promise { - const response = await fetch(`${API_BASE_URL}/${id}`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - ...authService.getAuthHeaders() - } - }); - - if (!response.ok) { - if (response.status === 401) { - authService.logout(); + try { + await apiClient.delete(`/shift-plans/${id}`); + } catch (error: any) { + if (error.statusCode === 401) { + localStorage.removeItem('token'); + localStorage.removeItem('employee'); throw new Error('Nicht authorisiert - bitte erneut anmelden'); } throw new Error('Fehler beim LΓΆschen des Schichtplans'); } }, - // Get specific template or plan - getTemplate: async (id: string): Promise => { - const response = await fetch(`${API_BASE_URL}/${id}`, { - headers: getAuthHeaders() - }); - return handleResponse(response); + async getTemplate(id: string): Promise { + return await apiClient.get(`/shift-plans/${id}`); }, - - async regenerateScheduledShifts(planId: string):Promise { + async regenerateScheduledShifts(planId: string): Promise { try { - console.log('πŸ”„ Attempting to regenerate scheduled 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'); - } else { - console.error('❌ Failed to regenerate shifts'); - } + console.log('πŸ”„ Attempting to regenerate scheduled shifts...'); + await apiClient.post(`/shift-plans/${planId}/regenerate-shifts`); + console.log('βœ… Scheduled shifts regenerated'); } catch (error) { - console.error('❌ Error regenerating shifts:', error); + console.error('❌ Error regenerating shifts:', error); + throw error; } }, - // Create new plan - createPlan: async (data: CreateShiftPlanRequest): Promise => { - const response = await fetch(`${API_BASE_URL}`, { - method: 'POST', - headers: getAuthHeaders(), - body: JSON.stringify(data), - }); - return handleResponse(response); + async createPlan(data: CreateShiftPlanRequest): Promise { + return await apiClient.post('/shift-plans', data); }, - createFromPreset: async (data: { + async createFromPreset(data: { presetName: string; name: string; startDate: string; endDate: string; isTemplate?: boolean; - }): Promise => { - const response = await fetch(`${API_BASE_URL}/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}`); + }): Promise { + try { + return await apiClient.post('/shift-plans/from-preset', data); + } catch (error: any) { + throw new Error(error.message || `HTTP error! status: ${error.statusCode}`); } - - return response.json(); }, - getTemplatePresets: async (): Promise<{name: string, label: string, description: string}[]> => { - // name = label - return Object.entries(TEMPLATE_PRESETS).map(([key, preset]) => ({ + async getTemplatePresets(): Promise<{name: string, label: string, description: string}[]> { + return Object.entries(TEMPLATE_PRESETS).map(([key, preset]) => ({ name: key, label: preset.name, description: preset.description @@ -203,22 +119,8 @@ export const shiftPlanService = { async clearAssignments(planId: string): Promise { try { console.log('πŸ”„ Clearing assignments for plan:', planId); - - 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}`); - } - + await apiClient.post(`/shift-plans/${planId}/clear-assignments`); console.log('βœ… Assignments cleared successfully'); - } catch (error) { console.error('❌ Error clearing assignments:', error); throw error; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index f541246..e2ff166 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -59,7 +59,13 @@ export default defineConfig(({ mode }) => { resolve: { alias: { '@': 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') } },