Improve the frontend modals
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { Brain, TrendingUp, AlertCircle, Play, RotateCcw, Eye, Loader, CheckCircle } from 'lucide-react';
|
||||
import { Button, Badge, Modal, Table, Select, StatsGrid, StatusCard, SearchAndFilter, type FilterConfig, Card } from '../../../../components/ui';
|
||||
import { Button, Badge, Modal, Table, Select, StatsGrid, StatusCard, SearchAndFilter, type FilterConfig, Card, EmptyState } from '../../../../components/ui';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
import { useToast } from '../../../../hooks/ui/useToast';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
@@ -116,7 +116,7 @@ const ModelsConfigPage: React.FC = () => {
|
||||
hasModel: !!model,
|
||||
model,
|
||||
isTraining,
|
||||
lastTrainingDate: model?.created_at,
|
||||
lastTrainingDate: model?.created_at || undefined,
|
||||
accuracy: model ?
|
||||
(model.training_metrics?.mape !== undefined ? (100 - model.training_metrics.mape) :
|
||||
(model as any).mape !== undefined ? (100 - (model as any).mape) :
|
||||
@@ -209,13 +209,12 @@ const ModelsConfigPage: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="Configuración de Modelos IA"
|
||||
description="Gestiona el entrenamiento y configuración de modelos de predicción para cada ingrediente"
|
||||
/>
|
||||
|
||||
|
||||
{/* Statistics Cards */}
|
||||
<StatsGrid
|
||||
stats={[
|
||||
@@ -232,39 +231,33 @@ const ModelsConfigPage: React.FC = () => {
|
||||
variant: 'warning',
|
||||
},
|
||||
{
|
||||
title: 'Modelos Huérfanos',
|
||||
value: orphanedModels.length,
|
||||
icon: AlertCircle,
|
||||
variant: 'info',
|
||||
title: 'Modelos Activos',
|
||||
value: modelStatuses.filter(s => s.status === 'active').length,
|
||||
icon: CheckCircle,
|
||||
variant: 'success',
|
||||
},
|
||||
{
|
||||
title: 'Precisión Promedio',
|
||||
value: statsError ? 'N/A' : (statistics?.average_accuracy ? `${(100 - statistics.average_accuracy).toFixed(1)}%` : 'N/A'),
|
||||
value: statsError ? 'N/A' : (statistics?.average_accuracy ? `${Number(statistics.average_accuracy).toFixed(1)}%` : 'N/A'),
|
||||
icon: TrendingUp,
|
||||
variant: 'success',
|
||||
},
|
||||
{
|
||||
title: 'Total Modelos',
|
||||
value: modelStatuses.length,
|
||||
icon: Brain,
|
||||
variant: 'info',
|
||||
},
|
||||
{
|
||||
title: 'Modelos Huérfanos',
|
||||
value: orphanedModels.length,
|
||||
icon: AlertCircle,
|
||||
variant: 'error',
|
||||
},
|
||||
]}
|
||||
columns={4}
|
||||
columns={3}
|
||||
/>
|
||||
|
||||
{/* Orphaned Models Warning */}
|
||||
{orphanedModels.length > 0 && (
|
||||
<Card className="p-4 bg-orange-50 border-orange-200">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertCircle className="w-5 h-5 text-orange-600 mt-0.5" />
|
||||
<div>
|
||||
<h4 className="font-medium text-orange-900 mb-1">
|
||||
Modelos Huérfanos Detectados
|
||||
</h4>
|
||||
<p className="text-sm text-orange-700">
|
||||
Se encontraron {orphanedModels.length} modelos entrenados para ingredientes que ya no existen en el inventario.
|
||||
Estos modelos pueden ser eliminados para optimizar el espacio de almacenamiento.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Search and Filter Controls */}
|
||||
<SearchAndFilter
|
||||
searchValue={searchTerm}
|
||||
@@ -289,18 +282,16 @@ const ModelsConfigPage: React.FC = () => {
|
||||
/>
|
||||
|
||||
{/* Models Grid */}
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{filteredStatuses.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-12 col-span-full">
|
||||
<Brain className="w-12 h-12 text-[var(--color-secondary)] mb-4" />
|
||||
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-2">
|
||||
No se encontraron ingredientes
|
||||
</h3>
|
||||
<p className="text-[var(--text-secondary)] text-center">
|
||||
No hay ingredientes que coincidan con los filtros aplicados.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
{filteredStatuses.length === 0 ? (
|
||||
<EmptyState
|
||||
icon={Brain}
|
||||
title="No se encontraron ingredientes"
|
||||
description="No hay ingredientes que coincidan con los filtros aplicados."
|
||||
className="col-span-full"
|
||||
/>
|
||||
) : (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{(
|
||||
filteredStatuses.map((status) => {
|
||||
// Get status configuration for the StatusCard
|
||||
const statusConfig = {
|
||||
@@ -335,7 +326,7 @@ const ModelsConfigPage: React.FC = () => {
|
||||
id={status.ingredient.id}
|
||||
statusIndicator={statusConfig}
|
||||
title={status.ingredient.name}
|
||||
subtitle={status.ingredient.category}
|
||||
subtitle={status.ingredient.category || undefined}
|
||||
primaryValue={status.accuracy ? status.accuracy.toFixed(1) : 'N/A'}
|
||||
primaryValueLabel="Precisión"
|
||||
secondaryInfo={status.lastTrainingDate ? {
|
||||
@@ -371,7 +362,8 @@ const ModelsConfigPage: React.FC = () => {
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Training Modal */}
|
||||
<Modal
|
||||
@@ -463,4 +455,4 @@ const ModelsConfigPage: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelsConfigPage;
|
||||
export default ModelsConfigPage;
|
||||
|
||||
Reference in New Issue
Block a user