import { useState, useCallback } from 'react'; import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; import { ApiResponse, PaginatedResponse, FilterOptions } from '../types'; export interface ApiServiceConfig { baseURL: string; defaultHeaders?: Record; timeout?: number; } export interface UseApiServiceReturn { data: T[]; loading: boolean; error: string | null; total: number; hasMore: boolean; client: AxiosInstance; // CRUD operations getAll: (filters?: FilterOptions) => Promise; getById: (id: string) => Promise; create: (data: Partial) => Promise; update: (id: string, data: Partial) => Promise; delete: (id: string) => Promise; // Utility methods clearError: () => void; refresh: () => Promise; } export const useApiService = ( config: ApiServiceConfig, endpoint: string ): UseApiServiceReturn => { const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [total, setTotal] = useState(0); const [hasMore, setHasMore] = useState(false); // Create axios instance const client = axios.create({ baseURL: config.baseURL, timeout: config.timeout || 10000, headers: { 'Content-Type': 'application/json', ...config.defaultHeaders, }, }); // Add request interceptor for common headers client.interceptors.request.use( (config) => { // Add user email header if available (common pattern in the codebase) const userEmail = 'admin@example.com'; // This could come from a context or config if (userEmail) { config.headers['X-User-Email'] = userEmail; } return config; }, (error) => Promise.reject(error) ); // Add response interceptor for error handling client.interceptors.response.use( (response) => response, (error) => { const errorMessage = error.response?.data?.message || error.message || 'An error occurred'; setError(errorMessage); return Promise.reject(error); } ); const clearError = useCallback(() => { setError(null); }, []); const getAll = useCallback(async (filters: FilterOptions = {}): Promise => { setLoading(true); setError(null); try { const params = new URLSearchParams(); Object.entries(filters).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== '') { params.append(key, value.toString()); } }); const response = await client.get | ApiResponse>(`${endpoint}?${params.toString()}`); // Handle both paginated and simple array responses if ('data' in response.data && Array.isArray(response.data.data)) { // Paginated response const paginatedData = response.data as PaginatedResponse; setData(paginatedData.data); setTotal(paginatedData.total); setHasMore(paginatedData.has_more || false); return paginatedData.data; } else if ('data' in response.data && Array.isArray(response.data.data)) { // Simple array response wrapped in ApiResponse const apiData = response.data as ApiResponse; setData(apiData.data); setTotal(apiData.data.length); setHasMore(false); return apiData.data; } else if (Array.isArray(response.data)) { // Direct array response setData(response.data); setTotal(response.data.length); setHasMore(false); return response.data; } else { throw new Error('Invalid response format'); } } catch (err: any) { const errorMessage = err.response?.data?.message || err.message || 'Failed to fetch data'; setError(errorMessage); throw err; } finally { setLoading(false); } }, [client, endpoint]); const getById = useCallback(async (id: string): Promise => { setLoading(true); setError(null); try { const response = await client.get | T>(`${endpoint}/${id}`); const item = 'data' in response.data ? response.data.data : response.data; return item; } catch (err: any) { const errorMessage = err.response?.data?.message || err.message || 'Failed to fetch item'; setError(errorMessage); throw err; } finally { setLoading(false); } }, [client, endpoint]); const create = useCallback(async (itemData: Partial): Promise => { setLoading(true); setError(null); try { const response = await client.post | T>(endpoint, itemData); const newItem = 'data' in response.data ? response.data.data : response.data; // Update local data setData(prev => [...prev, newItem]); setTotal(prev => prev + 1); return newItem; } catch (err: any) { const errorMessage = err.response?.data?.message || err.message || 'Failed to create item'; setError(errorMessage); throw err; } finally { setLoading(false); } }, [client, endpoint]); const update = useCallback(async (id: string, itemData: Partial): Promise => { setLoading(true); setError(null); try { const response = await client.put | T>(`${endpoint}/${id}`, itemData); const updatedItem = 'data' in response.data ? response.data.data : response.data; // Update local data setData(prev => prev.map(item => item.id === id ? updatedItem : item)); return updatedItem; } catch (err: any) { const errorMessage = err.response?.data?.message || err.message || 'Failed to update item'; setError(errorMessage); throw err; } finally { setLoading(false); } }, [client, endpoint]); const deleteItem = useCallback(async (id: string): Promise => { setLoading(true); setError(null); try { await client.delete(`${endpoint}/${id}`); // Update local data setData(prev => prev.filter(item => item.id !== id)); setTotal(prev => prev - 1); } catch (err: any) { const errorMessage = err.response?.data?.message || err.message || 'Failed to delete item'; setError(errorMessage); throw err; } finally { setLoading(false); } }, [client, endpoint]); const refresh = useCallback(async () => { await getAll(); }, [getAll]); return { data, loading, error, total, hasMore, client, getAll, getById, create, update, delete: deleteItem, clearError, refresh, }; };