Add frontend imporvements
This commit is contained in:
323
frontend/src/api/hooks/forecasting.ts
Normal file
323
frontend/src/api/hooks/forecasting.ts
Normal file
@@ -0,0 +1,323 @@
|
||||
/**
|
||||
* Forecasting React Query hooks
|
||||
*/
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
UseQueryOptions,
|
||||
UseMutationOptions,
|
||||
useInfiniteQuery,
|
||||
UseInfiniteQueryOptions,
|
||||
} from '@tanstack/react-query';
|
||||
import { forecastingService } from '../services/forecasting';
|
||||
import {
|
||||
ForecastRequest,
|
||||
ForecastResponse,
|
||||
BatchForecastRequest,
|
||||
BatchForecastResponse,
|
||||
ForecastListResponse,
|
||||
ForecastByIdResponse,
|
||||
ForecastStatistics,
|
||||
DeleteForecastResponse,
|
||||
GetForecastsParams,
|
||||
ForecastingHealthResponse,
|
||||
} from '../types/forecasting';
|
||||
import { ApiError } from '../client/apiClient';
|
||||
|
||||
// ================================================================
|
||||
// QUERY KEYS
|
||||
// ================================================================
|
||||
|
||||
export const forecastingKeys = {
|
||||
all: ['forecasting'] as const,
|
||||
lists: () => [...forecastingKeys.all, 'list'] as const,
|
||||
list: (tenantId: string, filters?: GetForecastsParams) =>
|
||||
[...forecastingKeys.lists(), tenantId, filters] as const,
|
||||
details: () => [...forecastingKeys.all, 'detail'] as const,
|
||||
detail: (tenantId: string, forecastId: string) =>
|
||||
[...forecastingKeys.details(), tenantId, forecastId] as const,
|
||||
statistics: (tenantId: string) =>
|
||||
[...forecastingKeys.all, 'statistics', tenantId] as const,
|
||||
health: () => [...forecastingKeys.all, 'health'] as const,
|
||||
} as const;
|
||||
|
||||
// ================================================================
|
||||
// QUERIES
|
||||
// ================================================================
|
||||
|
||||
/**
|
||||
* Get tenant forecasts with filtering and pagination
|
||||
*/
|
||||
export const useTenantForecasts = (
|
||||
tenantId: string,
|
||||
params?: GetForecastsParams,
|
||||
options?: Omit<UseQueryOptions<ForecastListResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ForecastListResponse, ApiError>({
|
||||
queryKey: forecastingKeys.list(tenantId, params),
|
||||
queryFn: () => forecastingService.getTenantForecasts(tenantId, params),
|
||||
staleTime: 2 * 60 * 1000, // 2 minutes
|
||||
enabled: !!tenantId,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get specific forecast by ID
|
||||
*/
|
||||
export const useForecastById = (
|
||||
tenantId: string,
|
||||
forecastId: string,
|
||||
options?: Omit<UseQueryOptions<ForecastByIdResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ForecastByIdResponse, ApiError>({
|
||||
queryKey: forecastingKeys.detail(tenantId, forecastId),
|
||||
queryFn: () => forecastingService.getForecastById(tenantId, forecastId),
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
enabled: !!tenantId && !!forecastId,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get forecast statistics for tenant
|
||||
*/
|
||||
export const useForecastStatistics = (
|
||||
tenantId: string,
|
||||
options?: Omit<UseQueryOptions<ForecastStatistics, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ForecastStatistics, ApiError>({
|
||||
queryKey: forecastingKeys.statistics(tenantId),
|
||||
queryFn: () => forecastingService.getForecastStatistics(tenantId),
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
enabled: !!tenantId,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Health check for forecasting service
|
||||
*/
|
||||
export const useForecastingHealth = (
|
||||
options?: Omit<UseQueryOptions<ForecastingHealthResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ForecastingHealthResponse, ApiError>({
|
||||
queryKey: forecastingKeys.health(),
|
||||
queryFn: () => forecastingService.getHealthCheck(),
|
||||
staleTime: 30 * 1000, // 30 seconds
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// ================================================================
|
||||
// INFINITE QUERIES
|
||||
// ================================================================
|
||||
|
||||
/**
|
||||
* Infinite query for tenant forecasts (for pagination)
|
||||
*/
|
||||
export const useInfiniteTenantForecasts = (
|
||||
tenantId: string,
|
||||
baseParams?: Omit<GetForecastsParams, 'skip' | 'limit'>,
|
||||
options?: Omit<UseInfiniteQueryOptions<ForecastListResponse, ApiError>, 'queryKey' | 'queryFn' | 'getNextPageParam'>
|
||||
) => {
|
||||
const limit = baseParams?.limit || 20;
|
||||
|
||||
return useInfiniteQuery<ForecastListResponse, ApiError>({
|
||||
queryKey: [...forecastingKeys.list(tenantId, baseParams), 'infinite'],
|
||||
queryFn: ({ pageParam = 0 }) => {
|
||||
const params: GetForecastsParams = {
|
||||
...baseParams,
|
||||
skip: pageParam as number,
|
||||
limit,
|
||||
};
|
||||
return forecastingService.getTenantForecasts(tenantId, params);
|
||||
},
|
||||
getNextPageParam: (lastPage, allPages) => {
|
||||
const totalFetched = allPages.reduce((sum, page) => sum + page.total_returned, 0);
|
||||
return lastPage.total_returned === limit ? totalFetched : undefined;
|
||||
},
|
||||
staleTime: 2 * 60 * 1000, // 2 minutes
|
||||
enabled: !!tenantId,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// ================================================================
|
||||
// MUTATIONS
|
||||
// ================================================================
|
||||
|
||||
/**
|
||||
* Create single forecast mutation
|
||||
*/
|
||||
export const useCreateSingleForecast = (
|
||||
options?: UseMutationOptions<
|
||||
ForecastResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; request: ForecastRequest }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
ForecastResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; request: ForecastRequest }
|
||||
>({
|
||||
mutationFn: ({ tenantId, request }) =>
|
||||
forecastingService.createSingleForecast(tenantId, request),
|
||||
onSuccess: (data, variables) => {
|
||||
// Invalidate and refetch forecasts list
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: forecastingKeys.lists(),
|
||||
});
|
||||
|
||||
// Update the specific forecast cache
|
||||
queryClient.setQueryData(
|
||||
forecastingKeys.detail(variables.tenantId, data.id),
|
||||
{
|
||||
...data,
|
||||
enhanced_features: true,
|
||||
repository_integration: true,
|
||||
} as ForecastByIdResponse
|
||||
);
|
||||
|
||||
// Update statistics
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: forecastingKeys.statistics(variables.tenantId),
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create batch forecast mutation
|
||||
*/
|
||||
export const useCreateBatchForecast = (
|
||||
options?: UseMutationOptions<
|
||||
BatchForecastResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; request: BatchForecastRequest }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
BatchForecastResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; request: BatchForecastRequest }
|
||||
>({
|
||||
mutationFn: ({ tenantId, request }) =>
|
||||
forecastingService.createBatchForecast(tenantId, request),
|
||||
onSuccess: (data, variables) => {
|
||||
// Invalidate forecasts list
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: forecastingKeys.lists(),
|
||||
});
|
||||
|
||||
// Cache individual forecasts if available
|
||||
if (data.forecasts) {
|
||||
data.forecasts.forEach((forecast) => {
|
||||
queryClient.setQueryData(
|
||||
forecastingKeys.detail(variables.tenantId, forecast.id),
|
||||
{
|
||||
...forecast,
|
||||
enhanced_features: true,
|
||||
repository_integration: true,
|
||||
} as ForecastByIdResponse
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Update statistics
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: forecastingKeys.statistics(variables.tenantId),
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete forecast mutation
|
||||
*/
|
||||
export const useDeleteForecast = (
|
||||
options?: UseMutationOptions<
|
||||
DeleteForecastResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; forecastId: string }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
DeleteForecastResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; forecastId: string }
|
||||
>({
|
||||
mutationFn: ({ tenantId, forecastId }) =>
|
||||
forecastingService.deleteForecast(tenantId, forecastId),
|
||||
onSuccess: (data, variables) => {
|
||||
// Remove from cache
|
||||
queryClient.removeQueries({
|
||||
queryKey: forecastingKeys.detail(variables.tenantId, variables.forecastId),
|
||||
});
|
||||
|
||||
// Invalidate lists to refresh
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: forecastingKeys.lists(),
|
||||
});
|
||||
|
||||
// Update statistics
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: forecastingKeys.statistics(variables.tenantId),
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// ================================================================
|
||||
// UTILITY FUNCTIONS
|
||||
// ================================================================
|
||||
|
||||
/**
|
||||
* Prefetch forecast by ID
|
||||
*/
|
||||
export const usePrefetchForecast = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return (tenantId: string, forecastId: string) => {
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: forecastingKeys.detail(tenantId, forecastId),
|
||||
queryFn: () => forecastingService.getForecastById(tenantId, forecastId),
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Invalidate all forecasting queries for a tenant
|
||||
*/
|
||||
export const useInvalidateForecasting = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return (tenantId?: string) => {
|
||||
if (tenantId) {
|
||||
// Invalidate specific tenant queries
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: forecastingKeys.list(tenantId),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: forecastingKeys.statistics(tenantId),
|
||||
});
|
||||
} else {
|
||||
// Invalidate all forecasting queries
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: forecastingKeys.all,
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user