import apiClient from '@/lib/api/axios-instance'; import { AxiosError } from 'axios'; import { AssetAssignment, AssetAssignmentCreateRequest, AssetAssignmentUpdateRequest, validateAssetAssignmentApiResponse } from '@/types/asset-management'; import { assetApi } from './assetApi'; import { employeeApi } from './employeeApi'; // Helper to create a safe asset assignment object with default values for null/undefined fields const createSafeAssetAssignment = (data: any): AssetAssignment => { return { assignmentID: data.assignmentID || 0, assetID: data.assetID || 0, employeeID: data.employeeID || 0, employeeName: data.employeeName || '', assetCode: data.assetCode || '', assignedDate: data.assignedDate || new Date().toISOString(), returnedDate: data.returnedDate || null, remarks: data.remarks || '', }; }; // Helper to ensure dates are in ISO 8601 format const formatDateToISO = (dateString: string): string => { if (!dateString) return new Date().toISOString(); // If already in ISO format, return as is if (dateString.includes('T') && dateString.includes('Z')) { return dateString; } // If it's a date-only string, add time component if (dateString.match(/^\d{4}-\d{2}-\d{2}$/)) { return `${dateString}T12:00:00.000Z`; } // Try to parse and convert to ISO try { return new Date(dateString).toISOString(); } catch (error) { console.warn('AssetAssignmentApi: Invalid date format, using current date:', dateString); return new Date().toISOString(); } }; // Helper to handle API errors with user-friendly messages const handleApiError = (error: unknown, operation: string): never => { console.error(`AssetAssignmentApi: Error during ${operation}:`, error); if (error instanceof AxiosError) { const status = error.response?.status; const message = error.response?.data?.message || error.message; switch (status) { case 400: throw new Error(`Invalid request: ${message}`); case 401: throw new Error('You are not authorized to perform this action. Please log in again.'); case 403: throw new Error('You do not have permission to perform this action.'); case 404: throw new Error('The requested assignment was not found.'); case 409: throw new Error('This assignment conflicts with existing data or the asset is already assigned.'); case 422: throw new Error(`Validation failed: ${message}`); case 500: throw new Error('Server error occurred. Please try again later.'); case 503: throw new Error('Service is temporarily unavailable. Please try again later.'); default: throw new Error(`${operation} failed: ${message}`); } } if (error instanceof Error) { throw new Error(`${operation} failed: ${error.message}`); } throw new Error(`${operation} failed: Unknown error occurred`); }; // Helper for retry logic const retryOperation = async ( operation: () => Promise, maxRetries: number = 3, delay: number = 1000 ): Promise => { let lastError: unknown; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error; // Don't retry on client errors (4xx) except for 408 (timeout) and 429 (rate limit) if (error instanceof AxiosError) { const status = error.response?.status; if (status && status >= 400 && status < 500 && status !== 408 && status !== 429) { throw error; } } if (attempt === maxRetries) { throw lastError; } // Exponential backoff const waitTime = delay * Math.pow(2, attempt - 1); console.log(`AssetAssignmentApi: Retry attempt ${attempt}/${maxRetries} in ${waitTime}ms`); await new Promise(resolve => setTimeout(resolve, waitTime)); } } throw lastError; }; class AssetAssignmentApi { private baseUrl = `/api/AssetAssignments`; async getAll(): Promise { try { console.log('AssetAssignmentApi: Fetching all asset assignments from', this.baseUrl); const response = await apiClient.get(this.baseUrl); console.log('AssetAssignmentApi: Response status', response.status); if (!response.data) { console.warn('AssetAssignmentApi: No data in response'); return []; } if (!Array.isArray(response.data)) { console.warn('AssetAssignmentApi: Response is not an array', response.data); return []; } console.log(`AssetAssignmentApi: Found ${response.data.length} asset assignments`); // Map each item to ensure it has all required fields and validate return response.data.map(item => { try { return validateAssetAssignmentApiResponse(item); } catch (error) { console.warn('AssetAssignmentApi: Invalid asset assignment data, using safe fallback:', error); return createSafeAssetAssignment(item); } }); } catch (error) { console.error('AssetAssignmentApi: Error fetching asset assignments:', error); // Return empty array instead of throwing to prevent UI crashes return []; } } async getById(id: number): Promise { try { console.log(`AssetAssignmentApi: Fetching asset assignment ${id}`); const response = await apiClient.get(`${this.baseUrl}/${id}`); console.log(`AssetAssignmentApi: Response status for asset assignment ${id}`, response.status); if (response.data) { try { return validateAssetAssignmentApiResponse(response.data); } catch (error) { console.warn(`AssetAssignmentApi: Invalid asset assignment data for ID ${id}, using safe fallback:`, error); return createSafeAssetAssignment(response.data); } } return null; } catch (error) { console.error(`AssetAssignmentApi: Error fetching asset assignment ${id}:`, error); return null; } } async getByAssetId(assetId: number): Promise { try { console.log(`AssetAssignmentApi: Fetching asset assignments for asset ${assetId}`); const response = await apiClient.get(`${this.baseUrl}/by-asset/${assetId}`); console.log(`AssetAssignmentApi: Response status for asset ${assetId} assignments`, response.status); if (!response.data) { console.warn(`AssetAssignmentApi: No asset assignments found for asset ${assetId}`); return []; } if (!Array.isArray(response.data)) { console.warn(`AssetAssignmentApi: Response is not an array for asset ${assetId}`, response.data); return []; } console.log(`AssetAssignmentApi: Found ${response.data.length} asset assignments for asset ${assetId}`); // Map each item to ensure it has all required fields and validate return response.data.map(item => { try { return validateAssetAssignmentApiResponse(item); } catch (error) { console.warn(`AssetAssignmentApi: Invalid asset assignment data for asset ${assetId}, using safe fallback:`, error); return createSafeAssetAssignment(item); } }); } catch (error) { console.error(`AssetAssignmentApi: Error fetching asset assignments for asset ${assetId}:`, error); // Return empty array instead of throwing to prevent UI crashes return []; } } async getByEmployeeId(employeeId: number): Promise { try { console.log(`AssetAssignmentApi: Fetching asset assignments for employee ${employeeId}`); const response = await apiClient.get(`${this.baseUrl}/by-employee/${employeeId}`); console.log(`AssetAssignmentApi: Response status for employee ${employeeId} assignments`, response.status); if (!response.data) { console.warn(`AssetAssignmentApi: No asset assignments found for employee ${employeeId}`); return []; } if (!Array.isArray(response.data)) { console.warn(`AssetAssignmentApi: Response is not an array for employee ${employeeId}`, response.data); return []; } console.log(`AssetAssignmentApi: Found ${response.data.length} asset assignments for employee ${employeeId}`); // Map each item to ensure it has all required fields and validate return response.data.map(item => { try { return validateAssetAssignmentApiResponse(item); } catch (error) { console.warn(`AssetAssignmentApi: Invalid asset assignment data for employee ${employeeId}, using safe fallback:`, error); return createSafeAssetAssignment(item); } }); } catch (error) { console.error(`AssetAssignmentApi: Error fetching asset assignments for employee ${employeeId}:`, error); // Return empty array instead of throwing to prevent UI crashes return []; } } async create(assetAssignment: AssetAssignmentCreateRequest): Promise { return retryOperation(async () => { try { // Validate that the referenced asset exists const asset = await assetApi.getById(assetAssignment.assetID); if (!asset) { throw new Error(`Asset with ID ${assetAssignment.assetID} does not exist. Please select a valid asset.`); } // Validate that the referenced employee exists const employeeResponse = await employeeApi.getById(assetAssignment.employeeID); if (!employeeResponse.success || !employeeResponse.data) { throw new Error(`Employee with ID ${assetAssignment.employeeID} does not exist. Please select a valid employee.`); } // Format dates to ISO 8601 const assetAssignmentData = { ...assetAssignment, assignedDate: formatDateToISO(assetAssignment.assignedDate), returnedDate: assetAssignment.returnedDate ? formatDateToISO(assetAssignment.returnedDate) : null, }; console.log('AssetAssignmentApi: Creating asset assignment with data', assetAssignmentData); const response = await apiClient.post(this.baseUrl, assetAssignmentData); console.log('AssetAssignmentApi: Create response status', response.status); try { return validateAssetAssignmentApiResponse(response.data); } catch (error) { console.warn('AssetAssignmentApi: Invalid response data from create, using safe fallback:', error); return createSafeAssetAssignment(response.data); } } catch (error) { handleApiError(error, 'create asset assignment'); } }, 2); // Fewer retries for create operations } async update(id: number, assetAssignment: AssetAssignmentUpdateRequest): Promise { try { // Validate that the referenced asset exists const asset = await assetApi.getById(assetAssignment.assetID); if (!asset) { const errorMessage = `Asset with ID ${assetAssignment.assetID} does not exist`; console.error('AssetAssignmentApi:', errorMessage); throw new Error(errorMessage); } // Validate that the referenced employee exists const employeeResponse = await employeeApi.getById(assetAssignment.employeeID); if (!employeeResponse.success || !employeeResponse.data) { const errorMessage = `Employee with ID ${assetAssignment.employeeID} does not exist`; console.error('AssetAssignmentApi:', errorMessage); throw new Error(errorMessage); } // Format dates to ISO 8601 const assetAssignmentData = { ...assetAssignment, assignedDate: formatDateToISO(assetAssignment.assignedDate), returnedDate: assetAssignment.returnedDate ? formatDateToISO(assetAssignment.returnedDate) : null, }; console.log(`AssetAssignmentApi: Updating asset assignment ${id} with data`, assetAssignmentData); const response = await apiClient.put(`${this.baseUrl}/${id}`, assetAssignmentData); console.log(`AssetAssignmentApi: Update response status for asset assignment ${id}`, response.status); try { return validateAssetAssignmentApiResponse(response.data); } catch (error) { console.warn(`AssetAssignmentApi: Invalid response data from update for ID ${id}, using safe fallback:`, error); return createSafeAssetAssignment(response.data); } } catch (error) { console.error(`AssetAssignmentApi: Error updating asset assignment ${id}:`, error); throw error; } } async delete(id: number): Promise { try { console.log(`AssetAssignmentApi: Deleting asset assignment ${id}`); await apiClient.delete(`${this.baseUrl}/${id}`); console.log(`AssetAssignmentApi: Asset assignment ${id} deleted successfully`); } catch (error) { console.error(`AssetAssignmentApi: Error deleting asset assignment ${id}:`, error); throw error; } } async getAssignmentHistory(assetId: number): Promise<{ success: boolean; data: Array; totalAssignments: number; activeAssignments: number; completedAssignments: number; totalDaysAssigned: number; message: string; }> { return retryOperation(async () => { try { console.log(`AssetAssignmentApi: Fetching assignment history for asset ${assetId}`); // Use the history by-asset endpoint const response = await apiClient.get(`${this.baseUrl}/history/by-asset/${assetId}`); console.log(`AssetAssignmentApi: Response status for asset ${assetId} assignment history`, response.status); if (!response.data) { console.warn(`AssetAssignmentApi: No assignment history found for asset ${assetId}`); return { success: false, data: [], totalAssignments: 0, activeAssignments: 0, completedAssignments: 0, totalDaysAssigned: 0, message: `No assignment history found for asset ${assetId}` }; } // Check if response already has the expected structure (from your curl example) if (response.data.success !== undefined && response.data.data !== undefined) { console.log(`AssetAssignmentApi: Found assignment history for asset ${assetId}:`, response.data); return response.data; } // If it's just an array, transform it to match the expected structure const assignments = Array.isArray(response.data) ? response.data : []; const activeAssignments = assignments.filter((assignment: any) => !assignment.returnedDate).length; const completedAssignments = assignments.filter((assignment: any) => assignment.returnedDate).length; // Calculate total days assigned for completed assignments const totalDaysAssigned = assignments.reduce((total: number, assignment: any) => { if (assignment.returnedDate && assignment.assignedDate) { const assignedDate = new Date(assignment.assignedDate); const returnedDate = new Date(assignment.returnedDate); const diffTime = Math.abs(returnedDate.getTime() - assignedDate.getTime()); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); return total + diffDays; } return total; }, 0); console.log(`AssetAssignmentApi: Found ${assignments.length} assignments for asset ${assetId}`); return { success: true, data: assignments.map((assignment: any) => ({ ...assignment, assignmentStatus: assignment.returnedDate ? 'Returned' : 'Active', assignmentDuration: assignment.returnedDate && assignment.assignedDate ? Math.ceil((new Date(assignment.returnedDate).getTime() - new Date(assignment.assignedDate).getTime()) / (1000 * 60 * 60 * 24)) : null, returnReason: assignment.returnReason || null })), totalAssignments: assignments.length, activeAssignments, completedAssignments, totalDaysAssigned, message: 'Assignment history retrieved successfully' }; } catch (error) { if (error instanceof AxiosError && error.response?.status === 404) { console.log(`AssetAssignmentApi: No assignment history found for asset ${assetId}`); return { success: false, data: [], totalAssignments: 0, activeAssignments: 0, completedAssignments: 0, totalDaysAssigned: 0, message: `No assignment history found for asset ${assetId}` }; } handleApiError(error, `fetch assignment history for asset ${assetId}`); } }); } async getActiveAssignments(): Promise<{ success: boolean; data: Array; totalActiveAssignments: number; overdueAssignments: number; assignmentsByDepartment: Record; message: string; }> { return retryOperation(async () => { try { console.log('AssetAssignmentApi: Fetching all active assignments'); const response = await apiClient.get(`${this.baseUrl}/active`); console.log('AssetAssignmentApi: Response status for active assignments', response.status); if (!response.data) { console.warn('AssetAssignmentApi: No active assignments found'); return { success: false, data: [], totalActiveAssignments: 0, overdueAssignments: 0, assignmentsByDepartment: {}, message: 'No active assignments found' }; } console.log('AssetAssignmentApi: Found active assignments:', response.data); // Return the response as-is since it should match the expected structure return response.data; } catch (error) { if (error instanceof AxiosError && error.response?.status === 404) { console.log('AssetAssignmentApi: No active assignments found'); return { success: false, data: [], totalActiveAssignments: 0, overdueAssignments: 0, assignmentsByDepartment: {}, message: 'No active assignments found' }; } handleApiError(error, 'fetch active assignments'); } }); } async bulkReturn(request: { assignmentIds: number[]; returnedDate: string; returnReason?: string; remarks?: string; performedBy?: { employeeID: number; name: string; }; }): Promise<{ success: boolean; data: { processedAssignments: number; successfulReturns: number; failedReturns: number; results: Array<{ assignmentID: number; status: 'success' | 'failed'; message: string; returnedAssignment?: AssetAssignment; error?: string; }>; }; summary: { totalRequested: number; successful: number; failed: number; assetsReturned: number; employeesAffected: number; }; message: string; }> { return retryOperation(async () => { try { // Validate input if (!Array.isArray(request.assignmentIds) || request.assignmentIds.length === 0) { throw new Error('Assignment IDs array is required and cannot be empty'); } if (request.assignmentIds.length > 100) { throw new Error('Cannot return more than 100 assignments at once'); } // Format the request data const requestData = { assignmentIds: request.assignmentIds, returnedDate: formatDateToISO(request.returnedDate), returnReason: request.returnReason || 'Bulk return operation', remarks: request.remarks || 'Bulk returned via system', performedBy: request.performedBy }; console.log(`AssetAssignmentApi: Bulk returning ${request.assignmentIds.length} assignments`, requestData); const response = await apiClient.post(`${this.baseUrl}/bulk-return`, requestData); console.log('AssetAssignmentApi: Bulk return response status', response.status); console.log('AssetAssignmentApi: Bulk return response data', response.data); // Validate response structure if (!response.data || typeof response.data !== 'object') { throw new Error('Invalid response format from bulk return API'); } const result = response.data; // Ensure required fields exist if (typeof result.success !== 'boolean') { throw new Error('Invalid response: missing success field'); } console.log(`AssetAssignmentApi: Bulk return completed - ${result.data?.successfulReturns || 0} successful, ${result.data?.failedReturns || 0} failed`); return result; } catch (error) { handleApiError(error, 'bulk return assignments'); } }, 1); // No retries for bulk operations to avoid duplicate operations } } export const assetAssignmentApi = new AssetAssignmentApi();