Add improved production UI 4

This commit is contained in:
Urtzi Alfaro
2025-09-23 22:11:34 +02:00
parent 7892c5a739
commit 87310ced5f
17 changed files with 1658 additions and 296 deletions

View File

@@ -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

View File

@@ -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>

View File

@@ -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,

View File

@@ -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');