Improve the frontend 3
This commit is contained in:
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
||||
import { Settings, Save, RotateCcw, AlertCircle, Loader } from 'lucide-react';
|
||||
import { Button, Card } from '../../../../components/ui';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
import { useToast } from '../../../../hooks/ui/useToast';
|
||||
import { showToast } from '../../../../utils/toast';
|
||||
import { useSettings, useUpdateSettings } from '../../../../api/hooks/settings';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
import type {
|
||||
@@ -13,6 +13,10 @@ import type {
|
||||
SupplierSettings,
|
||||
POSSettings,
|
||||
OrderSettings,
|
||||
ReplenishmentSettings,
|
||||
SafetyStockSettings,
|
||||
MOQSettings,
|
||||
SupplierSelectionSettings,
|
||||
} from '../../../../api/types/settings';
|
||||
import ProcurementSettingsCard from './cards/ProcurementSettingsCard';
|
||||
import InventorySettingsCard from './cards/InventorySettingsCard';
|
||||
@@ -20,9 +24,13 @@ import ProductionSettingsCard from './cards/ProductionSettingsCard';
|
||||
import SupplierSettingsCard from './cards/SupplierSettingsCard';
|
||||
import POSSettingsCard from './cards/POSSettingsCard';
|
||||
import OrderSettingsCard from './cards/OrderSettingsCard';
|
||||
import ReplenishmentSettingsCard from './cards/ReplenishmentSettingsCard';
|
||||
import SafetyStockSettingsCard from './cards/SafetyStockSettingsCard';
|
||||
import MOQSettingsCard from './cards/MOQSettingsCard';
|
||||
import SupplierSelectionSettingsCard from './cards/SupplierSelectionSettingsCard';
|
||||
|
||||
const AjustesPage: React.FC = () => {
|
||||
const { addToast } = useToast();
|
||||
|
||||
const currentTenant = useCurrentTenant();
|
||||
const tenantId = currentTenant?.id || '';
|
||||
|
||||
@@ -52,6 +60,10 @@ const AjustesPage: React.FC = () => {
|
||||
const [supplierSettings, setSupplierSettings] = useState<SupplierSettings | null>(null);
|
||||
const [posSettings, setPosSettings] = useState<POSSettings | null>(null);
|
||||
const [orderSettings, setOrderSettings] = useState<OrderSettings | null>(null);
|
||||
const [replenishmentSettings, setReplenishmentSettings] = useState<ReplenishmentSettings | null>(null);
|
||||
const [safetyStockSettings, setSafetyStockSettings] = useState<SafetyStockSettings | null>(null);
|
||||
const [moqSettings, setMoqSettings] = useState<MOQSettings | null>(null);
|
||||
const [supplierSelectionSettings, setSupplierSelectionSettings] = useState<SupplierSelectionSettings | null>(null);
|
||||
|
||||
// Load settings into local state when data is fetched
|
||||
React.useEffect(() => {
|
||||
@@ -62,13 +74,18 @@ const AjustesPage: React.FC = () => {
|
||||
setSupplierSettings(settings.supplier_settings);
|
||||
setPosSettings(settings.pos_settings);
|
||||
setOrderSettings(settings.order_settings);
|
||||
setReplenishmentSettings(settings.replenishment_settings);
|
||||
setSafetyStockSettings(settings.safety_stock_settings);
|
||||
setMoqSettings(settings.moq_settings);
|
||||
setSupplierSelectionSettings(settings.supplier_selection_settings);
|
||||
setHasUnsavedChanges(false);
|
||||
}
|
||||
}, [settings]);
|
||||
|
||||
const handleSaveAll = async () => {
|
||||
if (!tenantId || !procurementSettings || !inventorySettings || !productionSettings ||
|
||||
!supplierSettings || !posSettings || !orderSettings) {
|
||||
!supplierSettings || !posSettings || !orderSettings || !replenishmentSettings ||
|
||||
!safetyStockSettings || !moqSettings || !supplierSelectionSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -84,14 +101,18 @@ const AjustesPage: React.FC = () => {
|
||||
supplier_settings: supplierSettings,
|
||||
pos_settings: posSettings,
|
||||
order_settings: orderSettings,
|
||||
replenishment_settings: replenishmentSettings,
|
||||
safety_stock_settings: safetyStockSettings,
|
||||
moq_settings: moqSettings,
|
||||
supplier_selection_settings: supplierSelectionSettings,
|
||||
},
|
||||
});
|
||||
|
||||
setHasUnsavedChanges(false);
|
||||
addToast('Ajustes guardados correctamente', { type: 'success' });
|
||||
showToast.success('Ajustes guardados correctamente');
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Error desconocido';
|
||||
addToast(`Error al guardar ajustes: ${errorMessage}`, { type: 'error' });
|
||||
showToast.error(`Error al guardar ajustes: ${errorMessage}`);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
@@ -105,6 +126,10 @@ const AjustesPage: React.FC = () => {
|
||||
setSupplierSettings(settings.supplier_settings);
|
||||
setPosSettings(settings.pos_settings);
|
||||
setOrderSettings(settings.order_settings);
|
||||
setReplenishmentSettings(settings.replenishment_settings);
|
||||
setSafetyStockSettings(settings.safety_stock_settings);
|
||||
setMoqSettings(settings.moq_settings);
|
||||
setSupplierSelectionSettings(settings.supplier_selection_settings);
|
||||
setHasUnsavedChanges(false);
|
||||
}
|
||||
};
|
||||
@@ -256,6 +281,54 @@ const AjustesPage: React.FC = () => {
|
||||
disabled={isSaving}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Replenishment Settings */}
|
||||
{replenishmentSettings && (
|
||||
<ReplenishmentSettingsCard
|
||||
settings={replenishmentSettings}
|
||||
onChange={(newSettings) => {
|
||||
setReplenishmentSettings(newSettings);
|
||||
handleCategoryChange('replenishment');
|
||||
}}
|
||||
disabled={isSaving}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Safety Stock Settings */}
|
||||
{safetyStockSettings && (
|
||||
<SafetyStockSettingsCard
|
||||
settings={safetyStockSettings}
|
||||
onChange={(newSettings) => {
|
||||
setSafetyStockSettings(newSettings);
|
||||
handleCategoryChange('safety_stock');
|
||||
}}
|
||||
disabled={isSaving}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* MOQ Settings */}
|
||||
{moqSettings && (
|
||||
<MOQSettingsCard
|
||||
settings={moqSettings}
|
||||
onChange={(newSettings) => {
|
||||
setMoqSettings(newSettings);
|
||||
handleCategoryChange('moq');
|
||||
}}
|
||||
disabled={isSaving}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Supplier Selection Settings */}
|
||||
{supplierSelectionSettings && (
|
||||
<SupplierSelectionSettingsCard
|
||||
settings={supplierSelectionSettings}
|
||||
onChange={(newSettings) => {
|
||||
setSupplierSelectionSettings(newSettings);
|
||||
handleCategoryChange('supplier_selection');
|
||||
}}
|
||||
disabled={isSaving}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Floating Save Banner */}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import React from 'react';
|
||||
import { Card } from '@components/ui';
|
||||
import { MOQSettings } from '@services/types/settings';
|
||||
import { Input } from '@components/ui/Input';
|
||||
|
||||
interface MOQSettingsCardProps {
|
||||
settings: MOQSettings;
|
||||
onChange: (settings: MOQSettings) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const MOQSettingsCard: React.FC<MOQSettingsCardProps> = ({
|
||||
settings,
|
||||
onChange,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const handleNumberChange = (field: keyof MOQSettings, value: string) => {
|
||||
const numValue = value === '' ? 0 : Number(value);
|
||||
onChange({
|
||||
...settings,
|
||||
[field]: numValue,
|
||||
});
|
||||
};
|
||||
|
||||
const handleToggleChange = (field: keyof MOQSettings, value: boolean) => {
|
||||
onChange({
|
||||
...settings,
|
||||
[field]: value,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-6">
|
||||
Configuración de MOQ (Cantidad Mínima de Pedido)
|
||||
</h3>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Consolidation Window Days */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Días de Ventana de Consolidación (1-30)
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
max="30"
|
||||
value={settings.consolidation_window_days}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleNumberChange('consolidation_window_days', e.target.value)}
|
||||
disabled={disabled}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Min Batch Size */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Tamaño Mínimo de Lote (0.1-1000)
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min="0.1"
|
||||
max="1000"
|
||||
step="0.1"
|
||||
value={settings.min_batch_size}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleNumberChange('min_batch_size', e.target.value)}
|
||||
disabled={disabled}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Max Batch Size */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Tamaño Máximo de Lote (1-10000)
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
max="10000"
|
||||
step="1"
|
||||
value={settings.max_batch_size}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleNumberChange('max_batch_size', e.target.value)}
|
||||
disabled={disabled}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Toggle Options */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="allow_early_ordering"
|
||||
checked={settings.allow_early_ordering}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleToggleChange('allow_early_ordering', e.target.checked)}
|
||||
disabled={disabled}
|
||||
className="rounded border-[var(--border-primary)]"
|
||||
/>
|
||||
<label htmlFor="allow_early_ordering" className="text-sm text-[var(--text-secondary)]">
|
||||
Permitir Pedido Anticipado
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="enable_batch_optimization"
|
||||
checked={settings.enable_batch_optimization}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleToggleChange('enable_batch_optimization', e.target.checked)}
|
||||
disabled={disabled}
|
||||
className="rounded border-[var(--border-primary)]"
|
||||
/>
|
||||
<label htmlFor="enable_batch_optimization" className="text-sm text-[var(--text-secondary)]">
|
||||
Habilitar Optimización de Lotes
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default MOQSettingsCard;
|
||||
@@ -0,0 +1,158 @@
|
||||
import React from 'react';
|
||||
import { Card } from '@components/ui';
|
||||
import { ReplenishmentSettings } from '@services/types/settings';
|
||||
import { Slider } from '@components/ui/Slider';
|
||||
import { Input } from '@components/ui/Input';
|
||||
|
||||
interface ReplenishmentSettingsCardProps {
|
||||
settings: ReplenishmentSettings;
|
||||
onChange: (settings: ReplenishmentSettings) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const ReplenishmentSettingsCard: React.FC<ReplenishmentSettingsCardProps> = ({
|
||||
settings,
|
||||
onChange,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const handleNumberChange = (field: keyof ReplenishmentSettings, value: string) => {
|
||||
const numValue = value === '' ? 0 : Number(value);
|
||||
onChange({
|
||||
...settings,
|
||||
[field]: numValue,
|
||||
});
|
||||
};
|
||||
|
||||
const handleToggleChange = (field: keyof ReplenishmentSettings, value: boolean) => {
|
||||
onChange({
|
||||
...settings,
|
||||
[field]: value,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-6">
|
||||
Planeamiento de Reposición
|
||||
</h3>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Projection Horizon Days */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Días de Proyección (1-30)
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
max="30"
|
||||
value={settings.projection_horizon_days}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleNumberChange('projection_horizon_days', e.target.value)}
|
||||
disabled={disabled}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Service Level */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Nivel de Servicio ({(settings.service_level * 100).toFixed(0)}%)
|
||||
</label>
|
||||
<Slider
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={[settings.service_level]}
|
||||
onValueChange={([value]: number[]) => handleNumberChange('service_level', value.toString())}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Buffer Days */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Días de Buffer (0-14)
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
max="14"
|
||||
value={settings.buffer_days}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleNumberChange('buffer_days', e.target.value)}
|
||||
disabled={disabled}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Demand Forecast Days */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Días de Previsión de Demanda (1-90)
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
max="90"
|
||||
value={settings.demand_forecast_days}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleNumberChange('demand_forecast_days', e.target.value)}
|
||||
disabled={disabled}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Min Order Quantity */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Cantidad Mínima de Pedido (0.1-1000)
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min="0.1"
|
||||
max="100"
|
||||
step="0.1"
|
||||
value={settings.min_order_quantity}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleNumberChange('min_order_quantity', e.target.value)}
|
||||
disabled={disabled}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Max Order Quantity */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Cantidad Máxima de Pedido (1-1000)
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
max="10000"
|
||||
step="1"
|
||||
value={settings.max_order_quantity}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleNumberChange('max_order_quantity', e.target.value)}
|
||||
disabled={disabled}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enable Auto Replenishment Toggle */}
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="enable_auto_replenishment"
|
||||
checked={settings.enable_auto_replenishment}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleToggleChange('enable_auto_replenishment', e.target.checked)}
|
||||
disabled={disabled}
|
||||
className="rounded border-[var(--border-primary)]"
|
||||
/>
|
||||
<label htmlFor="enable_auto_replenishment" className="text-sm text-[var(--text-secondary)]">
|
||||
Habilitar Reposición Automática
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReplenishmentSettingsCard;
|
||||
@@ -0,0 +1,130 @@
|
||||
import React from 'react';
|
||||
import { Card } from '@components/ui';
|
||||
import { SafetyStockSettings } from '@services/types/settings';
|
||||
import { Slider } from '@components/ui/Slider';
|
||||
import { Input } from '@components/ui/Input';
|
||||
import { Select } from '@components/ui/Select';
|
||||
|
||||
interface SafetyStockSettingsCardProps {
|
||||
settings: SafetyStockSettings;
|
||||
onChange: (settings: SafetyStockSettings) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const SafetyStockSettingsCard: React.FC<SafetyStockSettingsCardProps> = ({
|
||||
settings,
|
||||
onChange,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const handleNumberChange = (field: keyof SafetyStockSettings, value: string) => {
|
||||
const numValue = value === '' ? 0 : Number(value);
|
||||
onChange({
|
||||
...settings,
|
||||
[field]: numValue,
|
||||
});
|
||||
};
|
||||
|
||||
const handleStringChange = (field: keyof SafetyStockSettings, value: any) => {
|
||||
const stringValue = typeof value === 'object' && value !== null ? value[0] : value;
|
||||
onChange({
|
||||
...settings,
|
||||
[field]: stringValue.toString(),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-6">
|
||||
Configuración de Stock de Seguridad
|
||||
</h3>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Service Level */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Nivel de Servicio ({(settings.service_level * 100).toFixed(0)}%)
|
||||
</label>
|
||||
<Slider
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={[settings.service_level]}
|
||||
onValueChange={([value]: number[]) => handleNumberChange('service_level', value.toString())}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Method */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Método de Cálculo
|
||||
</label>
|
||||
<Select
|
||||
value={settings.method}
|
||||
onChange={(value) => handleStringChange('method', value)}
|
||||
disabled={disabled}
|
||||
options={[
|
||||
{ value: 'statistical', label: 'Estadístico (Z×σ×√L)' },
|
||||
{ value: 'fixed_percentage', label: 'Porcentaje Fijo (20%)' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Min Safety Stock */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Stock de Seguridad Mínimo (0-1000)
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
max="1000"
|
||||
step="0.1"
|
||||
value={settings.min_safety_stock}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleNumberChange('min_safety_stock', e.target.value)}
|
||||
disabled={disabled}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Max Safety Stock */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Stock de Seguridad Máximo (0-1000)
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
max="1000"
|
||||
step="0.1"
|
||||
value={settings.max_safety_stock}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleNumberChange('max_safety_stock', e.target.value)}
|
||||
disabled={disabled}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Reorder Point Calculation */}
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Método de Punto de Reorden
|
||||
</label>
|
||||
<Select
|
||||
value={settings.reorder_point_calculation}
|
||||
onChange={(value) => handleStringChange('reorder_point_calculation', value)}
|
||||
disabled={disabled}
|
||||
options={[
|
||||
{ value: 'safety_stock_plus_lead_time_demand', label: 'Stock de Seguridad + Demanda de Tiempo de Entrega' },
|
||||
{ value: 'safety_stock_only', label: 'Solo Stock de Seguridad' },
|
||||
{ value: 'fixed_quantity', label: 'Cantidad Fija' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default SafetyStockSettingsCard;
|
||||
@@ -0,0 +1,152 @@
|
||||
import React from 'react';
|
||||
import { Card } from '@components/ui';
|
||||
import { SupplierSelectionSettings } from '@services/types/settings';
|
||||
import { Slider } from '@components/ui/Slider';
|
||||
import { Input } from '@components/ui/Input';
|
||||
|
||||
interface SupplierSelectionSettingsCardProps {
|
||||
settings: SupplierSelectionSettings;
|
||||
onChange: (settings: SupplierSelectionSettings) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const SupplierSelectionSettingsCard: React.FC<SupplierSelectionSettingsCardProps> = ({
|
||||
settings,
|
||||
onChange,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const handleNumberChange = (field: keyof SupplierSelectionSettings, value: string) => {
|
||||
const numValue = value === '' ? 0 : Number(value);
|
||||
onChange({
|
||||
...settings,
|
||||
[field]: numValue,
|
||||
});
|
||||
};
|
||||
|
||||
const handleToggleChange = (field: keyof SupplierSelectionSettings, value: boolean) => {
|
||||
onChange({
|
||||
...settings,
|
||||
[field]: value,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-6">
|
||||
Configuración de Selección de Proveedores
|
||||
</h3>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Price Weight */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Peso del Precio ({(settings.price_weight * 100).toFixed(0)}%)
|
||||
</label>
|
||||
<Slider
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={[settings.price_weight]}
|
||||
onValueChange={([value]: number[]) => handleNumberChange('price_weight', value.toString())}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Lead Time Weight */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Peso del Tiempo de Entrega ({(settings.lead_time_weight * 100).toFixed(0)}%)
|
||||
</label>
|
||||
<Slider
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={[settings.lead_time_weight]}
|
||||
onValueChange={([value]: number[]) => handleNumberChange('lead_time_weight', value.toString())}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Quality Weight */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Peso de la Calidad ({(settings.quality_weight * 100).toFixed(0)}%)
|
||||
</label>
|
||||
<Slider
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={[settings.quality_weight]}
|
||||
onValueChange={([value]: number[]) => handleNumberChange('quality_weight', value.toString())}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Reliability Weight */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Peso de la Confiabilidad ({(settings.reliability_weight * 100).toFixed(0)}%)
|
||||
</label>
|
||||
<Slider
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={[settings.reliability_weight]}
|
||||
onValueChange={([value]: number[]) => handleNumberChange('reliability_weight', value.toString())}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Diversification Threshold */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Umbral de Diversificación (0-1000)
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
max="1000"
|
||||
value={settings.diversification_threshold}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleNumberChange('diversification_threshold', e.target.value)}
|
||||
disabled={disabled}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Max Single Percentage */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
Máximo % para Proveedor Único ({(settings.max_single_percentage * 100).toFixed(0)}%)
|
||||
</label>
|
||||
<Slider
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={[settings.max_single_percentage]}
|
||||
onValueChange={([value]: number[]) => handleNumberChange('max_single_percentage', value.toString())}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enable Supplier Score Optimization Toggle */}
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="enable_supplier_score_optimization"
|
||||
checked={settings.enable_supplier_score_optimization}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleToggleChange('enable_supplier_score_optimization', e.target.checked)}
|
||||
disabled={disabled}
|
||||
className="rounded border-[var(--border-primary)]"
|
||||
/>
|
||||
<label htmlFor="enable_supplier_score_optimization" className="text-sm text-[var(--text-secondary)]">
|
||||
Habilitar Optimización por Puntuación de Proveedores
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default SupplierSelectionSettingsCard;
|
||||
@@ -2,7 +2,7 @@ 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, EmptyState } from '../../../../components/ui';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
import { useToast } from '../../../../hooks/ui/useToast';
|
||||
import { showToast } from '../../../../utils/toast';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
import { useIngredients } from '../../../../api/hooks/inventory';
|
||||
import {
|
||||
@@ -40,7 +40,7 @@ interface ModelStatus {
|
||||
}
|
||||
|
||||
const ModelsConfigPage: React.FC = () => {
|
||||
const { addToast } = useToast();
|
||||
|
||||
const currentTenant = useCurrentTenant();
|
||||
const tenantId = currentTenant?.id || '';
|
||||
|
||||
@@ -160,10 +160,10 @@ const ModelsConfigPage: React.FC = () => {
|
||||
request: trainingSettings
|
||||
});
|
||||
|
||||
addToast(`Entrenamiento iniciado para ${selectedIngredient.name}`, { type: 'success' });
|
||||
showToast.success(`Entrenamiento iniciado para ${selectedIngredient.name}`);
|
||||
setShowTrainingModal(false);
|
||||
} catch (error) {
|
||||
addToast('Error al iniciar el entrenamiento', { type: 'error' });
|
||||
showToast.error('Error al iniciar el entrenamiento');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -206,12 +206,12 @@ const ModelsConfigPage: React.FC = () => {
|
||||
request: settings
|
||||
});
|
||||
|
||||
addToast(`Reentrenamiento iniciado para ${selectedIngredient.name}`, { type: 'success' });
|
||||
showToast.success(`Reentrenamiento iniciado para ${selectedIngredient.name}`);
|
||||
setShowRetrainModal(false);
|
||||
setSelectedIngredient(null);
|
||||
setSelectedModel(null);
|
||||
} catch (error) {
|
||||
addToast('Error al reentrenar el modelo', { type: 'error' });
|
||||
showToast.error('Error al reentrenar el modelo');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user