297 lines
8.3 KiB
TypeScript
297 lines
8.3 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Edit, Package, AlertTriangle, Settings, Thermometer } from 'lucide-react';
|
|
import { StatusModal } from '../../ui/StatusModal/StatusModal';
|
|
import { IngredientResponse, IngredientUpdate, IngredientCategory, UnitOfMeasure } from '../../../api/types/inventory';
|
|
import { statusColors } from '../../../styles/colors';
|
|
|
|
interface EditItemModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
ingredient: IngredientResponse;
|
|
onUpdateIngredient?: (id: string, updateData: IngredientUpdate) => Promise<void>;
|
|
}
|
|
|
|
/**
|
|
* EditItemModal - Focused modal for editing ingredient details
|
|
* Organized form for updating ingredient properties
|
|
*/
|
|
export const EditItemModal: React.FC<EditItemModalProps> = ({
|
|
isOpen,
|
|
onClose,
|
|
ingredient,
|
|
onUpdateIngredient
|
|
}) => {
|
|
const [formData, setFormData] = useState<IngredientUpdate>({
|
|
name: ingredient.name,
|
|
description: ingredient.description || '',
|
|
category: ingredient.category,
|
|
brand: ingredient.brand || '',
|
|
unit_of_measure: ingredient.unit_of_measure,
|
|
average_cost: ingredient.average_cost || 0,
|
|
low_stock_threshold: ingredient.low_stock_threshold,
|
|
reorder_point: ingredient.reorder_point,
|
|
max_stock_level: ingredient.max_stock_level || undefined,
|
|
requires_refrigeration: ingredient.requires_refrigeration,
|
|
requires_freezing: ingredient.requires_freezing,
|
|
shelf_life_days: ingredient.shelf_life_days || undefined,
|
|
storage_instructions: ingredient.storage_instructions || '',
|
|
is_active: ingredient.is_active,
|
|
notes: ingredient.notes || ''
|
|
});
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
const [mode, setMode] = useState<'overview' | 'edit'>('edit');
|
|
|
|
const handleFieldChange = (sectionIndex: number, fieldIndex: number, value: string | number | boolean) => {
|
|
// Map field positions to form data fields
|
|
const fieldMappings = [
|
|
// Basic Information section
|
|
['name', 'description', 'category', 'brand'],
|
|
// Measurements section
|
|
['unit_of_measure', 'average_cost', 'low_stock_threshold', 'reorder_point', 'max_stock_level'],
|
|
// Storage Requirements section
|
|
['requires_refrigeration', 'requires_freezing', 'shelf_life_days', 'storage_instructions'],
|
|
// Additional Settings section
|
|
['is_active', 'notes']
|
|
];
|
|
|
|
const fieldName = fieldMappings[sectionIndex]?.[fieldIndex] as keyof IngredientUpdate;
|
|
if (fieldName) {
|
|
setFormData(prev => ({
|
|
...prev,
|
|
[fieldName]: value
|
|
}));
|
|
}
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
if (!formData.name?.trim()) {
|
|
alert('El nombre es requerido');
|
|
return;
|
|
}
|
|
|
|
if (!formData.low_stock_threshold || formData.low_stock_threshold < 0) {
|
|
alert('El umbral de stock bajo debe ser un número positivo');
|
|
return;
|
|
}
|
|
|
|
if (!formData.reorder_point || formData.reorder_point < 0) {
|
|
alert('El punto de reorden debe ser un número positivo');
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
try {
|
|
if (onUpdateIngredient) {
|
|
await onUpdateIngredient(ingredient.id, formData);
|
|
}
|
|
onClose();
|
|
} catch (error) {
|
|
console.error('Error updating ingredient:', error);
|
|
alert('Error al actualizar el artículo. Por favor, intenta de nuevo.');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const statusConfig = {
|
|
color: statusColors.inProgress.primary,
|
|
text: 'Editar Artículo',
|
|
icon: Edit
|
|
};
|
|
|
|
const categoryOptions = Object.values(IngredientCategory).map(cat => ({
|
|
label: cat.charAt(0).toUpperCase() + cat.slice(1),
|
|
value: cat
|
|
}));
|
|
|
|
const unitOptions = Object.values(UnitOfMeasure).map(unit => ({
|
|
label: unit,
|
|
value: unit
|
|
}));
|
|
|
|
const sections = [
|
|
{
|
|
title: 'Información Básica',
|
|
icon: Package,
|
|
fields: [
|
|
{
|
|
label: 'Nombre',
|
|
value: formData.name || '',
|
|
type: 'text' as const,
|
|
editable: true,
|
|
required: true,
|
|
placeholder: 'Nombre del artículo'
|
|
},
|
|
{
|
|
label: 'Descripción',
|
|
value: formData.description || '',
|
|
type: 'text' as const,
|
|
editable: true,
|
|
placeholder: 'Descripción del artículo'
|
|
},
|
|
{
|
|
label: 'Categoría',
|
|
value: formData.category || '',
|
|
type: 'select' as const,
|
|
editable: true,
|
|
required: true,
|
|
options: categoryOptions
|
|
},
|
|
{
|
|
label: 'Marca',
|
|
value: formData.brand || '',
|
|
type: 'text' as const,
|
|
editable: true,
|
|
placeholder: 'Marca del producto'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
title: 'Medidas y Costos',
|
|
icon: Settings,
|
|
fields: [
|
|
{
|
|
label: 'Unidad de Medida',
|
|
value: formData.unit_of_measure || '',
|
|
type: 'select' as const,
|
|
editable: true,
|
|
required: true,
|
|
options: unitOptions
|
|
},
|
|
{
|
|
label: 'Costo Promedio',
|
|
value: formData.average_cost || 0,
|
|
type: 'currency' as const,
|
|
editable: true,
|
|
placeholder: '0.00'
|
|
},
|
|
{
|
|
label: 'Umbral Stock Bajo',
|
|
value: formData.low_stock_threshold || 0,
|
|
type: 'number' as const,
|
|
editable: true,
|
|
required: true,
|
|
placeholder: 'Cantidad mínima'
|
|
},
|
|
{
|
|
label: 'Punto de Reorden',
|
|
value: formData.reorder_point || 0,
|
|
type: 'number' as const,
|
|
editable: true,
|
|
required: true,
|
|
placeholder: 'Cuando reordenar'
|
|
},
|
|
{
|
|
label: 'Stock Máximo',
|
|
value: formData.max_stock_level || 0,
|
|
type: 'number' as const,
|
|
editable: true,
|
|
placeholder: 'Stock máximo (opcional)'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
title: 'Requisitos de Almacenamiento',
|
|
icon: Thermometer,
|
|
fields: [
|
|
{
|
|
label: 'Requiere Refrigeración',
|
|
value: formData.requires_refrigeration ? 'Sí' : 'No',
|
|
type: 'select' as const,
|
|
editable: true,
|
|
options: [
|
|
{ label: 'No', value: false },
|
|
{ label: 'Sí', value: true }
|
|
]
|
|
},
|
|
{
|
|
label: 'Requiere Congelación',
|
|
value: formData.requires_freezing ? 'Sí' : 'No',
|
|
type: 'select' as const,
|
|
editable: true,
|
|
options: [
|
|
{ label: 'No', value: false },
|
|
{ label: 'Sí', value: true }
|
|
]
|
|
},
|
|
{
|
|
label: 'Vida Útil (días)',
|
|
value: formData.shelf_life_days || 0,
|
|
type: 'number' as const,
|
|
editable: true,
|
|
placeholder: 'Días de duración'
|
|
},
|
|
{
|
|
label: 'Instrucciones de Almacenamiento',
|
|
value: formData.storage_instructions || '',
|
|
type: 'text' as const,
|
|
editable: true,
|
|
placeholder: 'Instrucciones especiales...',
|
|
span: 2 as const
|
|
}
|
|
]
|
|
},
|
|
{
|
|
title: 'Configuración Adicional',
|
|
icon: AlertTriangle,
|
|
fields: [
|
|
{
|
|
label: 'Estado',
|
|
value: formData.is_active ? 'Activo' : 'Inactivo',
|
|
type: 'select' as const,
|
|
editable: true,
|
|
options: [
|
|
{ label: 'Activo', value: true },
|
|
{ label: 'Inactivo', value: false }
|
|
]
|
|
},
|
|
{
|
|
label: 'Notas',
|
|
value: formData.notes || '',
|
|
type: 'text' as const,
|
|
editable: true,
|
|
placeholder: 'Notas adicionales...',
|
|
span: 2 as const
|
|
}
|
|
]
|
|
}
|
|
];
|
|
|
|
const actions = [
|
|
{
|
|
label: 'Cancelar',
|
|
variant: 'outline' as const,
|
|
onClick: onClose,
|
|
disabled: loading
|
|
},
|
|
{
|
|
label: 'Guardar Cambios',
|
|
variant: 'primary' as const,
|
|
onClick: handleSave,
|
|
disabled: loading || !formData.name?.trim(),
|
|
loading
|
|
}
|
|
];
|
|
|
|
return (
|
|
<StatusModal
|
|
isOpen={isOpen}
|
|
onClose={onClose}
|
|
mode={mode}
|
|
onModeChange={setMode}
|
|
title={`Editar: ${ingredient.name}`}
|
|
subtitle={`${ingredient.category} • ID: ${ingredient.id.slice(0, 8)}...`}
|
|
statusIndicator={statusConfig}
|
|
sections={sections}
|
|
actions={actions}
|
|
onFieldChange={handleFieldChange}
|
|
onSave={handleSave}
|
|
size="xl"
|
|
loading={loading}
|
|
showDefaultActions={false}
|
|
/>
|
|
);
|
|
};
|
|
|
|
export default EditItemModal; |