Spaces:
Sleeping
Sleeping
| 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), | |
| }); | |
| }; |