ADD new frontend
This commit is contained in:
475
frontend/src/hooks/api/useInventory.ts
Normal file
475
frontend/src/hooks/api/useInventory.ts
Normal file
@@ -0,0 +1,475 @@
|
||||
/**
|
||||
* Inventory hook for managing inventory state and operations
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { InventoryService } from '../../services/api/inventory.service';
|
||||
import {
|
||||
Ingredient,
|
||||
IngredientCreate,
|
||||
IngredientUpdate,
|
||||
StockLevel,
|
||||
StockMovement,
|
||||
StockMovementCreate,
|
||||
InventoryAlert,
|
||||
QualityCheckCreate,
|
||||
QualityCheck
|
||||
} from '../../types/inventory.types';
|
||||
import { ApiResponse, PaginatedResponse, QueryParams } from '../../types/api.types';
|
||||
|
||||
interface InventoryState {
|
||||
ingredients: Ingredient[];
|
||||
stockLevels: StockLevel[];
|
||||
stockMovements: StockMovement[];
|
||||
alerts: InventoryAlert[];
|
||||
qualityChecks: QualityCheck[];
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
pagination: {
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
limit: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface InventoryActions {
|
||||
// Ingredients
|
||||
fetchIngredients: (params?: QueryParams) => Promise<void>;
|
||||
createIngredient: (data: IngredientCreate) => Promise<boolean>;
|
||||
updateIngredient: (id: string, data: IngredientUpdate) => Promise<boolean>;
|
||||
deleteIngredient: (id: string) => Promise<boolean>;
|
||||
getIngredient: (id: string) => Promise<Ingredient | null>;
|
||||
|
||||
// Stock Levels
|
||||
fetchStockLevels: (params?: QueryParams) => Promise<void>;
|
||||
updateStockLevel: (ingredientId: string, quantity: number, reason?: string) => Promise<boolean>;
|
||||
|
||||
// Stock Movements
|
||||
fetchStockMovements: (params?: QueryParams) => Promise<void>;
|
||||
createStockMovement: (data: StockMovementCreate) => Promise<boolean>;
|
||||
|
||||
// Alerts
|
||||
fetchAlerts: (params?: QueryParams) => Promise<void>;
|
||||
markAlertAsRead: (id: string) => Promise<boolean>;
|
||||
dismissAlert: (id: string) => Promise<boolean>;
|
||||
|
||||
// Quality Checks
|
||||
fetchQualityChecks: (params?: QueryParams) => Promise<void>;
|
||||
createQualityCheck: (data: QualityCheckCreate) => Promise<boolean>;
|
||||
|
||||
// Analytics
|
||||
getInventoryAnalytics: (startDate?: string, endDate?: string) => Promise<any>;
|
||||
getExpirationReport: () => Promise<any>;
|
||||
|
||||
// Utilities
|
||||
clearError: () => void;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const useInventory = (): InventoryState & InventoryActions => {
|
||||
const [state, setState] = useState<InventoryState>({
|
||||
ingredients: [],
|
||||
stockLevels: [],
|
||||
stockMovements: [],
|
||||
alerts: [],
|
||||
qualityChecks: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
pagination: {
|
||||
total: 0,
|
||||
page: 1,
|
||||
pages: 1,
|
||||
limit: 20,
|
||||
},
|
||||
});
|
||||
|
||||
const inventoryService = new InventoryService();
|
||||
|
||||
// Fetch ingredients
|
||||
const fetchIngredients = useCallback(async (params?: QueryParams) => {
|
||||
setState(prev => ({ ...prev, isLoading: true, error: null }));
|
||||
|
||||
try {
|
||||
const response = await inventoryService.getIngredients(params);
|
||||
|
||||
if (response.success && response.data) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
ingredients: Array.isArray(response.data) ? response.data : response.data.items || [],
|
||||
pagination: response.data.pagination || prev.pagination,
|
||||
isLoading: false,
|
||||
}));
|
||||
} else {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
isLoading: false,
|
||||
error: response.error || 'Error al cargar ingredientes',
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
isLoading: false,
|
||||
error: 'Error de conexión al servidor',
|
||||
}));
|
||||
}
|
||||
}, [inventoryService]);
|
||||
|
||||
// Create ingredient
|
||||
const createIngredient = useCallback(async (data: IngredientCreate): Promise<boolean> => {
|
||||
setState(prev => ({ ...prev, error: null }));
|
||||
|
||||
try {
|
||||
const response = await inventoryService.createIngredient(data);
|
||||
|
||||
if (response.success) {
|
||||
await fetchIngredients();
|
||||
return true;
|
||||
} else {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: response.error || 'Error al crear ingrediente',
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: 'Error de conexión al servidor',
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
}, [inventoryService, fetchIngredients]);
|
||||
|
||||
// Update ingredient
|
||||
const updateIngredient = useCallback(async (id: string, data: IngredientUpdate): Promise<boolean> => {
|
||||
setState(prev => ({ ...prev, error: null }));
|
||||
|
||||
try {
|
||||
const response = await inventoryService.updateIngredient(id, data);
|
||||
|
||||
if (response.success) {
|
||||
await fetchIngredients();
|
||||
return true;
|
||||
} else {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: response.error || 'Error al actualizar ingrediente',
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: 'Error de conexión al servidor',
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
}, [inventoryService, fetchIngredients]);
|
||||
|
||||
// Delete ingredient
|
||||
const deleteIngredient = useCallback(async (id: string): Promise<boolean> => {
|
||||
setState(prev => ({ ...prev, error: null }));
|
||||
|
||||
try {
|
||||
const response = await inventoryService.deleteIngredient(id);
|
||||
|
||||
if (response.success) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
ingredients: prev.ingredients.filter(ingredient => ingredient.id !== id),
|
||||
}));
|
||||
return true;
|
||||
} else {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: response.error || 'Error al eliminar ingrediente',
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: 'Error de conexión al servidor',
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
}, [inventoryService]);
|
||||
|
||||
// Get single ingredient
|
||||
const getIngredient = useCallback(async (id: string): Promise<Ingredient | null> => {
|
||||
try {
|
||||
const response = await inventoryService.getIngredient(id);
|
||||
return response.success ? response.data : null;
|
||||
} catch (error) {
|
||||
console.error('Error fetching ingredient:', error);
|
||||
return null;
|
||||
}
|
||||
}, [inventoryService]);
|
||||
|
||||
// Fetch stock levels
|
||||
const fetchStockLevels = useCallback(async (params?: QueryParams) => {
|
||||
setState(prev => ({ ...prev, isLoading: true, error: null }));
|
||||
|
||||
try {
|
||||
const response = await inventoryService.getStockLevels(params);
|
||||
|
||||
if (response.success && response.data) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
stockLevels: Array.isArray(response.data) ? response.data : response.data.items || [],
|
||||
isLoading: false,
|
||||
}));
|
||||
} else {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
isLoading: false,
|
||||
error: response.error || 'Error al cargar niveles de stock',
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
isLoading: false,
|
||||
error: 'Error de conexión al servidor',
|
||||
}));
|
||||
}
|
||||
}, [inventoryService]);
|
||||
|
||||
// Update stock level
|
||||
const updateStockLevel = useCallback(async (ingredientId: string, quantity: number, reason?: string): Promise<boolean> => {
|
||||
setState(prev => ({ ...prev, error: null }));
|
||||
|
||||
try {
|
||||
const response = await inventoryService.updateStockLevel(ingredientId, {
|
||||
quantity,
|
||||
reason: reason || 'Manual adjustment'
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
await fetchStockLevels();
|
||||
return true;
|
||||
} else {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: response.error || 'Error al actualizar stock',
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: 'Error de conexión al servidor',
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
}, [inventoryService, fetchStockLevels]);
|
||||
|
||||
// Fetch stock movements
|
||||
const fetchStockMovements = useCallback(async (params?: QueryParams) => {
|
||||
setState(prev => ({ ...prev, isLoading: true, error: null }));
|
||||
|
||||
try {
|
||||
const response = await inventoryService.getStockMovements(params);
|
||||
|
||||
if (response.success && response.data) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
stockMovements: Array.isArray(response.data) ? response.data : response.data.items || [],
|
||||
isLoading: false,
|
||||
}));
|
||||
} else {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
isLoading: false,
|
||||
error: response.error || 'Error al cargar movimientos de stock',
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
isLoading: false,
|
||||
error: 'Error de conexión al servidor',
|
||||
}));
|
||||
}
|
||||
}, [inventoryService]);
|
||||
|
||||
// Create stock movement
|
||||
const createStockMovement = useCallback(async (data: StockMovementCreate): Promise<boolean> => {
|
||||
setState(prev => ({ ...prev, error: null }));
|
||||
|
||||
try {
|
||||
const response = await inventoryService.createStockMovement(data);
|
||||
|
||||
if (response.success) {
|
||||
await fetchStockMovements();
|
||||
await fetchStockLevels();
|
||||
return true;
|
||||
} else {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: response.error || 'Error al crear movimiento de stock',
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: 'Error de conexión al servidor',
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
}, [inventoryService, fetchStockMovements, fetchStockLevels]);
|
||||
|
||||
// Fetch alerts
|
||||
const fetchAlerts = useCallback(async (params?: QueryParams) => {
|
||||
try {
|
||||
const response = await inventoryService.getAlerts(params);
|
||||
|
||||
if (response.success && response.data) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
alerts: Array.isArray(response.data) ? response.data : response.data.items || [],
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching alerts:', error);
|
||||
}
|
||||
}, [inventoryService]);
|
||||
|
||||
// Mark alert as read
|
||||
const markAlertAsRead = useCallback(async (id: string): Promise<boolean> => {
|
||||
try {
|
||||
const response = await inventoryService.markAlertAsRead(id);
|
||||
|
||||
if (response.success) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
alerts: prev.alerts.map(alert =>
|
||||
alert.id === id ? { ...alert, is_read: true } : alert
|
||||
),
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Error marking alert as read:', error);
|
||||
return false;
|
||||
}
|
||||
}, [inventoryService]);
|
||||
|
||||
// Dismiss alert
|
||||
const dismissAlert = useCallback(async (id: string): Promise<boolean> => {
|
||||
try {
|
||||
const response = await inventoryService.dismissAlert(id);
|
||||
|
||||
if (response.success) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
alerts: prev.alerts.filter(alert => alert.id !== id),
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Error dismissing alert:', error);
|
||||
return false;
|
||||
}
|
||||
}, [inventoryService]);
|
||||
|
||||
// Fetch quality checks
|
||||
const fetchQualityChecks = useCallback(async (params?: QueryParams) => {
|
||||
try {
|
||||
const response = await inventoryService.getQualityChecks(params);
|
||||
|
||||
if (response.success && response.data) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
qualityChecks: Array.isArray(response.data) ? response.data : response.data.items || [],
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching quality checks:', error);
|
||||
}
|
||||
}, [inventoryService]);
|
||||
|
||||
// Create quality check
|
||||
const createQualityCheck = useCallback(async (data: QualityCheckCreate): Promise<boolean> => {
|
||||
try {
|
||||
const response = await inventoryService.createQualityCheck(data);
|
||||
|
||||
if (response.success) {
|
||||
await fetchQualityChecks();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Error creating quality check:', error);
|
||||
return false;
|
||||
}
|
||||
}, [inventoryService, fetchQualityChecks]);
|
||||
|
||||
// Get inventory analytics
|
||||
const getInventoryAnalytics = useCallback(async (startDate?: string, endDate?: string) => {
|
||||
try {
|
||||
const response = await inventoryService.getAnalytics(startDate, endDate);
|
||||
return response.success ? response.data : null;
|
||||
} catch (error) {
|
||||
console.error('Error fetching inventory analytics:', error);
|
||||
return null;
|
||||
}
|
||||
}, [inventoryService]);
|
||||
|
||||
// Get expiration report
|
||||
const getExpirationReport = useCallback(async () => {
|
||||
try {
|
||||
const response = await inventoryService.getExpirationReport();
|
||||
return response.success ? response.data : null;
|
||||
} catch (error) {
|
||||
console.error('Error fetching expiration report:', error);
|
||||
return null;
|
||||
}
|
||||
}, [inventoryService]);
|
||||
|
||||
// Clear error
|
||||
const clearError = useCallback(() => {
|
||||
setState(prev => ({ ...prev, error: null }));
|
||||
}, []);
|
||||
|
||||
// Refresh all data
|
||||
const refresh = useCallback(async () => {
|
||||
await Promise.all([
|
||||
fetchIngredients(),
|
||||
fetchStockLevels(),
|
||||
fetchAlerts(),
|
||||
]);
|
||||
}, [fetchIngredients, fetchStockLevels, fetchAlerts]);
|
||||
|
||||
// Initialize data on mount
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchIngredients,
|
||||
createIngredient,
|
||||
updateIngredient,
|
||||
deleteIngredient,
|
||||
getIngredient,
|
||||
fetchStockLevels,
|
||||
updateStockLevel,
|
||||
fetchStockMovements,
|
||||
createStockMovement,
|
||||
fetchAlerts,
|
||||
markAlertAsRead,
|
||||
dismissAlert,
|
||||
fetchQualityChecks,
|
||||
createQualityCheck,
|
||||
getInventoryAnalytics,
|
||||
getExpirationReport,
|
||||
clearError,
|
||||
refresh,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user