Create the frontend receipes page to use real API

This commit is contained in:
Urtzi Alfaro
2025-09-19 21:39:04 +02:00
parent 8002d89d2b
commit d18c64ce6e
36 changed files with 3356 additions and 3171 deletions

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Plus, Clock, Package, Eye, Edit, CheckCircle, AlertCircle, Timer, Users, DollarSign, Loader } from 'lucide-react';
import { Plus, Clock, Package, Eye, Edit, CheckCircle, AlertCircle, Timer, Users, Loader, Euro } from 'lucide-react';
import { Button, Input, Card, Badge, StatsGrid, StatusCard, getStatusColor, StatusModal } from '../../../../components/ui';
import { formatters } from '../../../../components/ui/Stats/StatsPresets';
import { PageHeader } from '../../../../components/layout';
@@ -180,13 +180,13 @@ const OrdersPage: React.FC = () => {
title: 'Ingresos Hoy',
value: formatters.currency(orderStats.revenue_today),
variant: 'success' as const,
icon: DollarSign,
icon: Euro,
},
{
title: 'Valor Promedio',
value: formatters.currency(orderStats.average_order_value),
variant: 'info' as const,
icon: DollarSign,
icon: Euro,
},
];
} else {
@@ -219,13 +219,13 @@ const OrdersPage: React.FC = () => {
title: 'Valor Total',
value: formatters.currency(customers.reduce((sum, c) => sum + Number(c.total_spent || 0), 0)),
variant: 'success' as const,
icon: DollarSign,
icon: Euro,
},
{
title: 'Promedio por Cliente',
value: formatters.currency(customers.reduce((sum, c) => sum + Number(c.average_order_value || 0), 0) / Math.max(customers.length, 1)),
variant: 'info' as const,
icon: DollarSign,
icon: Euro,
},
];
}
@@ -565,7 +565,7 @@ const OrdersPage: React.FC = () => {
},
{
title: 'Información Financiera',
icon: DollarSign,
icon: Euro,
fields: [
{
label: 'Subtotal',
@@ -717,7 +717,7 @@ const OrdersPage: React.FC = () => {
},
{
title: 'Configuración Comercial',
icon: DollarSign,
icon: Euro,
fields: [
{
label: 'Código de Cliente',

View File

@@ -1,107 +1,47 @@
import React, { useState } from 'react';
import { Plus, Download, Star, Clock, Users, DollarSign, Package, Eye, Edit, ChefHat, Timer } from 'lucide-react';
import { Button, Input, Card, Badge, StatsGrid, StatusCard, getStatusColor, StatusModal } from '../../../../components/ui';
import React, { useState, useMemo } from 'react';
import { Plus, Star, Clock, DollarSign, Package, Eye, Edit, ChefHat, Timer, Euro } from 'lucide-react';
import { Button, Input, Card, StatsGrid, StatusCard, getStatusColor, StatusModal } from '../../../../components/ui';
import { LoadingSpinner } from '../../../../components/shared';
import { formatters } from '../../../../components/ui/Stats/StatsPresets';
import { PageHeader } from '../../../../components/layout';
import { useRecipes, useRecipeStatistics, useCreateRecipe, useUpdateRecipe, useDeleteRecipe } from '../../../../api/hooks/recipes';
import { useCurrentTenant } from '../../../../stores/tenant.store';
import type { RecipeResponse, RecipeCreate } from '../../../../api/types/recipes';
import { CreateRecipeModal } from '../../../../components/domain/recipes';
const RecipesPage: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
const [showForm, setShowForm] = useState(false);
const [modalMode, setModalMode] = useState<'view' | 'edit'>('view');
const [selectedRecipe, setSelectedRecipe] = useState<typeof mockRecipes[0] | null>(null);
const [selectedRecipe, setSelectedRecipe] = useState<RecipeResponse | null>(null);
const [showCreateModal, setShowCreateModal] = useState(false);
const [editedRecipe, setEditedRecipe] = useState<Partial<RecipeResponse>>({});
const mockRecipes = [
{
id: '1',
name: 'Pan de Molde Integral',
category: 'bread',
difficulty: 'medium',
prepTime: 120,
bakingTime: 35,
yield: 1,
rating: 4.8,
cost: 2.50,
price: 4.50,
profit: 2.00,
image: '/api/placeholder/300/200',
tags: ['integral', 'saludable', 'artesanal'],
description: 'Pan integral artesanal con semillas, perfecto para desayunos saludables.',
ingredients: [
{ name: 'Harina integral', quantity: 500, unit: 'g' },
{ name: 'Agua', quantity: 300, unit: 'ml' },
{ name: 'Levadura', quantity: 10, unit: 'g' },
{ name: 'Sal', quantity: 8, unit: 'g' },
],
},
{
id: '2',
name: 'Croissants de Mantequilla',
category: 'pastry',
difficulty: 'hard',
prepTime: 480,
bakingTime: 20,
yield: 12,
rating: 4.9,
cost: 8.50,
price: 18.00,
profit: 9.50,
image: '/api/placeholder/300/200',
tags: ['francés', 'mantequilla', 'hojaldrado'],
description: 'Croissants franceses tradicionales con laminado de mantequilla.',
ingredients: [
{ name: 'Harina de fuerza', quantity: 500, unit: 'g' },
{ name: 'Mantequilla', quantity: 250, unit: 'g' },
{ name: 'Leche', quantity: 150, unit: 'ml' },
{ name: 'Azúcar', quantity: 50, unit: 'g' },
],
},
{
id: '3',
name: 'Tarta de Manzana',
category: 'cake',
difficulty: 'easy',
prepTime: 45,
bakingTime: 40,
yield: 8,
rating: 4.6,
cost: 4.20,
price: 12.00,
profit: 7.80,
image: '/api/placeholder/300/200',
tags: ['frutal', 'casera', 'temporada'],
description: 'Tarta casera de manzana con canela y masa quebrada.',
ingredients: [
{ name: 'Manzanas', quantity: 1000, unit: 'g' },
{ name: 'Harina', quantity: 250, unit: 'g' },
{ name: 'Mantequilla', quantity: 125, unit: 'g' },
{ name: 'Azúcar', quantity: 100, unit: 'g' },
],
},
{
id: '4',
name: 'Magdalenas de Limón',
category: 'pastry',
difficulty: 'easy',
prepTime: 20,
bakingTime: 25,
yield: 12,
rating: 4.4,
cost: 3.80,
price: 9.00,
profit: 5.20,
image: '/api/placeholder/300/200',
tags: ['cítrico', 'esponjoso', 'individual'],
description: 'Magdalenas suaves y esponjosas con ralladura de limón.',
ingredients: [
{ name: 'Harina', quantity: 200, unit: 'g' },
{ name: 'Huevos', quantity: 3, unit: 'uds' },
{ name: 'Azúcar', quantity: 150, unit: 'g' },
{ name: 'Limón', quantity: 2, unit: 'uds' },
],
},
];
const currentTenant = useCurrentTenant();
const tenantId = currentTenant?.id || '';
const getRecipeStatusConfig = (category: string, difficulty: string, rating: number) => {
// Mutations
const createRecipeMutation = useCreateRecipe(tenantId);
const updateRecipeMutation = useUpdateRecipe(tenantId);
const deleteRecipeMutation = useDeleteRecipe(tenantId);
// API Data
const {
data: recipes = [],
isLoading: recipesLoading,
error: recipesError
} = useRecipes(tenantId, { search_term: searchTerm || undefined });
const {
data: statisticsData,
isLoading: statisticsLoading
} = useRecipeStatistics(tenantId);
const getRecipeStatusConfig = (recipe: RecipeResponse) => {
const category = recipe.category || 'other';
const difficulty = recipe.difficulty_level || 1;
const isSignature = recipe.is_signature_item;
const categoryConfig = {
bread: { text: 'Pan', icon: ChefHat },
pastry: { text: 'Bollería', icon: ChefHat },
@@ -109,25 +49,24 @@ const RecipesPage: React.FC = () => {
cookie: { text: 'Galleta', icon: ChefHat },
other: { text: 'Otro', icon: ChefHat },
};
const difficultyConfig = {
easy: { icon: '●', label: 'Fácil' },
medium: { icon: '●●', label: 'Medio' },
hard: { icon: '●●●', label: 'Difícil' },
1: { icon: '●', label: 'Fácil' },
2: { icon: '●●', label: 'Medio' },
3: { icon: '●●●', label: 'Difícil' },
};
const categoryInfo = categoryConfig[category as keyof typeof categoryConfig] || categoryConfig.other;
const difficultyInfo = difficultyConfig[difficulty as keyof typeof difficultyConfig];
const isPopular = rating >= 4.7;
const difficultyInfo = difficultyConfig[Math.min(difficulty, 3) as keyof typeof difficultyConfig] || difficultyConfig[1];
return {
color: getStatusColor(category),
text: categoryInfo.text,
icon: categoryInfo.icon,
difficultyIcon: difficultyInfo?.icon || '●',
difficultyLabel: difficultyInfo?.label || difficulty,
difficultyIcon: difficultyInfo.icon,
difficultyLabel: difficultyInfo.label,
isCritical: false,
isHighlight: isPopular
isHighlight: isSignature
};
};
@@ -137,88 +76,303 @@ const RecipesPage: React.FC = () => {
return hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
};
const filteredRecipes = mockRecipes.filter(recipe => {
const matchesSearch = recipe.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
recipe.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
recipe.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()));
return matchesSearch;
});
const filteredRecipes = useMemo(() => {
if (!searchTerm) return recipes;
const mockRecipeStats = {
totalRecipes: mockRecipes.length,
popularRecipes: mockRecipes.filter(r => r.rating > 4.7).length,
easyRecipes: mockRecipes.filter(r => r.difficulty === 'easy').length,
averageCost: mockRecipes.reduce((sum, r) => sum + r.cost, 0) / mockRecipes.length,
averageProfit: mockRecipes.reduce((sum, r) => sum + r.profit, 0) / mockRecipes.length,
categories: [...new Set(mockRecipes.map(r => r.category))].length,
};
const searchLower = searchTerm.toLowerCase();
return recipes.filter(recipe =>
recipe.name.toLowerCase().includes(searchLower) ||
(recipe.description && recipe.description.toLowerCase().includes(searchLower)) ||
(recipe.category && recipe.category.toLowerCase().includes(searchLower))
);
}, [recipes, searchTerm]);
const recipeStats = [
const recipeStats = useMemo(() => {
const stats = {
totalRecipes: recipes.length,
signatureRecipes: recipes.filter(r => r.is_signature_item).length,
easyRecipes: recipes.filter(r => r.difficulty_level === 1).length,
averageCost: recipes.length > 0 ? recipes.reduce((sum, r) => sum + (r.estimated_cost_per_unit || 0), 0) / recipes.length : 0,
averageProfit: 0, // Will be calculated based on suggested selling price - cost
categories: [...new Set(recipes.map(r => r.category).filter(Boolean))].length,
};
// Calculate average profit
stats.averageProfit = recipes.length > 0 ?
recipes.reduce((sum, r) => {
const cost = r.estimated_cost_per_unit || 0;
const price = r.suggested_selling_price || 0;
return sum + (price - cost);
}, 0) / recipes.length : 0;
return stats;
}, [recipes]);
const stats = [
{
title: 'Total Recetas',
value: mockRecipeStats.totalRecipes,
value: recipeStats.totalRecipes,
variant: 'default' as const,
icon: ChefHat,
},
{
title: 'Populares',
value: mockRecipeStats.popularRecipes,
title: 'Especiales',
value: recipeStats.signatureRecipes,
variant: 'warning' as const,
icon: Star,
},
{
title: 'Fáciles',
value: mockRecipeStats.easyRecipes,
value: recipeStats.easyRecipes,
variant: 'success' as const,
icon: Timer,
},
{
title: 'Costo Promedio',
value: formatters.currency(mockRecipeStats.averageCost),
value: formatters.currency(recipeStats.averageCost),
variant: 'info' as const,
icon: DollarSign,
icon: Euro,
},
{
title: 'Margen Promedio',
value: formatters.currency(mockRecipeStats.averageProfit),
value: formatters.currency(recipeStats.averageProfit),
variant: 'success' as const,
icon: DollarSign,
icon: Euro,
},
{
title: 'Categorías',
value: mockRecipeStats.categories,
value: recipeStats.categories,
variant: 'info' as const,
icon: Package,
},
];
// Handle creating a new recipe
const handleCreateRecipe = async (recipeData: RecipeCreate) => {
try {
await createRecipeMutation.mutateAsync(recipeData);
setShowCreateModal(false);
console.log('Recipe created successfully');
} catch (error) {
console.error('Error creating recipe:', error);
throw error;
}
};
// Handle field changes in edit mode
const handleFieldChange = (sectionIndex: number, fieldIndex: number, value: string | number | boolean) => {
if (!selectedRecipe) return;
const fieldMap: Record<string, string> = {
'Categoría': 'category',
'Dificultad': 'difficulty_level',
'Estado': 'status',
'Rendimiento': 'yield_quantity',
'Tiempo de preparación': 'prep_time_minutes',
'Tiempo de cocción': 'cook_time_minutes',
'Costo estimado por unidad': 'estimated_cost_per_unit',
'Precio de venta sugerido': 'suggested_selling_price',
'Margen objetivo': 'target_margin_percentage'
};
const sections = getModalSections();
const field = sections[sectionIndex]?.fields[fieldIndex];
const fieldKey = fieldMap[field?.label || ''];
if (fieldKey) {
setEditedRecipe(prev => ({
...prev,
[fieldKey]: value
}));
}
};
// Handle saving edited recipe
const handleSaveRecipe = async () => {
if (!selectedRecipe || !Object.keys(editedRecipe).length) return;
try {
const updateData = {
...editedRecipe,
// Convert time fields from formatted strings back to numbers if needed
prep_time_minutes: typeof editedRecipe.prep_time_minutes === 'string'
? parseInt(editedRecipe.prep_time_minutes.toString())
: editedRecipe.prep_time_minutes,
cook_time_minutes: typeof editedRecipe.cook_time_minutes === 'string'
? parseInt(editedRecipe.cook_time_minutes.toString())
: editedRecipe.cook_time_minutes,
};
await updateRecipeMutation.mutateAsync({
id: selectedRecipe.id,
data: updateData
});
setModalMode('view');
setEditedRecipe({});
console.log('Recipe updated successfully');
} catch (error) {
console.error('Error updating recipe:', error);
}
};
// Get current value for field (edited value or original)
const getFieldValue = (originalValue: any, fieldKey: string) => {
return editedRecipe[fieldKey as keyof RecipeResponse] !== undefined
? editedRecipe[fieldKey as keyof RecipeResponse]
: originalValue;
};
// Get modal sections with editable fields
const getModalSections = () => {
if (!selectedRecipe) return [];
return [
{
title: 'Información Básica',
icon: ChefHat,
fields: [
{
label: 'Categoría',
value: getFieldValue(selectedRecipe.category || 'Sin categoría', 'category'),
type: modalMode === 'edit' ? 'select' : 'status',
options: modalMode === 'edit' ? [
{ value: 'bread', label: 'Pan' },
{ value: 'pastry', label: 'Bollería' },
{ value: 'cake', label: 'Tarta' },
{ value: 'cookie', label: 'Galleta' },
{ value: 'other', label: 'Otro' }
] : undefined,
editable: true
},
{
label: 'Dificultad',
value: modalMode === 'edit'
? getFieldValue(selectedRecipe.difficulty_level, 'difficulty_level')
: `Nivel ${getFieldValue(selectedRecipe.difficulty_level, 'difficulty_level')}`,
type: modalMode === 'edit' ? 'select' : 'text',
options: modalMode === 'edit' ? [
{ 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' }
] : undefined,
editable: true
},
{
label: 'Estado',
value: getFieldValue(selectedRecipe.status, 'status'),
type: modalMode === 'edit' ? 'select' : 'text',
options: modalMode === 'edit' ? [
{ value: 'draft', label: 'Borrador' },
{ value: 'active', label: 'Activo' },
{ value: 'archived', label: 'Archivado' }
] : undefined,
highlight: selectedRecipe.status === 'active',
editable: true
},
{
label: 'Rendimiento',
value: modalMode === 'edit'
? getFieldValue(selectedRecipe.yield_quantity, 'yield_quantity')
: `${getFieldValue(selectedRecipe.yield_quantity, 'yield_quantity')} ${selectedRecipe.yield_unit}`,
type: modalMode === 'edit' ? 'number' : 'text',
editable: modalMode === 'edit'
}
]
},
{
title: 'Tiempos',
icon: Clock,
fields: [
{
label: 'Tiempo de preparación',
value: modalMode === 'edit'
? getFieldValue(selectedRecipe.prep_time_minutes || 0, 'prep_time_minutes')
: formatTime(getFieldValue(selectedRecipe.prep_time_minutes || 0, 'prep_time_minutes')),
type: modalMode === 'edit' ? 'number' : 'text',
placeholder: modalMode === 'edit' ? 'minutos' : undefined,
editable: modalMode === 'edit'
},
{
label: 'Tiempo de cocción',
value: modalMode === 'edit'
? getFieldValue(selectedRecipe.cook_time_minutes || 0, 'cook_time_minutes')
: formatTime(getFieldValue(selectedRecipe.cook_time_minutes || 0, 'cook_time_minutes')),
type: modalMode === 'edit' ? 'number' : 'text',
placeholder: modalMode === 'edit' ? 'minutos' : undefined,
editable: modalMode === 'edit'
},
{
label: 'Tiempo total',
value: selectedRecipe.total_time_minutes ? formatTime(selectedRecipe.total_time_minutes) : 'No especificado',
type: 'text',
highlight: true,
readonly: true
}
]
},
{
title: 'Análisis Financiero',
icon: DollarSign,
fields: [
{
label: 'Costo estimado por unidad',
value: getFieldValue(selectedRecipe.estimated_cost_per_unit || 0, 'estimated_cost_per_unit'),
type: modalMode === 'edit' ? 'number' : 'currency',
editable: modalMode === 'edit'
},
{
label: 'Precio de venta sugerido',
value: getFieldValue(selectedRecipe.suggested_selling_price || 0, 'suggested_selling_price'),
type: modalMode === 'edit' ? 'number' : 'currency',
editable: modalMode === 'edit'
},
{
label: 'Margen objetivo',
value: getFieldValue(selectedRecipe.target_margin_percentage || 0, 'target_margin_percentage'),
type: modalMode === 'edit' ? 'number' : 'percentage',
highlight: true,
editable: modalMode === 'edit'
}
]
},
{
title: 'Ingredientes',
icon: Package,
fields: [
{
label: 'Lista de ingredientes',
value: selectedRecipe.ingredients?.map(ing => `${ing.quantity} ${ing.unit} - ${ing.ingredient_id}`) || ['No especificados'],
type: 'list',
span: 2,
readonly: true // For now, ingredients editing can be complex, so we'll keep it read-only
}
]
}
];
};
return (
<div className="space-y-6">
<PageHeader
title="Gestión de Recetas"
description="Administra y organiza todas las recetas de tu panadería"
actions={[
{
id: "export",
label: "Exportar",
variant: "outline" as const,
icon: Download,
onClick: () => console.log('Export recipes')
},
{
id: "new",
label: "Nueva Receta",
variant: "primary" as const,
icon: Plus,
onClick: () => setShowForm(true)
onClick: () => setShowCreateModal(true)
}
]}
/>
{/* Stats Grid */}
<StatsGrid
stats={recipeStats}
<StatsGrid
stats={stats}
columns={3}
/>
@@ -227,25 +381,23 @@ const RecipesPage: React.FC = () => {
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<Input
placeholder="Buscar recetas por nombre, ingredientes o etiquetas..."
placeholder="Buscar recetas por nombre, descripción o categoría..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full"
/>
</div>
<Button variant="outline" onClick={() => console.log('Export filtered')}>
<Download className="w-4 h-4 mr-2" />
Exportar
</Button>
</div>
</Card>
{/* Recipes Grid */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{filteredRecipes.map((recipe) => {
const statusConfig = getRecipeStatusConfig(recipe.category, recipe.difficulty, recipe.rating);
const profitMargin = Math.round((recipe.profit / recipe.price) * 100);
const totalTime = formatTime(recipe.prepTime + recipe.bakingTime);
const statusConfig = getRecipeStatusConfig(recipe);
const cost = recipe.estimated_cost_per_unit || 0;
const price = recipe.suggested_selling_price || 0;
const profitMargin = price > 0 ? Math.round(((price - cost) / price) * 100) : 0;
const totalTime = formatTime((recipe.prep_time_minutes || 0) + (recipe.cook_time_minutes || 0));
return (
<StatusCard
@@ -253,12 +405,12 @@ const RecipesPage: React.FC = () => {
id={recipe.id}
statusIndicator={statusConfig}
title={recipe.name}
subtitle={`${statusConfig.text}${statusConfig.difficultyLabel}${statusConfig.isHighlight ? ' ★' + recipe.rating : ''}`}
primaryValue={recipe.ingredients.length}
subtitle={`${statusConfig.text}${statusConfig.difficultyLabel}${statusConfig.isHighlight ? ' ★' : ''}`}
primaryValue={recipe.ingredients?.length || 0}
primaryValueLabel="ingredientes"
secondaryInfo={{
label: 'Margen',
value: `${formatters.compact(recipe.profit)}`
value: `${formatters.compact(price - cost)}`
}}
progress={{
label: 'Margen de beneficio',
@@ -267,8 +419,8 @@ const RecipesPage: React.FC = () => {
}}
metadata={[
`Tiempo: ${totalTime}`,
`Porciones: ${recipe.yield}`,
`${recipe.ingredients.length} ingredientes principales`
`Rendimiento: ${recipe.yield_quantity} ${recipe.yield_unit}`,
`${recipe.ingredients?.length || 0} ingredientes principales`
]}
actions={[
{
@@ -328,110 +480,33 @@ const RecipesPage: React.FC = () => {
setShowForm(false);
setSelectedRecipe(null);
setModalMode('view');
setEditedRecipe({});
}}
mode={modalMode}
onModeChange={setModalMode}
title={selectedRecipe.name}
subtitle={selectedRecipe.description}
statusIndicator={getRecipeStatusConfig(selectedRecipe.category, selectedRecipe.difficulty, selectedRecipe.rating)}
image={selectedRecipe.image}
subtitle={selectedRecipe.description || ''}
statusIndicator={getRecipeStatusConfig(selectedRecipe)}
size="xl"
sections={[
sections={getModalSections()}
onFieldChange={handleFieldChange}
actions={modalMode === 'edit' ? [
{
title: 'Información Básica',
label: 'Guardar',
icon: ChefHat,
fields: [
{
label: 'Categoría',
value: selectedRecipe.category,
type: 'status'
},
{
label: 'Dificultad',
value: selectedRecipe.difficulty
},
{
label: 'Valoración',
value: `${selectedRecipe.rating}`,
highlight: selectedRecipe.rating >= 4.7
},
{
label: 'Rendimiento',
value: `${selectedRecipe.yield} porciones`
}
]
variant: 'primary',
onClick: handleSaveRecipe,
disabled: updateRecipeMutation.isPending
},
{
title: 'Tiempos',
icon: Clock,
fields: [
{
label: 'Tiempo de preparación',
value: formatTime(selectedRecipe.prepTime)
},
{
label: 'Tiempo de horneado',
value: formatTime(selectedRecipe.bakingTime)
},
{
label: 'Tiempo total',
value: formatTime(selectedRecipe.prepTime + selectedRecipe.bakingTime),
highlight: true
}
]
},
{
title: 'Análisis Financiero',
icon: DollarSign,
fields: [
{
label: 'Costo de producción',
value: selectedRecipe.cost,
type: 'currency'
},
{
label: 'Precio de venta',
value: selectedRecipe.price,
type: 'currency'
},
{
label: 'Margen de beneficio',
value: selectedRecipe.profit,
type: 'currency',
highlight: true
},
{
label: 'Porcentaje de margen',
value: Math.round((selectedRecipe.profit / selectedRecipe.price) * 100),
type: 'percentage',
highlight: true
}
]
},
{
title: 'Ingredientes',
icon: Package,
fields: [
{
label: 'Lista de ingredientes',
value: selectedRecipe.ingredients.map(ing => `${ing.name}: ${ing.quantity} ${ing.unit}`),
type: 'list',
span: 2
}
]
},
{
title: 'Etiquetas',
fields: [
{
label: 'Tags',
value: selectedRecipe.tags.join(', '),
span: 2
}
]
label: 'Cancelar',
variant: 'outline',
onClick: () => {
setModalMode('view');
setEditedRecipe({});
}
}
]}
actions={[
] : [
{
label: 'Producir',
icon: ChefHat,
@@ -444,10 +519,18 @@ const RecipesPage: React.FC = () => {
}
]}
onEdit={() => {
console.log('Editing recipe:', selectedRecipe.id);
setModalMode('edit');
setEditedRecipe({});
}}
/>
)}
{/* Create Recipe Modal */}
<CreateRecipeModal
isOpen={showCreateModal}
onClose={() => setShowCreateModal(false)}
onCreateRecipe={handleCreateRecipe}
/>
</div>
);
};

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Plus, Building2, Phone, Mail, Eye, Edit, CheckCircle, AlertCircle, Timer, Users, DollarSign, Loader } from 'lucide-react';
import { Plus, Building2, Phone, Mail, Eye, Edit, CheckCircle, AlertCircle, Timer, Users, Euro, Loader } from 'lucide-react';
import { Button, Input, Card, Badge, StatsGrid, StatusCard, getStatusColor, StatusModal } from '../../../../components/ui';
import { formatters } from '../../../../components/ui/Stats/StatsPresets';
import { PageHeader } from '../../../../components/layout';
@@ -107,7 +107,7 @@ const SuppliersPage: React.FC = () => {
title: 'Gasto Total',
value: formatters.currency(supplierStats.total_spend),
variant: 'info' as const,
icon: DollarSign,
icon: Euro,
},
{
title: 'Calidad Media',
@@ -382,7 +382,7 @@ const SuppliersPage: React.FC = () => {
},
{
title: 'Rendimiento y Estadísticas',
icon: DollarSign,
icon: Euro,
fields: [
{
label: 'Moneda',