Files
bakery-ia/frontend/src/components/domain/inventory/EditItemModal.tsx
2025-09-17 16:06:30 +02:00

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;