diff --git a/src/pages/Inventory.tsx b/src/pages/Inventory.tsx index 49bb256..c67b8e0 100644 --- a/src/pages/Inventory.tsx +++ b/src/pages/Inventory.tsx @@ -1,32 +1,169 @@ import { useState } from 'react'; -import { Package, TrendingUp, TrendingDown, AlertTriangle, Plus, Minus } from 'lucide-react'; +import { useQuery } from '@tanstack/react-query'; +import { Package, TrendingUp, TrendingDown, AlertTriangle, Plus, Minus, Loader2, AlertCircle, RefreshCw } from 'lucide-react'; import clsx from 'clsx'; +import { productsApi, inventoryApi } from '../lib/api'; -const mockInventory = [ - { id: '1', name: 'Coca-Cola 600ml', category: 'bebidas', stock: 24, minStock: 10, maxStock: 50, cost: 12.00, price: 18.00 }, - { id: '2', name: 'Sabritas Original', category: 'botanas', stock: 12, minStock: 5, maxStock: 30, cost: 10.00, price: 15.00 }, - { id: '3', name: 'Leche Lala 1L', category: 'lacteos', stock: 3, minStock: 8, maxStock: 20, cost: 22.00, price: 28.00 }, - { id: '4', name: 'Pan Bimbo Grande', category: 'panaderia', stock: 2, minStock: 5, maxStock: 15, cost: 35.00, price: 45.00 }, - { id: '5', name: 'Fabuloso 1L', category: 'limpieza', stock: 6, minStock: 5, maxStock: 20, cost: 25.00, price: 32.00 }, -]; +// Types based on API responses +interface Product { + id: string; + name: string; + category: string; + stock: number; + minStock: number; + maxStock: number; + cost: number; + price: number; +} -const recentMovements = [ - { product: 'Coca-Cola 600ml', type: 'sale', quantity: -2, date: '10:30 AM' }, - { product: 'Sabritas', type: 'sale', quantity: -1, date: '10:15 AM' }, - { product: 'Leche Lala 1L', type: 'purchase', quantity: +12, date: '09:00 AM' }, - { product: 'Pan Bimbo', type: 'sale', quantity: -3, date: '08:45 AM' }, -]; +interface InventoryMovement { + id: string; + productId: string; + productName: string; + type: 'entrada' | 'salida' | 'ajuste' | 'venta'; + quantity: number; + createdAt: string; +} + +interface LowStockProduct { + id: string; + name: string; + category: string; + stock: number; + minStock: number; + maxStock: number; + cost: number; +} + +interface InventoryAlert { + id: string; + type: 'low_stock' | 'out_of_stock' | 'overstock'; + productId: string; + productName: string; + message: string; + createdAt: string; +} export function Inventory() { const [showLowStock, setShowLowStock] = useState(false); - const lowStockItems = mockInventory.filter(item => item.stock <= item.minStock); - const totalValue = mockInventory.reduce((sum, item) => sum + (item.stock * item.cost), 0); - const totalItems = mockInventory.reduce((sum, item) => sum + item.stock, 0); + // Fetch products with inventory data + const { + data: products, + isLoading: productsLoading, + error: productsError, + refetch: refetchProducts + } = useQuery({ + queryKey: ['products'], + queryFn: async () => { + const res = await productsApi.getAll(); + return res.data; + }, + }); + // Fetch recent inventory movements + const { + data: movements, + isLoading: movementsLoading, + error: movementsError + } = useQuery({ + queryKey: ['inventory-movements'], + queryFn: async () => { + const res = await inventoryApi.getMovements(); + return res.data; + }, + }); + + // Fetch low stock products + const { + data: lowStockData, + isLoading: lowStockLoading + } = useQuery({ + queryKey: ['low-stock'], + queryFn: async () => { + const res = await inventoryApi.getLowStock(); + return res.data; + }, + }); + + // Fetch inventory alerts + const { data: alerts } = useQuery({ + queryKey: ['inventory-alerts'], + queryFn: async () => { + const res = await inventoryApi.getAlerts(); + return res.data; + }, + }); + + // Loading state + const isLoading = productsLoading || movementsLoading || lowStockLoading; + + if (isLoading) { + return ( +
+
+ +

