/** * 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, ApiError>, 'queryKey' | 'queryFn'> ) => { return useQuery, 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, 'queryKey' | 'queryFn'> ) => { return useQuery({ 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, ApiError>, 'queryKey' | 'queryFn'> ) => { return useQuery, 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, 'queryKey' | 'queryFn'> ) => { return useQuery({ 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, ApiError>, 'queryKey' | 'queryFn'> ) => { return useQuery, 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, 'queryKey' | 'queryFn'> ) => { return useQuery({ 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, 'queryKey' | 'queryFn'> ) => { return useQuery({ 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, 'queryKey' | 'queryFn'> ) => { return useQuery({ 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, ApiError>, 'queryKey' | 'queryFn'> ) => { return useQuery, 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, 'queryKey' | 'queryFn'> ) => { return useQuery({ 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 ) => { const queryClient = useQueryClient(); return useMutation({ 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 ) => { const queryClient = useQueryClient(); return useMutation({ 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, }); };