Improve the inventory page
This commit is contained in:
297
frontend/src/components/domain/inventory/EditItemModal.tsx
Normal file
297
frontend/src/components/domain/inventory/EditItemModal.tsx
Normal file
@@ -0,0 +1,297 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user