Cargando inventario...

+
+
+ ); + } + + // Error state + if (productsError || movementsError) { + return ( +
+
+ +

Error al cargar el inventario

+

+ {(productsError as Error)?.message || (movementsError as Error)?.message || 'Error desconocido'} +

+ +
+
+ ); + } + + // Use low stock data from API or filter products locally as fallback + const lowStockItems = lowStockData || products?.filter(item => item.stock <= item.minStock) || []; + const inventoryItems = products || []; + + // Calculate totals + const totalValue = inventoryItems.reduce((sum, item) => sum + (item.stock * item.cost), 0); + const totalItems = inventoryItems.reduce((sum, item) => sum + item.stock, 0); + + // Filter display items based on low stock toggle const displayItems = showLowStock ? lowStockItems - : mockInventory; + : inventoryItems; + + // Get recent movements (limit to 10) + const recentMovements = (movements || []).slice(0, 10); + + // Format movement time + const formatMovementTime = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleTimeString('es-MX', { + hour: '2-digit', + minute: '2-digit', + }); + }; + + // Determine movement type for display + const getMovementDisplay = (movement: InventoryMovement) => { + const isSale = movement.type === 'venta' || movement.type === 'salida'; + return { + isSale, + quantity: isSale ? -Math.abs(movement.quantity) : Math.abs(movement.quantity), + }; + }; return (
@@ -47,6 +184,28 @@ export function Inventory() {
+ {/* Alerts Banner */} + {alerts && alerts.length > 0 && ( +
+
+ + + {alerts.length} alerta{alerts.length !== 1 ? 's' : ''} de inventario + +
+
    + {alerts.slice(0, 3).map((alert) => ( +
  • {alert.message}
  • + ))} + {alerts.length > 3 && ( +
  • + + {alerts.length - 3} alertas mas... +
  • + )} +
+
+ )} + {/* Summary */}
@@ -88,86 +247,108 @@ export function Inventory() { {/* Inventory Table */}
- - - - - - - - - - - {displayItems.map((item) => { - const isLow = item.stock <= item.minStock; - const percentage = (item.stock / item.maxStock) * 100; + {displayItems.length === 0 ? ( +
+ +

+ {showLowStock ? 'No hay productos con stock bajo' : 'No hay productos en inventario'} +

+

+ {showLowStock ? 'Tu inventario esta en buen estado' : 'Agrega productos para comenzar'} +

+
+ ) : ( +
ProductoStockMin/MaxValor
+ + + + + + + + + + {displayItems.map((item) => { + const isLow = item.stock <= item.minStock; + const percentage = item.maxStock > 0 ? (item.stock / item.maxStock) * 100 : 0; - return ( - - - + + - - - - ); - })} - -
ProductoStockMin/MaxValor
-

{item.name}

-

{item.category}

-
-
- - {item.stock} - -
-
+ return ( +
+

{item.name}

+

{item.category}

+
+
+ + {item.stock} + +
+
+
-
-
- {item.minStock} / {item.maxStock} - - ${(item.stock * item.cost).toFixed(2)} -
+ + + {item.minStock} / {item.maxStock} + + + ${(item.stock * item.cost).toFixed(2)} + + + ); + })} + + + )}
{/* Recent Movements */}

Movimientos Recientes

-
- {recentMovements.map((mov, i) => ( -
-
- {mov.type === 'sale' ? ( - - ) : ( - - )} -
-

{mov.product}

-

{mov.date}

+ {recentMovements.length === 0 ? ( +
+ +

Sin movimientos recientes

+
+ ) : ( +
+ {recentMovements.map((mov) => { + const { isSale, quantity } = getMovementDisplay(mov); + return ( +
+
+ {isSale ? ( + + ) : ( + + )} +
+

{mov.productName}

+

{formatMovementTime(mov.createdAt)}

+
+
+ 0 ? 'text-green-600' : 'text-red-600' + )}> + {quantity > 0 ? '+' : ''}{quantity} +
-
- 0 ? 'text-green-600' : 'text-red-600' - )}> - {mov.quantity > 0 ? '+' : ''}{mov.quantity} - -
- ))} -
+ ); + })} +
+ )}