Create the frontend receipes page to use real API
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import { Card, Button, Badge, Input, Modal, Table, Select, DatePicker } from '../../ui';
|
||||
import { productionService, type ProductionBatchResponse, ProductionBatchStatus, ProductionPriority } from '../../../api/services/production.service';
|
||||
import { productionService, type ProductionBatchResponse, ProductionBatchStatus, ProductionPriority } from '../../../api';
|
||||
import type { ProductionBatch, QualityCheck } from '../../../types/production.types';
|
||||
|
||||
interface BatchTrackerProps {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import { Card, Button, Badge, Select, DatePicker, Input, Modal, Table } from '../../ui';
|
||||
import { productionService, type ProductionScheduleEntry, ProductionBatchStatus } from '../../../api/services/production.service';
|
||||
import { productionService, type ProductionScheduleEntry, ProductionBatchStatus } from '../../../api';
|
||||
import type { ProductionSchedule as ProductionScheduleType, ProductionBatch, EquipmentReservation, StaffAssignment } from '../../../types/production.types';
|
||||
|
||||
interface ProductionScheduleProps {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useCallback, useRef } from 'react';
|
||||
import { Card, Button, Badge, Input, Modal, Table, Select, DatePicker } from '../../ui';
|
||||
import { productionService, type QualityCheckResponse, QualityCheckStatus } from '../../../api/services/production.service';
|
||||
import { productionService, type QualityCheckResponse, QualityCheckStatus } from '../../../api';
|
||||
import type { QualityCheck, QualityCheckCriteria, ProductionBatch, QualityCheckType } from '../../../types/production.types';
|
||||
|
||||
interface QualityControlProps {
|
||||
|
||||
622
frontend/src/components/domain/recipes/CreateRecipeModal.tsx
Normal file
622
frontend/src/components/domain/recipes/CreateRecipeModal.tsx
Normal file
@@ -0,0 +1,622 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { ChefHat, Package, Clock, DollarSign, Star } from 'lucide-react';
|
||||
import { StatusModal } from '../../ui/StatusModal/StatusModal';
|
||||
import { RecipeCreate, RecipeIngredientCreate, MeasurementUnit } from '../../../api/types/recipes';
|
||||
import { useIngredients } from '../../../api/hooks/inventory';
|
||||
import { useCurrentTenant } from '../../../stores/tenant.store';
|
||||
|
||||
interface CreateRecipeModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onCreateRecipe?: (recipeData: RecipeCreate) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* CreateRecipeModal - Modal for creating a new recipe
|
||||
* Comprehensive form for adding new recipes
|
||||
*/
|
||||
export const CreateRecipeModal: React.FC<CreateRecipeModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onCreateRecipe
|
||||
}) => {
|
||||
const [formData, setFormData] = useState<RecipeCreate>({
|
||||
name: '',
|
||||
recipe_code: '',
|
||||
finished_product_id: '', // This should come from a product selector
|
||||
description: '',
|
||||
category: '',
|
||||
cuisine_type: '',
|
||||
difficulty_level: 1,
|
||||
yield_quantity: 1,
|
||||
yield_unit: MeasurementUnit.UNITS,
|
||||
prep_time_minutes: 0,
|
||||
cook_time_minutes: 0,
|
||||
total_time_minutes: 0,
|
||||
rest_time_minutes: 0,
|
||||
estimated_cost_per_unit: 0,
|
||||
target_margin_percentage: 30,
|
||||
suggested_selling_price: 0,
|
||||
preparation_notes: '',
|
||||
storage_instructions: '',
|
||||
quality_standards: '',
|
||||
serves_count: 1,
|
||||
is_seasonal: false,
|
||||
season_start_month: undefined,
|
||||
season_end_month: undefined,
|
||||
is_signature_item: false,
|
||||
batch_size_multiplier: 1.0,
|
||||
minimum_batch_size: undefined,
|
||||
maximum_batch_size: undefined,
|
||||
optimal_production_temperature: undefined,
|
||||
optimal_humidity: undefined,
|
||||
allergen_info: '',
|
||||
dietary_tags: '',
|
||||
nutritional_info: '',
|
||||
ingredients: []
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [mode, setMode] = useState<'overview' | 'edit'>('edit');
|
||||
|
||||
// Get tenant and fetch inventory data
|
||||
const currentTenant = useCurrentTenant();
|
||||
const tenantId = currentTenant?.id || '';
|
||||
|
||||
// Fetch inventory items to populate product and ingredient selectors
|
||||
const {
|
||||
data: inventoryItems = [],
|
||||
isLoading: inventoryLoading
|
||||
} = useIngredients(tenantId, {});
|
||||
|
||||
// Separate finished products and ingredients
|
||||
const finishedProducts = useMemo(() =>
|
||||
inventoryItems.filter(item => item.product_type === 'finished_product')
|
||||
.map(product => ({
|
||||
value: product.id,
|
||||
label: `${product.name} (${product.category || 'Sin categoría'})`
|
||||
})),
|
||||
[inventoryItems]
|
||||
);
|
||||
|
||||
const availableIngredients = useMemo(() =>
|
||||
inventoryItems.filter(item => item.product_type === 'ingredient')
|
||||
.map(ingredient => ({
|
||||
value: ingredient.id,
|
||||
label: `${ingredient.name} (${ingredient.unit_of_measure})`,
|
||||
unit: ingredient.unit_of_measure
|
||||
})),
|
||||
[inventoryItems]
|
||||
);
|
||||
|
||||
// Category options
|
||||
const categoryOptions = [
|
||||
{ value: 'bread', label: 'Pan' },
|
||||
{ value: 'pastry', label: 'Bollería' },
|
||||
{ value: 'cake', label: 'Tarta' },
|
||||
{ value: 'cookie', label: 'Galleta' },
|
||||
{ value: 'muffin', label: 'Muffin' },
|
||||
{ value: 'savory', label: 'Salado' },
|
||||
{ value: 'desserts', label: 'Postres' },
|
||||
{ value: 'specialty', label: 'Especialidad' },
|
||||
{ value: 'other', label: 'Otro' }
|
||||
];
|
||||
|
||||
// Cuisine type options
|
||||
const cuisineTypeOptions = [
|
||||
{ value: 'french', label: 'Francés' },
|
||||
{ value: 'spanish', label: 'Español' },
|
||||
{ value: 'italian', label: 'Italiano' },
|
||||
{ value: 'german', label: 'Alemán' },
|
||||
{ value: 'american', label: 'Americano' },
|
||||
{ value: 'artisanal', label: 'Artesanal' },
|
||||
{ value: 'traditional', label: 'Tradicional' },
|
||||
{ value: 'modern', label: 'Moderno' }
|
||||
];
|
||||
|
||||
// Unit options
|
||||
const unitOptions = [
|
||||
{ value: MeasurementUnit.UNITS, label: 'Unidades' },
|
||||
{ value: MeasurementUnit.PIECES, label: 'Piezas' },
|
||||
{ value: MeasurementUnit.GRAMS, label: 'Gramos' },
|
||||
{ value: MeasurementUnit.KILOGRAMS, label: 'Kilogramos' },
|
||||
{ value: MeasurementUnit.MILLILITERS, label: 'Mililitros' },
|
||||
{ value: MeasurementUnit.LITERS, label: 'Litros' }
|
||||
];
|
||||
|
||||
// Month options for seasonal recipes
|
||||
const monthOptions = [
|
||||
{ value: 1, label: 'Enero' },
|
||||
{ value: 2, label: 'Febrero' },
|
||||
{ value: 3, label: 'Marzo' },
|
||||
{ value: 4, label: 'Abril' },
|
||||
{ value: 5, label: 'Mayo' },
|
||||
{ value: 6, label: 'Junio' },
|
||||
{ value: 7, label: 'Julio' },
|
||||
{ value: 8, label: 'Agosto' },
|
||||
{ value: 9, label: 'Septiembre' },
|
||||
{ value: 10, label: 'Octubre' },
|
||||
{ value: 11, label: 'Noviembre' },
|
||||
{ value: 12, label: 'Diciembre' }
|
||||
];
|
||||
|
||||
// Allergen options
|
||||
const allergenOptions = [
|
||||
'Gluten', 'Lácteos', 'Huevos', 'Frutos secos', 'Soja', 'Sésamo', 'Pescado', 'Mariscos'
|
||||
];
|
||||
|
||||
// Dietary tags
|
||||
const dietaryTagOptions = [
|
||||
'Vegano', 'Vegetariano', 'Sin gluten', 'Sin lácteos', 'Sin frutos secos', 'Sin azúcar', 'Bajo en carbohidratos', 'Keto', 'Orgánico'
|
||||
];
|
||||
|
||||
const handleFieldChange = (sectionIndex: number, fieldIndex: number, value: string | number | boolean) => {
|
||||
const sections = getModalSections();
|
||||
const field = sections[sectionIndex]?.fields[fieldIndex];
|
||||
|
||||
if (!field) return;
|
||||
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[field.key]: value
|
||||
}));
|
||||
|
||||
// Auto-calculate total time when prep or cook time changes
|
||||
if (field.key === 'prep_time_minutes' || field.key === 'cook_time_minutes') {
|
||||
const prepTime = field.key === 'prep_time_minutes' ? Number(value) : formData.prep_time_minutes || 0;
|
||||
const cookTime = field.key === 'cook_time_minutes' ? Number(value) : formData.cook_time_minutes || 0;
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
total_time_minutes: prepTime + cookTime
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formData.name.trim()) {
|
||||
alert('El nombre de la receta es obligatorio');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.category.trim()) {
|
||||
alert('Debe seleccionar una categoría');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.finished_product_id.trim()) {
|
||||
alert('Debe seleccionar un producto terminado');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate seasonal dates if seasonal is enabled
|
||||
if (formData.is_seasonal) {
|
||||
if (!formData.season_start_month || !formData.season_end_month) {
|
||||
alert('Para recetas estacionales, debe especificar los meses de inicio y fin');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate batch sizes
|
||||
if (formData.minimum_batch_size && formData.maximum_batch_size) {
|
||||
if (formData.minimum_batch_size > formData.maximum_batch_size) {
|
||||
alert('El tamaño mínimo de lote no puede ser mayor que el máximo');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Generate recipe code if not provided
|
||||
const recipeCode = formData.recipe_code ||
|
||||
formData.name.substring(0, 3).toUpperCase() +
|
||||
String(Date.now()).slice(-3);
|
||||
|
||||
// Calculate total time including rest time
|
||||
const totalTime = (formData.prep_time_minutes || 0) +
|
||||
(formData.cook_time_minutes || 0) +
|
||||
(formData.rest_time_minutes || 0);
|
||||
|
||||
const recipeData: RecipeCreate = {
|
||||
...formData,
|
||||
recipe_code: recipeCode,
|
||||
total_time_minutes: totalTime,
|
||||
// Clean up undefined values for optional fields
|
||||
season_start_month: formData.is_seasonal ? formData.season_start_month : undefined,
|
||||
season_end_month: formData.is_seasonal ? formData.season_end_month : undefined,
|
||||
minimum_batch_size: formData.minimum_batch_size || undefined,
|
||||
maximum_batch_size: formData.maximum_batch_size || undefined,
|
||||
optimal_production_temperature: formData.optimal_production_temperature || undefined,
|
||||
optimal_humidity: formData.optimal_humidity || undefined
|
||||
};
|
||||
|
||||
if (onCreateRecipe) {
|
||||
await onCreateRecipe(recipeData);
|
||||
}
|
||||
|
||||
onClose();
|
||||
|
||||
// Reset form
|
||||
setFormData({
|
||||
name: '',
|
||||
recipe_code: '',
|
||||
finished_product_id: '',
|
||||
description: '',
|
||||
category: '',
|
||||
cuisine_type: '',
|
||||
difficulty_level: 1,
|
||||
yield_quantity: 1,
|
||||
yield_unit: MeasurementUnit.UNITS,
|
||||
prep_time_minutes: 0,
|
||||
cook_time_minutes: 0,
|
||||
total_time_minutes: 0,
|
||||
rest_time_minutes: 0,
|
||||
estimated_cost_per_unit: 0,
|
||||
target_margin_percentage: 30,
|
||||
suggested_selling_price: 0,
|
||||
preparation_notes: '',
|
||||
storage_instructions: '',
|
||||
quality_standards: '',
|
||||
serves_count: 1,
|
||||
is_seasonal: false,
|
||||
season_start_month: undefined,
|
||||
season_end_month: undefined,
|
||||
is_signature_item: false,
|
||||
batch_size_multiplier: 1.0,
|
||||
minimum_batch_size: undefined,
|
||||
maximum_batch_size: undefined,
|
||||
optimal_production_temperature: undefined,
|
||||
optimal_humidity: undefined,
|
||||
allergen_info: '',
|
||||
dietary_tags: '',
|
||||
nutritional_info: '',
|
||||
ingredients: []
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating recipe:', error);
|
||||
alert('Error al crear la receta. Por favor, inténtelo de nuevo.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getModalSections = () => [
|
||||
{
|
||||
title: 'Información Básica',
|
||||
icon: ChefHat,
|
||||
fields: [
|
||||
{
|
||||
key: 'name',
|
||||
label: 'Nombre de la receta',
|
||||
value: formData.name,
|
||||
type: 'text',
|
||||
required: true,
|
||||
placeholder: 'Ej: Pan de molde integral'
|
||||
},
|
||||
{
|
||||
key: 'recipe_code',
|
||||
label: 'Código de receta',
|
||||
value: formData.recipe_code,
|
||||
type: 'text',
|
||||
placeholder: 'Ej: PAN001 (opcional, se genera automáticamente)'
|
||||
},
|
||||
{
|
||||
key: 'description',
|
||||
label: 'Descripción',
|
||||
value: formData.description,
|
||||
type: 'textarea',
|
||||
placeholder: 'Descripción de la receta...'
|
||||
},
|
||||
{
|
||||
key: 'category',
|
||||
label: 'Categoría',
|
||||
value: formData.category,
|
||||
type: 'select',
|
||||
options: categoryOptions,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'cuisine_type',
|
||||
label: 'Tipo de cocina',
|
||||
value: formData.cuisine_type,
|
||||
type: 'select',
|
||||
options: cuisineTypeOptions,
|
||||
placeholder: 'Selecciona el tipo de cocina'
|
||||
},
|
||||
{
|
||||
key: 'difficulty_level',
|
||||
label: 'Nivel de dificultad',
|
||||
value: formData.difficulty_level,
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: 1, label: '1 - Fácil' },
|
||||
{ value: 2, label: '2 - Medio' },
|
||||
{ value: 3, label: '3 - Difícil' },
|
||||
{ value: 4, label: '4 - Muy Difícil' },
|
||||
{ value: 5, label: '5 - Extremo' }
|
||||
],
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'serves_count',
|
||||
label: 'Número de porciones',
|
||||
value: formData.serves_count,
|
||||
type: 'number',
|
||||
min: 1,
|
||||
placeholder: 'Cuántas personas sirve'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Rendimiento y Tiempos',
|
||||
icon: Clock,
|
||||
fields: [
|
||||
{
|
||||
key: 'yield_quantity',
|
||||
label: 'Cantidad que produce',
|
||||
value: formData.yield_quantity,
|
||||
type: 'number',
|
||||
min: 1,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'yield_unit',
|
||||
label: 'Unidad de medida',
|
||||
value: formData.yield_unit,
|
||||
type: 'select',
|
||||
options: unitOptions,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'prep_time_minutes',
|
||||
label: 'Tiempo de preparación (minutos)',
|
||||
value: formData.prep_time_minutes,
|
||||
type: 'number',
|
||||
min: 0
|
||||
},
|
||||
{
|
||||
key: 'cook_time_minutes',
|
||||
label: 'Tiempo de cocción (minutos)',
|
||||
value: formData.cook_time_minutes,
|
||||
type: 'number',
|
||||
min: 0
|
||||
},
|
||||
{
|
||||
key: 'rest_time_minutes',
|
||||
label: 'Tiempo de reposo (minutos)',
|
||||
value: formData.rest_time_minutes,
|
||||
type: 'number',
|
||||
min: 0,
|
||||
placeholder: 'Tiempo de fermentación o reposo'
|
||||
},
|
||||
{
|
||||
key: 'total_time_minutes',
|
||||
label: 'Tiempo total (calculado automáticamente)',
|
||||
value: formData.total_time_minutes,
|
||||
type: 'number',
|
||||
disabled: true,
|
||||
readonly: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Configuración Financiera',
|
||||
icon: DollarSign,
|
||||
fields: [
|
||||
{
|
||||
key: 'estimated_cost_per_unit',
|
||||
label: 'Costo estimado por unidad (€)',
|
||||
value: formData.estimated_cost_per_unit,
|
||||
type: 'number',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
placeholder: '0.00'
|
||||
},
|
||||
{
|
||||
key: 'suggested_selling_price',
|
||||
label: 'Precio de venta sugerido (€)',
|
||||
value: formData.suggested_selling_price,
|
||||
type: 'number',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
placeholder: '0.00'
|
||||
},
|
||||
{
|
||||
key: 'target_margin_percentage',
|
||||
label: 'Margen objetivo (%)',
|
||||
value: formData.target_margin_percentage,
|
||||
type: 'number',
|
||||
min: 0,
|
||||
max: 100,
|
||||
placeholder: '30'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Configuración de Producción',
|
||||
icon: Package,
|
||||
fields: [
|
||||
{
|
||||
key: 'batch_size_multiplier',
|
||||
label: 'Multiplicador de lote',
|
||||
value: formData.batch_size_multiplier,
|
||||
type: 'number',
|
||||
min: 0.1,
|
||||
step: 0.1,
|
||||
placeholder: '1.0'
|
||||
},
|
||||
{
|
||||
key: 'minimum_batch_size',
|
||||
label: 'Tamaño mínimo de lote',
|
||||
value: formData.minimum_batch_size,
|
||||
type: 'number',
|
||||
min: 1,
|
||||
placeholder: 'Cantidad mínima a producir'
|
||||
},
|
||||
{
|
||||
key: 'maximum_batch_size',
|
||||
label: 'Tamaño máximo de lote',
|
||||
value: formData.maximum_batch_size,
|
||||
type: 'number',
|
||||
min: 1,
|
||||
placeholder: 'Cantidad máxima a producir'
|
||||
},
|
||||
{
|
||||
key: 'optimal_production_temperature',
|
||||
label: 'Temperatura óptima (°C)',
|
||||
value: formData.optimal_production_temperature,
|
||||
type: 'number',
|
||||
placeholder: 'Temperatura ideal de producción'
|
||||
},
|
||||
{
|
||||
key: 'optimal_humidity',
|
||||
label: 'Humedad óptima (%)',
|
||||
value: formData.optimal_humidity,
|
||||
type: 'number',
|
||||
min: 0,
|
||||
max: 100,
|
||||
placeholder: 'Humedad ideal'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Temporalidad y Especiales',
|
||||
icon: Star,
|
||||
fields: [
|
||||
{
|
||||
key: 'is_signature_item',
|
||||
label: 'Receta especial/estrella',
|
||||
value: formData.is_signature_item,
|
||||
type: 'checkbox'
|
||||
},
|
||||
{
|
||||
key: 'is_seasonal',
|
||||
label: 'Receta estacional',
|
||||
value: formData.is_seasonal,
|
||||
type: 'checkbox'
|
||||
},
|
||||
{
|
||||
key: 'season_start_month',
|
||||
label: 'Mes de inicio de temporada',
|
||||
value: formData.season_start_month,
|
||||
type: 'select',
|
||||
options: monthOptions,
|
||||
placeholder: 'Selecciona mes de inicio',
|
||||
disabled: !formData.is_seasonal
|
||||
},
|
||||
{
|
||||
key: 'season_end_month',
|
||||
label: 'Mes de fin de temporada',
|
||||
value: formData.season_end_month,
|
||||
type: 'select',
|
||||
options: monthOptions,
|
||||
placeholder: 'Selecciona mes de fin',
|
||||
disabled: !formData.is_seasonal
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Información Nutricional y Alérgenos',
|
||||
icon: Package,
|
||||
fields: [
|
||||
{
|
||||
key: 'allergen_info',
|
||||
label: 'Información de alérgenos',
|
||||
value: formData.allergen_info,
|
||||
type: 'text',
|
||||
placeholder: 'Ej: Gluten, Lácteos, Huevos'
|
||||
},
|
||||
{
|
||||
key: 'dietary_tags',
|
||||
label: 'Etiquetas dietéticas',
|
||||
value: formData.dietary_tags,
|
||||
type: 'text',
|
||||
placeholder: 'Ej: Vegano, Sin gluten, Orgánico'
|
||||
},
|
||||
{
|
||||
key: 'nutritional_info',
|
||||
label: 'Información nutricional',
|
||||
value: formData.nutritional_info,
|
||||
type: 'textarea',
|
||||
placeholder: 'Calorías, proteínas, carbohidratos, etc.'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Notas de Preparación',
|
||||
icon: ChefHat,
|
||||
fields: [
|
||||
{
|
||||
key: 'preparation_notes',
|
||||
label: 'Notas de preparación',
|
||||
value: formData.preparation_notes,
|
||||
type: 'textarea',
|
||||
placeholder: 'Instrucciones especiales, consejos, técnicas...'
|
||||
},
|
||||
{
|
||||
key: 'storage_instructions',
|
||||
label: 'Instrucciones de almacenamiento',
|
||||
value: formData.storage_instructions,
|
||||
type: 'textarea',
|
||||
placeholder: 'Cómo almacenar el producto terminado...'
|
||||
},
|
||||
{
|
||||
key: 'quality_standards',
|
||||
label: 'Estándares de calidad',
|
||||
value: formData.quality_standards,
|
||||
type: 'textarea',
|
||||
placeholder: 'Criterios de calidad, características del producto...'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Producto Terminado',
|
||||
icon: Package,
|
||||
fields: [
|
||||
{
|
||||
key: 'finished_product_id',
|
||||
label: 'Producto terminado',
|
||||
value: formData.finished_product_id,
|
||||
type: 'select',
|
||||
options: finishedProducts,
|
||||
required: true,
|
||||
placeholder: inventoryLoading ? 'Cargando productos...' : 'Selecciona un producto terminado',
|
||||
help: 'Selecciona el producto del inventario que produce esta receta',
|
||||
disabled: inventoryLoading
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<StatusModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
mode={mode}
|
||||
onModeChange={setMode}
|
||||
title="Nueva Receta"
|
||||
subtitle="Crear una nueva receta para la panadería"
|
||||
statusIndicator={{
|
||||
color: '#3b82f6',
|
||||
text: 'Nueva',
|
||||
icon: ChefHat,
|
||||
isCritical: false,
|
||||
isHighlight: true
|
||||
}}
|
||||
size="xl"
|
||||
sections={getModalSections()}
|
||||
onFieldChange={handleFieldChange}
|
||||
actions={[
|
||||
{
|
||||
label: loading ? 'Creando...' : 'Crear Receta',
|
||||
icon: ChefHat,
|
||||
variant: 'primary',
|
||||
onClick: handleSubmit,
|
||||
disabled: loading || !formData.name.trim() || !formData.finished_product_id.trim()
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateRecipeModal;
|
||||
1
frontend/src/components/domain/recipes/index.ts
Normal file
1
frontend/src/components/domain/recipes/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { CreateRecipeModal } from './CreateRecipeModal';
|
||||
Reference in New Issue
Block a user