Fix inventario api error
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { Plus, Download, AlertTriangle, Package, Clock, CheckCircle, Eye, Edit, Calendar, DollarSign } from 'lucide-react';
|
||||
import { Plus, Download, AlertTriangle, Package, Clock, CheckCircle, Eye, Edit, Calendar, DollarSign, ArrowRight, TrendingUp, Shield } from 'lucide-react';
|
||||
import { Button, Input, Card, StatsGrid, StatusCard, getStatusColor, StatusModal } from '../../../../components/ui';
|
||||
import { LoadingSpinner } from '../../../../components/shared';
|
||||
import { formatters } from '../../../../components/ui/Stats/StatsPresets';
|
||||
@@ -93,16 +93,28 @@ const InventoryPage: React.FC = () => {
|
||||
expiringSoon: 0, // This would come from expired stock API
|
||||
totalValue: ingredients.reduce((sum, item) => sum + (item.current_stock_level * (item.average_cost || 0)), 0),
|
||||
categories: [...new Set(ingredients.map(item => item.category))].length,
|
||||
turnoverRate: 0,
|
||||
fastMovingItems: 0,
|
||||
qualityScore: 85,
|
||||
reorderAccuracy: 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Extract data from new analytics structure
|
||||
const stockoutCount = Object.values(analyticsData.stockout_frequency || {}).reduce((sum: number, count) => sum + count, 0);
|
||||
const fastMovingCount = (analyticsData.fast_moving_items || []).length;
|
||||
|
||||
return {
|
||||
totalItems: analyticsData.total_ingredients || 0,
|
||||
lowStockItems: analyticsData.low_stock_count || 0,
|
||||
outOfStock: analyticsData.out_of_stock_count || 0,
|
||||
expiringSoon: analyticsData.expiring_soon_count || 0,
|
||||
totalValue: analyticsData.total_stock_value || 0,
|
||||
categories: [...new Set(ingredients.map(item => item.category))].length,
|
||||
totalItems: ingredients.length, // Use ingredients array as fallback
|
||||
lowStockItems: stockoutCount || lowStockItems.length,
|
||||
outOfStock: stockoutCount || ingredients.filter(item => item.stock_status === 'out_of_stock').length,
|
||||
expiringSoon: Math.round(Number(analyticsData.quality_incidents_rate || 0) * ingredients.length), // Estimate from quality incidents
|
||||
totalValue: Number(analyticsData.total_inventory_cost || 0),
|
||||
categories: Object.keys(analyticsData.cost_by_category || {}).length || [...new Set(ingredients.map(item => item.category))].length,
|
||||
turnoverRate: Number(analyticsData.inventory_turnover_rate || 0),
|
||||
fastMovingItems: fastMovingCount,
|
||||
qualityScore: Number(analyticsData.food_safety_score || 85),
|
||||
reorderAccuracy: Math.round(Number(analyticsData.reorder_accuracy || 0) * 100),
|
||||
};
|
||||
}, [analyticsData, ingredients, lowStockItems]);
|
||||
|
||||
@@ -138,10 +150,22 @@ const InventoryPage: React.FC = () => {
|
||||
icon: DollarSign,
|
||||
},
|
||||
{
|
||||
title: 'Categorías',
|
||||
value: inventoryStats.categories,
|
||||
title: 'Tasa Rotación',
|
||||
value: inventoryStats.turnoverRate.toFixed(1),
|
||||
variant: 'info' as const,
|
||||
icon: Package,
|
||||
icon: ArrowRight,
|
||||
},
|
||||
{
|
||||
title: 'Items Dinámicos',
|
||||
value: inventoryStats.fastMovingItems,
|
||||
variant: 'success' as const,
|
||||
icon: TrendingUp,
|
||||
},
|
||||
{
|
||||
title: 'Puntuación Calidad',
|
||||
value: `${inventoryStats.qualityScore}%`,
|
||||
variant: inventoryStats.qualityScore >= 90 ? 'success' as const : inventoryStats.qualityScore >= 70 ? 'warning' as const : 'error' as const,
|
||||
icon: Shield,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -195,11 +219,99 @@ const InventoryPage: React.FC = () => {
|
||||
/>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<StatsGrid
|
||||
<StatsGrid
|
||||
stats={stats}
|
||||
columns={3}
|
||||
columns={4}
|
||||
/>
|
||||
|
||||
{/* Analytics Section */}
|
||||
{analyticsData && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Fast Moving Items */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||
<TrendingUp className="w-5 h-5 text-green-500" />
|
||||
Items de Alta Rotación
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{(analyticsData.fast_moving_items || []).slice(0, 5).map((item: any, index: number) => (
|
||||
<div key={item.ingredient_id || index} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||
<div>
|
||||
<p className="font-medium">{item.name}</p>
|
||||
<p className="text-sm text-gray-600">{item.movement_count} movimientos</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-medium">{formatters.currency(item.avg_cost)}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{(!analyticsData.fast_moving_items || analyticsData.fast_moving_items.length === 0) && (
|
||||
<p className="text-gray-500 text-center py-4">No hay datos de items de alta rotación</p>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Cost by Category */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||
<Package className="w-5 h-5 text-blue-500" />
|
||||
Costos por Categoría
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{Object.entries(analyticsData.cost_by_category || {}).slice(0, 5).map(([category, cost]) => (
|
||||
<div key={category} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||
<div>
|
||||
<p className="font-medium capitalize">{category}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-medium">{formatters.currency(Number(cost))}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{Object.keys(analyticsData.cost_by_category || {}).length === 0 && (
|
||||
<p className="text-gray-500 text-center py-4">No hay datos de costos por categoría</p>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Efficiency Metrics */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||
<Shield className="w-5 h-5 text-purple-500" />
|
||||
Métricas de Eficiencia
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="text-center p-3 bg-gray-50 rounded-lg">
|
||||
<p className="text-2xl font-bold text-blue-600">{inventoryStats.reorderAccuracy}%</p>
|
||||
<p className="text-sm text-gray-600">Precisión Reorden</p>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-gray-50 rounded-lg">
|
||||
<p className="text-2xl font-bold text-green-600">{inventoryStats.turnoverRate.toFixed(1)}</p>
|
||||
<p className="text-sm text-gray-600">Tasa Rotación</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Quality Metrics */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||
<CheckCircle className="w-5 h-5 text-green-500" />
|
||||
Indicadores de Calidad
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="text-center p-3 bg-gray-50 rounded-lg">
|
||||
<p className="text-2xl font-bold text-green-600">{inventoryStats.qualityScore}%</p>
|
||||
<p className="text-sm text-gray-600">Puntuación Seguridad</p>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-gray-50 rounded-lg">
|
||||
<p className="text-2xl font-bold text-blue-600">{Number(analyticsData.temperature_compliance_rate || 95).toFixed(1)}%</p>
|
||||
<p className="text-sm text-gray-600">Cumplimiento Temp.</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Low Stock Alert */}
|
||||
{lowStockItems.length > 0 && (
|
||||
<LowStockAlert items={lowStockItems} />
|
||||
|
||||
Reference in New Issue
Block a user