Add improved production UI 4
This commit is contained in:
@@ -8,17 +8,12 @@ import StatsGrid from '../../components/ui/Stats/StatsGrid';
|
||||
import RealTimeAlerts from '../../components/domain/dashboard/RealTimeAlerts';
|
||||
import ProcurementPlansToday from '../../components/domain/dashboard/ProcurementPlansToday';
|
||||
import ProductionPlansToday from '../../components/domain/dashboard/ProductionPlansToday';
|
||||
import ProductionCostMonitor from '../../components/domain/dashboard/ProductionCostMonitor';
|
||||
import EquipmentStatusWidget from '../../components/domain/dashboard/EquipmentStatusWidget';
|
||||
import AIInsightsWidget from '../../components/domain/dashboard/AIInsightsWidget';
|
||||
import { useTenant } from '../../stores/tenant.store';
|
||||
import {
|
||||
AlertTriangle,
|
||||
Clock,
|
||||
Euro,
|
||||
Package,
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
Plus,
|
||||
Building2
|
||||
} from 'lucide-react';
|
||||
@@ -173,15 +168,6 @@ const DashboardPage: React.FC = () => {
|
||||
onViewDetails={handleViewDetails}
|
||||
onViewAllPlans={handleViewAllPlans}
|
||||
/>
|
||||
|
||||
{/* 4. Production Cost Monitor */}
|
||||
<ProductionCostMonitor />
|
||||
|
||||
{/* 5. Equipment Status Widget */}
|
||||
<EquipmentStatusWidget />
|
||||
|
||||
{/* 6. AI Insights Widget */}
|
||||
<AIInsightsWidget />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,9 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { Calendar, TrendingUp, AlertTriangle, BarChart3, Download, Settings, Loader, Zap, Brain, Target, CloudRain, Sun, Thermometer, Package, Activity, Clock } from 'lucide-react';
|
||||
import { Button, Card, Badge, Table, StatsGrid, StatusCard, getStatusColor } from '../../../../components/ui';
|
||||
import type { TableColumn } from '../../../../components/ui';
|
||||
import { Calendar, TrendingUp, AlertTriangle, BarChart3, Settings, Loader, Zap, Brain, Target, Activity } from 'lucide-react';
|
||||
import { Button, Card, Badge, StatsGrid, StatusCard, getStatusColor } from '../../../../components/ui';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
import { LoadingSpinner } from '../../../../components/shared';
|
||||
import { DemandChart, ForecastTable } from '../../../../components/domain/forecasting';
|
||||
import { DemandChart } from '../../../../components/domain/forecasting';
|
||||
import { useTenantForecasts, useCreateSingleForecast } from '../../../../api/hooks/forecasting';
|
||||
import { useIngredients } from '../../../../api/hooks/inventory';
|
||||
import { useModels } from '../../../../api/hooks/training';
|
||||
@@ -16,7 +15,6 @@ import { formatters } from '../../../../components/ui/Stats/StatsPresets';
|
||||
const ForecastingPage: React.FC = () => {
|
||||
const [selectedProduct, setSelectedProduct] = useState('');
|
||||
const [forecastPeriod, setForecastPeriod] = useState('7');
|
||||
const [viewMode, setViewMode] = useState<'chart' | 'table'>('chart');
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
const [hasGeneratedForecast, setHasGeneratedForecast] = useState(false);
|
||||
const [currentForecastData, setCurrentForecastData] = useState<ForecastResponse[]>([]);
|
||||
@@ -132,63 +130,8 @@ const ForecastingPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Transform forecast data for table display - only real data
|
||||
const transformForecastsForTable = (forecasts: ForecastResponse[]) => {
|
||||
return forecasts.map(forecast => ({
|
||||
id: forecast.id,
|
||||
product: forecast.inventory_product_id,
|
||||
forecastDate: forecast.forecast_date,
|
||||
forecastDemand: forecast.predicted_demand,
|
||||
confidence: Math.round(forecast.confidence_level * 100),
|
||||
confidenceRange: `${forecast.confidence_lower?.toFixed(1) || 'N/A'} - ${forecast.confidence_upper?.toFixed(1) || 'N/A'}`,
|
||||
algorithm: forecast.algorithm,
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const forecastColumns: TableColumn[] = [
|
||||
{
|
||||
key: 'product',
|
||||
title: 'Producto ID',
|
||||
dataIndex: 'product',
|
||||
},
|
||||
{
|
||||
key: 'forecastDate',
|
||||
title: 'Fecha',
|
||||
dataIndex: 'forecastDate',
|
||||
render: (value) => new Date(value).toLocaleDateString('es-ES'),
|
||||
},
|
||||
{
|
||||
key: 'forecastDemand',
|
||||
title: 'Demanda Prevista',
|
||||
dataIndex: 'forecastDemand',
|
||||
render: (value) => (
|
||||
<span className="font-medium text-[var(--color-info)]">{value?.toFixed(2) || 'N/A'}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'confidence',
|
||||
title: 'Confianza',
|
||||
dataIndex: 'confidence',
|
||||
render: (value) => `${value}%`,
|
||||
},
|
||||
{
|
||||
key: 'confidenceRange',
|
||||
title: 'Rango de Confianza',
|
||||
dataIndex: 'confidenceRange',
|
||||
},
|
||||
{
|
||||
key: 'algorithm',
|
||||
title: 'Algoritmo',
|
||||
dataIndex: 'algorithm',
|
||||
},
|
||||
];
|
||||
|
||||
// Use either current forecast data or fetched data
|
||||
const forecasts = currentForecastData.length > 0 ? currentForecastData : (forecastsData?.forecasts || []);
|
||||
const transformedForecasts = transformForecastsForTable(forecasts);
|
||||
const isLoading = forecastsLoading || ingredientsLoading || modelsLoading || isGenerating;
|
||||
const hasError = forecastsError || ingredientsError || modelsError;
|
||||
|
||||
@@ -467,47 +410,19 @@ const ForecastingPage: React.FC = () => {
|
||||
{products.find(p => p.id === selectedProduct)?.name} • {forecastPeriod} días
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex rounded-lg border border-[var(--border-secondary)] overflow-hidden">
|
||||
<Button
|
||||
variant={viewMode === 'chart' ? 'primary' : 'outline'}
|
||||
onClick={() => setViewMode('chart')}
|
||||
size="sm"
|
||||
>
|
||||
<BarChart3 className="w-4 h-4 mr-1" />
|
||||
Gráfico
|
||||
</Button>
|
||||
<Button
|
||||
variant={viewMode === 'table' ? 'primary' : 'outline'}
|
||||
onClick={() => setViewMode('table')}
|
||||
size="sm"
|
||||
>
|
||||
<Package className="w-4 h-4 mr-1" />
|
||||
Tabla
|
||||
</Button>
|
||||
</div>
|
||||
<Button variant="outline" size="sm">
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Exportar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chart or Table */}
|
||||
{/* Chart View */}
|
||||
<div className="min-h-96">
|
||||
{viewMode === 'chart' ? (
|
||||
<DemandChart
|
||||
data={forecasts}
|
||||
product={selectedProduct}
|
||||
period={forecastPeriod}
|
||||
loading={isLoading}
|
||||
error={hasError ? 'Error al cargar las predicciones' : null}
|
||||
height={400}
|
||||
title=""
|
||||
/>
|
||||
) : (
|
||||
<ForecastTable forecasts={transformedForecasts} />
|
||||
)}
|
||||
<DemandChart
|
||||
data={forecasts}
|
||||
product={selectedProduct}
|
||||
period={forecastPeriod}
|
||||
loading={isLoading}
|
||||
error={hasError ? 'Error al cargar las predicciones' : null}
|
||||
height={400}
|
||||
title=""
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Plus, AlertTriangle, Settings, CheckCircle, Eye, Clock, Zap, Wrench, Thermometer, Activity, Search, Filter, Download, TrendingUp, Bell, History, Calendar, Edit, Trash2 } from 'lucide-react';
|
||||
import { Plus, AlertTriangle, Settings, CheckCircle, Eye, Wrench, Thermometer, Activity, Search, Filter, Bell, History, Calendar, Edit, Trash2 } from 'lucide-react';
|
||||
import { Button, Input, Card, StatsGrid, StatusCard, getStatusColor } from '../../../../components/ui';
|
||||
import { Badge } from '../../../../components/ui/Badge';
|
||||
import { LoadingSpinner } from '../../../../components/shared';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
@@ -200,8 +201,6 @@ const MaquinariaPage: React.FC = () => {
|
||||
const warning = MOCK_EQUIPMENT.filter(e => e.status === 'warning').length;
|
||||
const maintenance = MOCK_EQUIPMENT.filter(e => e.status === 'maintenance').length;
|
||||
const down = MOCK_EQUIPMENT.filter(e => e.status === 'down').length;
|
||||
const avgEfficiency = MOCK_EQUIPMENT.reduce((sum, e) => sum + e.efficiency, 0) / total;
|
||||
const avgUptime = MOCK_EQUIPMENT.reduce((sum, e) => sum + e.uptime, 0) / total;
|
||||
const totalAlerts = MOCK_EQUIPMENT.reduce((sum, e) => sum + e.alerts.filter(a => !a.acknowledged).length, 0);
|
||||
|
||||
return {
|
||||
@@ -210,8 +209,6 @@ const MaquinariaPage: React.FC = () => {
|
||||
warning,
|
||||
maintenance,
|
||||
down,
|
||||
avgEfficiency,
|
||||
avgUptime,
|
||||
totalAlerts
|
||||
};
|
||||
}, [MOCK_EQUIPMENT]);
|
||||
@@ -231,23 +228,13 @@ const MaquinariaPage: React.FC = () => {
|
||||
oven: Thermometer,
|
||||
mixer: Activity,
|
||||
proofer: Settings,
|
||||
freezer: Zap,
|
||||
freezer: Settings,
|
||||
packaging: Settings,
|
||||
other: Settings
|
||||
};
|
||||
return icons[type];
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string): string => {
|
||||
const statusColors: { [key: string]: string } = {
|
||||
operational: '#10B981', // green-500
|
||||
warning: '#F59E0B', // amber-500
|
||||
maintenance: '#3B82F6', // blue-500
|
||||
down: '#EF4444' // red-500
|
||||
};
|
||||
return statusColors[status] || '#6B7280'; // gray-500 as default
|
||||
};
|
||||
|
||||
const stats = [
|
||||
{
|
||||
title: t('labels.total_equipment'),
|
||||
@@ -262,12 +249,6 @@ const MaquinariaPage: React.FC = () => {
|
||||
variant: 'success' as const,
|
||||
subtitle: `${((equipmentStats.operational / equipmentStats.total) * 100).toFixed(1)}%`
|
||||
},
|
||||
{
|
||||
title: t('labels.avg_efficiency'),
|
||||
value: `${equipmentStats.avgEfficiency.toFixed(1)}%`,
|
||||
icon: TrendingUp,
|
||||
variant: equipmentStats.avgEfficiency >= 90 ? 'success' as const : 'warning' as const
|
||||
},
|
||||
{
|
||||
title: t('labels.active_alerts'),
|
||||
value: equipmentStats.totalAlerts,
|
||||
|
||||
@@ -264,7 +264,7 @@ const ProfilePage: React.FC = () => {
|
||||
|
||||
// Subscription handlers
|
||||
const loadSubscriptionData = async () => {
|
||||
let tenantId = currentTenant?.id || user?.tenant_id;
|
||||
const tenantId = currentTenant?.id || user?.tenant_id;
|
||||
|
||||
if (!tenantId) {
|
||||
addToast('No se encontró información del tenant', 'error');
|
||||
@@ -294,7 +294,7 @@ const ProfilePage: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleUpgradeConfirm = async () => {
|
||||
let tenantId = currentTenant?.id || user?.tenant_id;
|
||||
const tenantId = currentTenant?.id || user?.tenant_id;
|
||||
|
||||
if (!tenantId || !selectedPlan) {
|
||||
addToast('Información de tenant no disponible', 'error');
|
||||
|
||||
Reference in New Issue
Block a user