Add frontend imporvements

This commit is contained in:
Urtzi Alfaro
2025-09-09 21:39:12 +02:00
parent 23e088dcb4
commit 2a05048912
16 changed files with 1761 additions and 1233 deletions

View 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,
});
}
};
};