import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { assetApi } from '@/services/assetApi'; import { Asset, AssetCreateRequest, AssetUpdateRequest } from '@/types/asset-management'; import { toast } from '@/lib/custom-toast'; // Query keys for asset-related queries export const assetQueryKeys = { all: ['assets'] as const, lists: () => [...assetQueryKeys.all, 'list'] as const, list: (filters?: any) => [...assetQueryKeys.lists(), { filters }] as const, details: () => [...assetQueryKeys.all, 'detail'] as const, detail: (id: number) => [...assetQueryKeys.details(), id] as const, }; /** * Hook for fetching all assets */ export const useAssets = () => { return useQuery({ queryKey: assetQueryKeys.lists(), queryFn: () => assetApi.getAll(), staleTime: 5 * 60 * 1000, // 5 minutes gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime) }); }; /** * Hook for fetching a single asset by ID */ export const useAsset = (id: number) => { return useQuery({ queryKey: assetQueryKeys.detail(id), queryFn: () => assetApi.getById(id), staleTime: 5 * 60 * 1000, // 5 minutes gcTime: 10 * 60 * 1000, // 10 minutes enabled: !!id && id > 0, // Only run query if ID is valid }); }; /** * Hook for creating a new asset */ export const useCreateAsset = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (asset: AssetCreateRequest) => assetApi.create(asset), onSuccess: (newAsset) => { // Invalidate and refetch assets list queryClient.invalidateQueries({ queryKey: assetQueryKeys.lists() }); // Optionally add the new asset to the cache queryClient.setQueryData(assetQueryKeys.detail(newAsset.assetID), newAsset); toast.success('Asset created successfully', { description: `Asset ${newAsset.brandModel} (${newAsset.serialNumber}) has been created.` }); }, onError: (error) => { console.error('Create asset error:', error); const errorMessage = error instanceof Error ? error.message : 'Failed to create asset'; toast.error('Failed to create asset', { description: errorMessage, action: { label: 'Retry', onClick: () => { // The retry will be handled by the form submission } } }); }, retry: (failureCount, error) => { // Don't retry on validation errors (4xx status codes) if (error instanceof Error && error.message.includes('Validation failed')) { return false; } return failureCount < 2; }, retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), }); }; /** * Hook for updating an existing asset */ export const useUpdateAsset = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, asset }: { id: number; asset: AssetUpdateRequest }) => assetApi.update(id, asset), onMutate: async ({ id, asset }) => { // Cancel any outgoing refetches await queryClient.cancelQueries({ queryKey: assetQueryKeys.detail(id) }); // Snapshot the previous value const previousAsset = queryClient.getQueryData(assetQueryKeys.detail(id)); // Optimistically update to the new value queryClient.setQueryData(assetQueryKeys.detail(id), asset); // Return a context object with the snapshotted value return { previousAsset, id }; }, onError: (error, variables, context) => { // If the mutation fails, use the context returned from onMutate to roll back if (context?.previousAsset) { queryClient.setQueryData(assetQueryKeys.detail(context.id), context.previousAsset); } console.error('Update asset error:', error); const errorMessage = error instanceof Error ? error.message : 'Failed to update asset'; toast.error('Failed to update asset', { description: errorMessage, action: { label: 'Retry', onClick: () => { // The retry will be handled by the form submission } } }); }, onSuccess: (updatedAsset, { id }) => { // Update the asset in the cache queryClient.setQueryData(assetQueryKeys.detail(id), updatedAsset); // Invalidate assets list to ensure consistency queryClient.invalidateQueries({ queryKey: assetQueryKeys.lists() }); toast.success('Asset updated successfully', { description: `Asset ${updatedAsset.brandModel} (${updatedAsset.serialNumber}) has been updated.` }); }, retry: (failureCount, error) => { // Don't retry on validation errors (4xx status codes) if (error instanceof Error && error.message.includes('Validation failed')) { return false; } return failureCount < 2; }, retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), }); }; /** * Hook for deleting an asset */ export const useDeleteAsset = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: number) => assetApi.delete(id), onMutate: async (id) => { // Cancel any outgoing refetches await queryClient.cancelQueries({ queryKey: assetQueryKeys.detail(id) }); await queryClient.cancelQueries({ queryKey: assetQueryKeys.lists() }); // Snapshot the previous values const previousAsset = queryClient.getQueryData(assetQueryKeys.detail(id)); const previousAssets = queryClient.getQueryData(assetQueryKeys.lists()); // Optimistically remove the asset from the list if (previousAssets) { queryClient.setQueryData( assetQueryKeys.lists(), (previousAssets as Asset[]).filter(asset => asset.assetID !== id) ); } // Return a context object with the snapshotted values return { previousAsset, previousAssets, id }; }, onError: (error, id, context) => { // If the mutation fails, use the context to roll back if (context?.previousAssets) { queryClient.setQueryData(assetQueryKeys.lists(), context.previousAssets); } if (context?.previousAsset) { queryClient.setQueryData(assetQueryKeys.detail(id), context.previousAsset); } console.error('Delete asset error:', error); const errorMessage = error instanceof Error ? error.message : 'Failed to delete asset'; toast.error('Failed to delete asset', { description: errorMessage, action: { label: 'Retry', onClick: () => { // The retry will be handled by calling the delete function again } } }); }, onSuccess: (_, id, context) => { // Remove the asset from the cache queryClient.removeQueries({ queryKey: assetQueryKeys.detail(id) }); // Invalidate assets list to ensure consistency queryClient.invalidateQueries({ queryKey: assetQueryKeys.lists() }); const assetInfo = context?.previousAsset as Asset; toast.success('Asset deleted successfully', { description: assetInfo ? `Asset ${assetInfo.brandModel} (${assetInfo.serialNumber}) has been deleted.` : undefined }); }, retry: (failureCount, error) => { // Don't retry on validation errors or not found errors if (error instanceof Error && ( error.message.includes('not found') || error.message.includes('Validation failed') )) { return false; } return failureCount < 2; }, retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), }); };