Start integrating the onboarding flow with backend 6
This commit is contained in:
372
frontend/src/api/hooks/inventory.ts
Normal file
372
frontend/src/api/hooks/inventory.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
/**
|
||||
* Inventory React Query hooks
|
||||
*/
|
||||
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
|
||||
import { inventoryService } from '../services/inventory';
|
||||
import {
|
||||
IngredientCreate,
|
||||
IngredientUpdate,
|
||||
IngredientResponse,
|
||||
StockCreate,
|
||||
StockUpdate,
|
||||
StockResponse,
|
||||
StockMovementCreate,
|
||||
StockMovementResponse,
|
||||
InventoryFilter,
|
||||
StockFilter,
|
||||
StockConsumptionRequest,
|
||||
StockConsumptionResponse,
|
||||
PaginatedResponse,
|
||||
} from '../types/inventory';
|
||||
import { ApiError } from '../client';
|
||||
|
||||
// Query Keys
|
||||
export const inventoryKeys = {
|
||||
all: ['inventory'] as const,
|
||||
ingredients: {
|
||||
all: () => [...inventoryKeys.all, 'ingredients'] as const,
|
||||
lists: () => [...inventoryKeys.ingredients.all(), 'list'] as const,
|
||||
list: (tenantId: string, filters?: InventoryFilter) =>
|
||||
[...inventoryKeys.ingredients.lists(), tenantId, filters] as const,
|
||||
details: () => [...inventoryKeys.ingredients.all(), 'detail'] as const,
|
||||
detail: (tenantId: string, ingredientId: string) =>
|
||||
[...inventoryKeys.ingredients.details(), tenantId, ingredientId] as const,
|
||||
byCategory: (tenantId: string) =>
|
||||
[...inventoryKeys.ingredients.all(), 'by-category', tenantId] as const,
|
||||
lowStock: (tenantId: string) =>
|
||||
[...inventoryKeys.ingredients.all(), 'low-stock', tenantId] as const,
|
||||
},
|
||||
stock: {
|
||||
all: () => [...inventoryKeys.all, 'stock'] as const,
|
||||
lists: () => [...inventoryKeys.stock.all(), 'list'] as const,
|
||||
list: (tenantId: string, filters?: StockFilter) =>
|
||||
[...inventoryKeys.stock.lists(), tenantId, filters] as const,
|
||||
details: () => [...inventoryKeys.stock.all(), 'detail'] as const,
|
||||
detail: (tenantId: string, stockId: string) =>
|
||||
[...inventoryKeys.stock.details(), tenantId, stockId] as const,
|
||||
byIngredient: (tenantId: string, ingredientId: string, includeUnavailable?: boolean) =>
|
||||
[...inventoryKeys.stock.all(), 'by-ingredient', tenantId, ingredientId, includeUnavailable] as const,
|
||||
expiring: (tenantId: string, withinDays?: number) =>
|
||||
[...inventoryKeys.stock.all(), 'expiring', tenantId, withinDays] as const,
|
||||
expired: (tenantId: string) =>
|
||||
[...inventoryKeys.stock.all(), 'expired', tenantId] as const,
|
||||
movements: (tenantId: string, ingredientId?: string) =>
|
||||
[...inventoryKeys.stock.all(), 'movements', tenantId, ingredientId] as const,
|
||||
},
|
||||
analytics: (tenantId: string, startDate?: string, endDate?: string) =>
|
||||
[...inventoryKeys.all, 'analytics', tenantId, { startDate, endDate }] as const,
|
||||
} as const;
|
||||
|
||||
// Ingredient Queries
|
||||
export const useIngredients = (
|
||||
tenantId: string,
|
||||
filter?: InventoryFilter,
|
||||
options?: Omit<UseQueryOptions<PaginatedResponse<IngredientResponse>, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PaginatedResponse<IngredientResponse>, ApiError>({
|
||||
queryKey: inventoryKeys.ingredients.list(tenantId, filter),
|
||||
queryFn: () => inventoryService.getIngredients(tenantId, filter),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 2 * 60 * 1000, // 2 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useIngredient = (
|
||||
tenantId: string,
|
||||
ingredientId: string,
|
||||
options?: Omit<UseQueryOptions<IngredientResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<IngredientResponse, ApiError>({
|
||||
queryKey: inventoryKeys.ingredients.detail(tenantId, ingredientId),
|
||||
queryFn: () => inventoryService.getIngredient(tenantId, ingredientId),
|
||||
enabled: !!tenantId && !!ingredientId,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useIngredientsByCategory = (
|
||||
tenantId: string,
|
||||
options?: Omit<UseQueryOptions<Record<string, IngredientResponse[]>, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<Record<string, IngredientResponse[]>, ApiError>({
|
||||
queryKey: inventoryKeys.ingredients.byCategory(tenantId),
|
||||
queryFn: () => inventoryService.getIngredientsByCategory(tenantId),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useLowStockIngredients = (
|
||||
tenantId: string,
|
||||
options?: Omit<UseQueryOptions<IngredientResponse[], ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<IngredientResponse[], ApiError>({
|
||||
queryKey: inventoryKeys.ingredients.lowStock(tenantId),
|
||||
queryFn: () => inventoryService.getLowStockIngredients(tenantId),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 30 * 1000, // 30 seconds
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Stock Queries
|
||||
export const useStock = (
|
||||
tenantId: string,
|
||||
filter?: StockFilter,
|
||||
options?: Omit<UseQueryOptions<PaginatedResponse<StockResponse>, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PaginatedResponse<StockResponse>, ApiError>({
|
||||
queryKey: inventoryKeys.stock.list(tenantId, filter),
|
||||
queryFn: () => inventoryService.getAllStock(tenantId, filter),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 30 * 1000, // 30 seconds
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useStockByIngredient = (
|
||||
tenantId: string,
|
||||
ingredientId: string,
|
||||
includeUnavailable: boolean = false,
|
||||
options?: Omit<UseQueryOptions<StockResponse[], ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<StockResponse[], ApiError>({
|
||||
queryKey: inventoryKeys.stock.byIngredient(tenantId, ingredientId, includeUnavailable),
|
||||
queryFn: () => inventoryService.getStockByIngredient(tenantId, ingredientId, includeUnavailable),
|
||||
enabled: !!tenantId && !!ingredientId,
|
||||
staleTime: 1 * 60 * 1000, // 1 minute
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useExpiringStock = (
|
||||
tenantId: string,
|
||||
withinDays: number = 7,
|
||||
options?: Omit<UseQueryOptions<StockResponse[], ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<StockResponse[], ApiError>({
|
||||
queryKey: inventoryKeys.stock.expiring(tenantId, withinDays),
|
||||
queryFn: () => inventoryService.getExpiringStock(tenantId, withinDays),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 30 * 1000, // 30 seconds
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useExpiredStock = (
|
||||
tenantId: string,
|
||||
options?: Omit<UseQueryOptions<StockResponse[], ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<StockResponse[], ApiError>({
|
||||
queryKey: inventoryKeys.stock.expired(tenantId),
|
||||
queryFn: () => inventoryService.getExpiredStock(tenantId),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 1 * 60 * 1000, // 1 minute
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useStockMovements = (
|
||||
tenantId: string,
|
||||
ingredientId?: string,
|
||||
limit: number = 50,
|
||||
offset: number = 0,
|
||||
options?: Omit<UseQueryOptions<PaginatedResponse<StockMovementResponse>, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PaginatedResponse<StockMovementResponse>, ApiError>({
|
||||
queryKey: inventoryKeys.stock.movements(tenantId, ingredientId),
|
||||
queryFn: () => inventoryService.getStockMovements(tenantId, ingredientId, limit, offset),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 1 * 60 * 1000, // 1 minute
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useStockAnalytics = (
|
||||
tenantId: string,
|
||||
startDate?: string,
|
||||
endDate?: string,
|
||||
options?: Omit<UseQueryOptions<any, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<any, ApiError>({
|
||||
queryKey: inventoryKeys.analytics(tenantId, startDate, endDate),
|
||||
queryFn: () => inventoryService.getStockAnalytics(tenantId, startDate, endDate),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Ingredient Mutations
|
||||
export const useCreateIngredient = (
|
||||
options?: UseMutationOptions<IngredientResponse, ApiError, { tenantId: string; ingredientData: IngredientCreate }>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<IngredientResponse, ApiError, { tenantId: string; ingredientData: IngredientCreate }>({
|
||||
mutationFn: ({ tenantId, ingredientData }) => inventoryService.createIngredient(tenantId, ingredientData),
|
||||
onSuccess: (data, { tenantId }) => {
|
||||
// Add to cache
|
||||
queryClient.setQueryData(inventoryKeys.ingredients.detail(tenantId, data.id), data);
|
||||
// Invalidate lists
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.byCategory(tenantId) });
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateIngredient = (
|
||||
options?: UseMutationOptions<
|
||||
IngredientResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; ingredientId: string; updateData: IngredientUpdate }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
IngredientResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; ingredientId: string; updateData: IngredientUpdate }
|
||||
>({
|
||||
mutationFn: ({ tenantId, ingredientId, updateData }) =>
|
||||
inventoryService.updateIngredient(tenantId, ingredientId, updateData),
|
||||
onSuccess: (data, { tenantId, ingredientId }) => {
|
||||
// Update cache
|
||||
queryClient.setQueryData(inventoryKeys.ingredients.detail(tenantId, ingredientId), data);
|
||||
// Invalidate lists
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.byCategory(tenantId) });
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteIngredient = (
|
||||
options?: UseMutationOptions<{ message: string }, ApiError, { tenantId: string; ingredientId: string }>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{ message: string }, ApiError, { tenantId: string; ingredientId: string }>({
|
||||
mutationFn: ({ tenantId, ingredientId }) => inventoryService.deleteIngredient(tenantId, ingredientId),
|
||||
onSuccess: (data, { tenantId, ingredientId }) => {
|
||||
// Remove from cache
|
||||
queryClient.removeQueries({ queryKey: inventoryKeys.ingredients.detail(tenantId, ingredientId) });
|
||||
// Invalidate lists
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.byCategory(tenantId) });
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Stock Mutations
|
||||
export const useAddStock = (
|
||||
options?: UseMutationOptions<StockResponse, ApiError, { tenantId: string; stockData: StockCreate }>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<StockResponse, ApiError, { tenantId: string; stockData: StockCreate }>({
|
||||
mutationFn: ({ tenantId, stockData }) => inventoryService.addStock(tenantId, stockData),
|
||||
onSuccess: (data, { tenantId }) => {
|
||||
// Invalidate stock queries
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.byIngredient(tenantId, data.ingredient_id) });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.lists() });
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateStock = (
|
||||
options?: UseMutationOptions<
|
||||
StockResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; stockId: string; updateData: StockUpdate }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
StockResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; stockId: string; updateData: StockUpdate }
|
||||
>({
|
||||
mutationFn: ({ tenantId, stockId, updateData }) =>
|
||||
inventoryService.updateStock(tenantId, stockId, updateData),
|
||||
onSuccess: (data, { tenantId, stockId }) => {
|
||||
// Update cache
|
||||
queryClient.setQueryData(inventoryKeys.stock.detail(tenantId, stockId), data);
|
||||
// Invalidate related queries
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.byIngredient(tenantId, data.ingredient_id) });
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useConsumeStock = (
|
||||
options?: UseMutationOptions<
|
||||
StockConsumptionResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; consumptionData: StockConsumptionRequest }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
StockConsumptionResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; consumptionData: StockConsumptionRequest }
|
||||
>({
|
||||
mutationFn: ({ tenantId, consumptionData }) => inventoryService.consumeStock(tenantId, consumptionData),
|
||||
onSuccess: (data, { tenantId, consumptionData }) => {
|
||||
// Invalidate stock queries for the affected ingredient
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: inventoryKeys.stock.byIngredient(tenantId, consumptionData.ingredient_id)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.movements(tenantId) });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.lists() });
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateStockMovement = (
|
||||
options?: UseMutationOptions<
|
||||
StockMovementResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; movementData: StockMovementCreate }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
StockMovementResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; movementData: StockMovementCreate }
|
||||
>({
|
||||
mutationFn: ({ tenantId, movementData }) => inventoryService.createStockMovement(tenantId, movementData),
|
||||
onSuccess: (data, { tenantId, movementData }) => {
|
||||
// Invalidate movement queries
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.movements(tenantId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: inventoryKeys.stock.movements(tenantId, movementData.ingredient_id)
|
||||
});
|
||||
// Invalidate stock queries if this affects stock levels
|
||||
if (['in', 'out', 'adjustment'].includes(movementData.movement_type)) {
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.lists() });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: inventoryKeys.stock.byIngredient(tenantId, movementData.ingredient_id)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.lists() });
|
||||
}
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user