Add new page designs
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Plus, Search, Filter, Star, Clock, Users, DollarSign } from 'lucide-react';
|
||||
import { Button, Input, Card, Badge } from '../../../../components/ui';
|
||||
import { Plus, Download, Star, Clock, Users, DollarSign, Package, Eye, Edit, ChefHat, Timer } from 'lucide-react';
|
||||
import { Button, Input, Card, Badge, StatsGrid } from '../../../../components/ui';
|
||||
import { formatters } from '../../../../components/ui/Stats/StatsPresets';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
|
||||
const RecipesPage: React.FC = () => {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [selectedCategory, setSelectedCategory] = useState('all');
|
||||
const [selectedDifficulty, setSelectedDifficulty] = useState('all');
|
||||
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [selectedRecipe, setSelectedRecipe] = useState<typeof mockRecipes[0] | null>(null);
|
||||
|
||||
const mockRecipes = [
|
||||
{
|
||||
@@ -76,46 +76,62 @@ const RecipesPage: React.FC = () => {
|
||||
{ name: 'Azúcar', quantity: 100, unit: 'g' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const categories = [
|
||||
{ value: 'all', label: 'Todas las categorías' },
|
||||
{ value: 'bread', label: 'Panes' },
|
||||
{ value: 'pastry', label: 'Bollería' },
|
||||
{ value: 'cake', label: 'Tartas' },
|
||||
{ value: 'cookie', label: 'Galletas' },
|
||||
{ value: 'other', label: 'Otros' },
|
||||
];
|
||||
|
||||
const difficulties = [
|
||||
{ value: 'all', label: 'Todas las dificultades' },
|
||||
{ value: 'easy', label: 'Fácil' },
|
||||
{ value: 'medium', label: 'Medio' },
|
||||
{ value: 'hard', label: 'Difícil' },
|
||||
{
|
||||
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 getCategoryBadge = (category: string) => {
|
||||
const categoryConfig = {
|
||||
bread: { color: 'brown', text: 'Pan' },
|
||||
pastry: { color: 'yellow', text: 'Bollería' },
|
||||
cake: { color: 'pink', text: 'Tarta' },
|
||||
cookie: { color: 'orange', text: 'Galleta' },
|
||||
other: { color: 'gray', text: 'Otro' },
|
||||
bread: { color: 'default', text: 'Pan' },
|
||||
pastry: { color: 'warning', text: 'Bollería' },
|
||||
cake: { color: 'secondary', text: 'Tarta' },
|
||||
cookie: { color: 'info', text: 'Galleta' },
|
||||
other: { color: 'outline', text: 'Otro' },
|
||||
};
|
||||
|
||||
const config = categoryConfig[category as keyof typeof categoryConfig] || categoryConfig.other;
|
||||
return <Badge variant={config.color as any}>{config.text}</Badge>;
|
||||
return (
|
||||
<Badge
|
||||
variant={config.color as any}
|
||||
text={config.text}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getDifficultyBadge = (difficulty: string) => {
|
||||
const difficultyConfig = {
|
||||
easy: { color: 'green', text: 'Fácil' },
|
||||
medium: { color: 'yellow', text: 'Medio' },
|
||||
hard: { color: 'red', text: 'Difícil' },
|
||||
easy: { color: 'success', text: 'Fácil' },
|
||||
medium: { color: 'warning', text: 'Medio' },
|
||||
hard: { color: 'error', text: 'Difícil' },
|
||||
};
|
||||
|
||||
const config = difficultyConfig[difficulty as keyof typeof difficultyConfig];
|
||||
return <Badge variant={config.color as any}>{config.text}</Badge>;
|
||||
return (
|
||||
<Badge
|
||||
variant={config?.color as any}
|
||||
text={config?.text || difficulty}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const formatTime = (minutes: number) => {
|
||||
@@ -129,281 +145,300 @@ const RecipesPage: React.FC = () => {
|
||||
recipe.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
recipe.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()));
|
||||
|
||||
const matchesCategory = selectedCategory === 'all' || recipe.category === selectedCategory;
|
||||
const matchesDifficulty = selectedDifficulty === 'all' || recipe.difficulty === selectedDifficulty;
|
||||
|
||||
return matchesSearch && matchesCategory && matchesDifficulty;
|
||||
return matchesSearch;
|
||||
});
|
||||
|
||||
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 recipeStats = [
|
||||
{
|
||||
title: 'Total Recetas',
|
||||
value: mockRecipeStats.totalRecipes,
|
||||
variant: 'default' as const,
|
||||
icon: ChefHat,
|
||||
},
|
||||
{
|
||||
title: 'Populares',
|
||||
value: mockRecipeStats.popularRecipes,
|
||||
variant: 'warning' as const,
|
||||
icon: Star,
|
||||
},
|
||||
{
|
||||
title: 'Fáciles',
|
||||
value: mockRecipeStats.easyRecipes,
|
||||
variant: 'success' as const,
|
||||
icon: Timer,
|
||||
},
|
||||
{
|
||||
title: 'Costo Promedio',
|
||||
value: formatters.currency(mockRecipeStats.averageCost),
|
||||
variant: 'info' as const,
|
||||
icon: DollarSign,
|
||||
},
|
||||
{
|
||||
title: 'Margen Promedio',
|
||||
value: formatters.currency(mockRecipeStats.averageProfit),
|
||||
variant: 'success' as const,
|
||||
icon: DollarSign,
|
||||
},
|
||||
{
|
||||
title: 'Categorías',
|
||||
value: mockRecipeStats.categories,
|
||||
variant: 'info' as const,
|
||||
icon: Package,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="Gestión de Recetas"
|
||||
description="Administra y organiza todas las recetas de tu panadería"
|
||||
action={
|
||||
<Button>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Nueva Receta
|
||||
</Button>
|
||||
}
|
||||
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)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-[var(--text-secondary)]">Total Recetas</p>
|
||||
<p className="text-3xl font-bold text-[var(--text-primary)]">{mockRecipes.length}</p>
|
||||
</div>
|
||||
<div className="h-12 w-12 bg-[var(--color-info)]/10 rounded-full flex items-center justify-center">
|
||||
<svg className="h-6 w-6 text-[var(--color-info)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
{/* Stats Grid */}
|
||||
<StatsGrid
|
||||
stats={recipeStats}
|
||||
columns={6}
|
||||
/>
|
||||
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-[var(--text-secondary)]">Más Populares</p>
|
||||
<p className="text-3xl font-bold text-yellow-600">
|
||||
{mockRecipes.filter(r => r.rating > 4.7).length}
|
||||
</p>
|
||||
</div>
|
||||
<Star className="h-12 w-12 text-yellow-600" />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-[var(--text-secondary)]">Costo Promedio</p>
|
||||
<p className="text-3xl font-bold text-[var(--color-success)]">
|
||||
€{(mockRecipes.reduce((sum, r) => sum + r.cost, 0) / mockRecipes.length).toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
<DollarSign className="h-12 w-12 text-[var(--color-success)]" />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-[var(--text-secondary)]">Margen Promedio</p>
|
||||
<p className="text-3xl font-bold text-purple-600">
|
||||
€{(mockRecipes.reduce((sum, r) => sum + r.profit, 0) / mockRecipes.length).toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="h-12 w-12 bg-purple-100 rounded-full flex items-center justify-center">
|
||||
<svg className="h-6 w-6 text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Filters and Search */}
|
||||
<Card className="p-6">
|
||||
<div className="flex flex-col lg:flex-row gap-4">
|
||||
{/* Simplified Controls */}
|
||||
<Card className="p-4">
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[var(--text-tertiary)] h-4 w-4" />
|
||||
<Input
|
||||
placeholder="Buscar recetas por nombre, ingredientes o etiquetas..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<select
|
||||
value={selectedCategory}
|
||||
onChange={(e) => setSelectedCategory(e.target.value)}
|
||||
className="px-3 py-2 border border-[var(--border-secondary)] rounded-md"
|
||||
>
|
||||
{categories.map(cat => (
|
||||
<option key={cat.value} value={cat.value}>{cat.label}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<select
|
||||
value={selectedDifficulty}
|
||||
onChange={(e) => setSelectedDifficulty(e.target.value)}
|
||||
className="px-3 py-2 border border-[var(--border-secondary)] rounded-md"
|
||||
>
|
||||
{difficulties.map(diff => (
|
||||
<option key={diff.value} value={diff.value}>{diff.label}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setViewMode(viewMode === 'grid' ? 'list' : 'grid')}
|
||||
>
|
||||
{viewMode === 'grid' ? 'Vista Lista' : 'Vista Cuadrícula'}
|
||||
</Button>
|
||||
<Input
|
||||
placeholder="Buscar recetas por nombre, ingredientes o etiquetas..."
|
||||
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/List */}
|
||||
{viewMode === 'grid' ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filteredRecipes.map((recipe) => (
|
||||
<Card key={recipe.id} className="overflow-hidden hover:shadow-lg transition-shadow">
|
||||
<div className="aspect-w-16 aspect-h-9">
|
||||
<img
|
||||
src={recipe.image}
|
||||
alt={recipe.name}
|
||||
className="w-full h-48 object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] line-clamp-1">
|
||||
{/* Recipes Grid */}
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{filteredRecipes.map((recipe) => (
|
||||
<Card key={recipe.id} className="p-4">
|
||||
<div className="space-y-4">
|
||||
{/* Header with Image */}
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-shrink-0">
|
||||
<img
|
||||
src={recipe.image}
|
||||
alt={recipe.name}
|
||||
className="w-16 h-16 rounded-lg object-cover bg-[var(--bg-secondary)]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-[var(--text-primary)] truncate">
|
||||
{recipe.name}
|
||||
</h3>
|
||||
<div className="flex items-center ml-2">
|
||||
<Star className="h-4 w-4 text-yellow-400 fill-current" />
|
||||
<span className="text-sm text-[var(--text-secondary)] ml-1">{recipe.rating}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-[var(--text-secondary)] text-sm mb-3 line-clamp-2">
|
||||
{recipe.description}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
{getCategoryBadge(recipe.category)}
|
||||
{getDifficultyBadge(recipe.difficulty)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 mb-4 text-sm text-[var(--text-secondary)]">
|
||||
<div className="flex items-center">
|
||||
<Clock className="h-4 w-4 mr-1" />
|
||||
<span>{formatTime(recipe.prepTime + recipe.bakingTime)}</span>
|
||||
<div className="flex items-center gap-1 mt-1">
|
||||
<Star className="w-3 h-3 text-yellow-400 fill-current" />
|
||||
<span className="text-xs text-[var(--text-secondary)]">{recipe.rating}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Users className="h-4 w-4 mr-1" />
|
||||
<span>{recipe.yield} porciones</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="text-sm">
|
||||
<span className="text-[var(--text-secondary)]">Costo: </span>
|
||||
<span className="font-medium">€{recipe.cost.toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span className="text-[var(--text-secondary)]">Precio: </span>
|
||||
<span className="font-medium text-[var(--color-success)]">€{recipe.price.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" className="flex-1">
|
||||
Ver Receta
|
||||
</Button>
|
||||
<Button size="sm" className="flex-1">
|
||||
Producir
|
||||
</Button>
|
||||
<p className="text-xs text-[var(--text-secondary)] mt-1 line-clamp-2">
|
||||
{recipe.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
|
||||
{/* Badges */}
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{getCategoryBadge(recipe.category)}
|
||||
{getDifficultyBadge(recipe.difficulty)}
|
||||
</div>
|
||||
|
||||
{/* Time and Yield */}
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-1">
|
||||
<Clock className="w-4 h-4 text-[var(--text-tertiary)]" />
|
||||
<span className="text-[var(--text-primary)]">
|
||||
{formatTime(recipe.prepTime + recipe.bakingTime)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Users className="w-4 h-4 text-[var(--text-tertiary)]" />
|
||||
<span className="text-[var(--text-primary)]">
|
||||
{recipe.yield} porciones
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Financial Info */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-[var(--text-secondary)]">Costo:</span>
|
||||
<span className="font-medium text-[var(--text-primary)]">
|
||||
{formatters.currency(recipe.cost)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-[var(--text-secondary)]">Precio:</span>
|
||||
<span className="font-medium text-[var(--color-success)]">
|
||||
{formatters.currency(recipe.price)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-[var(--text-secondary)]">Margen:</span>
|
||||
<span className="font-bold text-[var(--color-success)]">
|
||||
{formatters.currency(recipe.profit)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Profit Margin Bar */}
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-xs">
|
||||
<span className="text-[var(--text-secondary)]">Margen de beneficio</span>
|
||||
<span className="text-[var(--text-primary)]">
|
||||
{Math.round((recipe.profit / recipe.price) * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-[var(--bg-tertiary)] rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full transition-all duration-300 ${
|
||||
(recipe.profit / recipe.price) > 0.5
|
||||
? 'bg-[var(--color-success)]'
|
||||
: (recipe.profit / recipe.price) > 0.3
|
||||
? 'bg-[var(--color-warning)]'
|
||||
: 'bg-[var(--color-error)]'
|
||||
}`}
|
||||
style={{ width: `${Math.min((recipe.profit / recipe.price) * 100, 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Ingredients Count */}
|
||||
<div className="text-xs text-[var(--text-secondary)]">
|
||||
{recipe.ingredients.length} ingredientes principales
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2 pt-2 border-t border-[var(--border-primary)]">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
onClick={() => {
|
||||
setSelectedRecipe(recipe);
|
||||
setShowForm(true);
|
||||
}}
|
||||
>
|
||||
<Eye className="w-4 h-4 mr-1" />
|
||||
Ver
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
onClick={() => console.log('Produce recipe', recipe.id)}
|
||||
>
|
||||
<ChefHat className="w-4 h-4 mr-1" />
|
||||
Producir
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Empty State */}
|
||||
{filteredRecipes.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<ChefHat className="mx-auto h-12 w-12 text-[var(--text-tertiary)] mb-4" />
|
||||
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-2">
|
||||
No se encontraron recetas
|
||||
</h3>
|
||||
<p className="text-[var(--text-secondary)] mb-4">
|
||||
Intenta ajustar la búsqueda o crear una nueva receta
|
||||
</p>
|
||||
<Button onClick={() => setShowForm(true)}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Nueva Receta
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Recipe Form Modal - Placeholder */}
|
||||
{showForm && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<Card className="w-full max-w-2xl max-h-[90vh] overflow-auto m-4 p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xl font-semibold text-[var(--text-primary)]">
|
||||
{selectedRecipe ? 'Ver Receta' : 'Nueva Receta'}
|
||||
</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setShowForm(false);
|
||||
setSelectedRecipe(null);
|
||||
}}
|
||||
>
|
||||
Cerrar
|
||||
</Button>
|
||||
</div>
|
||||
{selectedRecipe && (
|
||||
<div className="space-y-4">
|
||||
<img
|
||||
src={selectedRecipe.image}
|
||||
alt={selectedRecipe.name}
|
||||
className="w-full h-48 object-cover rounded-lg"
|
||||
/>
|
||||
<h3 className="text-lg font-medium">{selectedRecipe.name}</h3>
|
||||
<p className="text-[var(--text-secondary)]">{selectedRecipe.description}</p>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="font-medium">Tiempo total:</span> {formatTime(selectedRecipe.prepTime + selectedRecipe.bakingTime)}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Rendimiento:</span> {selectedRecipe.yield} porciones
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">Ingredientes:</h4>
|
||||
<ul className="space-y-1 text-sm">
|
||||
{selectedRecipe.ingredients.map((ing, i) => (
|
||||
<li key={i} className="flex justify-between">
|
||||
<span>{ing.name}</span>
|
||||
<span>{ing.quantity} {ing.unit}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
) : (
|
||||
<Card>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-[var(--bg-secondary)]">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
|
||||
Receta
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
|
||||
Categoría
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
|
||||
Dificultad
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
|
||||
Tiempo Total
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
|
||||
Rendimiento
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
|
||||
Costo
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
|
||||
Precio
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
|
||||
Margen
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
|
||||
Acciones
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{filteredRecipes.map((recipe) => (
|
||||
<tr key={recipe.id} className="hover:bg-[var(--bg-secondary)]">
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={recipe.image}
|
||||
alt={recipe.name}
|
||||
className="h-10 w-10 rounded-full mr-4"
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-[var(--text-primary)]">{recipe.name}</div>
|
||||
<div className="flex items-center">
|
||||
<Star className="h-3 w-3 text-yellow-400 fill-current" />
|
||||
<span className="text-xs text-[var(--text-tertiary)] ml-1">{recipe.rating}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
{getCategoryBadge(recipe.category)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
{getDifficultyBadge(recipe.difficulty)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-[var(--text-primary)]">
|
||||
{formatTime(recipe.prepTime + recipe.bakingTime)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-[var(--text-primary)]">
|
||||
{recipe.yield} porciones
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-[var(--text-primary)]">
|
||||
€{recipe.cost.toFixed(2)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-[var(--color-success)] font-medium">
|
||||
€{recipe.price.toFixed(2)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-purple-600 font-medium">
|
||||
€{recipe.profit.toFixed(2)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline" size="sm">Ver</Button>
|
||||
<Button size="sm">Producir</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user