IMPORVE ONBOARDING STEPS

This commit is contained in:
Urtzi Alfaro
2025-11-09 09:22:08 +01:00
parent 4678f96f8f
commit cbe19a3cd1
27 changed files with 2801 additions and 1149 deletions

View File

@@ -0,0 +1,860 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '../../../ui/Button';
import { useCurrentTenant } from '../../../../stores/tenant.store';
import { useCreateIngredient } from '../../../../api/hooks/inventory';
import { useImportSalesData } from '../../../../api/hooks/sales';
import type { ProductSuggestionResponse, IngredientCreate } from '../../../../api/types/inventory';
import { ProductType, UnitOfMeasure, IngredientCategory, ProductCategory } from '../../../../api/types/inventory';
import { Package, ShoppingBag, AlertCircle, CheckCircle2, Edit2, Trash2, Plus, Sparkles } from 'lucide-react';
interface InventoryReviewStepProps {
onNext: () => void;
onPrevious: () => void;
onComplete: (data: {
inventoryItemsCreated: number;
salesDataImported: boolean;
}) => void;
isFirstStep: boolean;
isLastStep: boolean;
initialData?: {
uploadedFile?: File; // NEW: File object for sales import
validationResult?: any; // NEW: Validation result
aiSuggestions: ProductSuggestionResponse[];
uploadedFileName: string;
uploadedFileSize: number;
};
}
interface InventoryItemForm {
id: string; // Unique ID for UI tracking
name: string;
product_type: ProductType;
category: string;
unit_of_measure: UnitOfMeasure | string;
// AI suggestion metadata (if from AI)
isSuggested: boolean;
confidence_score?: number;
sales_data?: {
total_quantity: number;
average_daily_sales: number;
};
}
type FilterType = 'all' | 'ingredients' | 'finished_products';
// Template Definitions - Common Bakery Ingredients
interface TemplateItem {
name: string;
product_type: ProductType;
category: string;
unit_of_measure: UnitOfMeasure;
}
interface IngredientTemplate {
id: string;
name: string;
description: string;
icon: string;
items: TemplateItem[];
}
const INGREDIENT_TEMPLATES: IngredientTemplate[] = [
{
id: 'basic-bakery',
name: 'Ingredientes Básicos de Panadería',
description: 'Esenciales para cualquier panadería',
icon: '🍞',
items: [
{ name: 'Harina de Trigo', product_type: ProductType.INGREDIENT, category: IngredientCategory.FLOUR, unit_of_measure: UnitOfMeasure.KILOGRAMS },
{ name: 'Azúcar', product_type: ProductType.INGREDIENT, category: IngredientCategory.SUGAR, unit_of_measure: UnitOfMeasure.KILOGRAMS },
{ name: 'Sal', product_type: ProductType.INGREDIENT, category: IngredientCategory.SALT, unit_of_measure: UnitOfMeasure.KILOGRAMS },
{ name: 'Levadura Fresca', product_type: ProductType.INGREDIENT, category: IngredientCategory.YEAST, unit_of_measure: UnitOfMeasure.KILOGRAMS },
{ name: 'Agua', product_type: ProductType.INGREDIENT, category: IngredientCategory.OTHER, unit_of_measure: UnitOfMeasure.LITERS },
],
},
{
id: 'pastry-essentials',
name: 'Esenciales para Pastelería',
description: 'Ingredientes para pasteles y postres',
icon: '🎂',
items: [
{ name: 'Huevos', product_type: ProductType.INGREDIENT, category: IngredientCategory.EGGS, unit_of_measure: UnitOfMeasure.UNITS },
{ name: 'Mantequilla', product_type: ProductType.INGREDIENT, category: IngredientCategory.FATS, unit_of_measure: UnitOfMeasure.KILOGRAMS },
{ name: 'Leche', product_type: ProductType.INGREDIENT, category: IngredientCategory.DAIRY, unit_of_measure: UnitOfMeasure.LITERS },
{ name: 'Vainilla', product_type: ProductType.INGREDIENT, category: IngredientCategory.SPICES, unit_of_measure: UnitOfMeasure.MILLILITERS },
{ name: 'Azúcar Glass', product_type: ProductType.INGREDIENT, category: IngredientCategory.SUGAR, unit_of_measure: UnitOfMeasure.KILOGRAMS },
],
},
{
id: 'bread-basics',
name: 'Básicos para Pan Artesanal',
description: 'Todo lo necesario para pan artesanal',
icon: '🥖',
items: [
{ name: 'Harina Integral', product_type: ProductType.INGREDIENT, category: IngredientCategory.FLOUR, unit_of_measure: UnitOfMeasure.KILOGRAMS },
{ name: 'Masa Madre', product_type: ProductType.INGREDIENT, category: IngredientCategory.YEAST, unit_of_measure: UnitOfMeasure.KILOGRAMS },
{ name: 'Aceite de Oliva', product_type: ProductType.INGREDIENT, category: IngredientCategory.FATS, unit_of_measure: UnitOfMeasure.LITERS },
{ name: 'Semillas de Sésamo', product_type: ProductType.INGREDIENT, category: IngredientCategory.OTHER, unit_of_measure: UnitOfMeasure.KILOGRAMS },
],
},
{
id: 'chocolate-specialties',
name: 'Especialidades de Chocolate',
description: 'Para productos con chocolate',
icon: '🍫',
items: [
{ name: 'Chocolate Negro', product_type: ProductType.INGREDIENT, category: IngredientCategory.OTHER, unit_of_measure: UnitOfMeasure.KILOGRAMS },
{ name: 'Cacao en Polvo', product_type: ProductType.INGREDIENT, category: IngredientCategory.OTHER, unit_of_measure: UnitOfMeasure.KILOGRAMS },
{ name: 'Chocolate con Leche', product_type: ProductType.INGREDIENT, category: IngredientCategory.OTHER, unit_of_measure: UnitOfMeasure.KILOGRAMS },
{ name: 'Crema de Avellanas', product_type: ProductType.INGREDIENT, category: IngredientCategory.OTHER, unit_of_measure: UnitOfMeasure.KILOGRAMS },
],
},
];
export const InventoryReviewStep: React.FC<InventoryReviewStepProps> = ({
onComplete,
onPrevious,
isFirstStep,
initialData
}) => {
const { t } = useTranslation();
const [inventoryItems, setInventoryItems] = useState<InventoryItemForm[]>([]);
const [activeFilter, setActiveFilter] = useState<FilterType>('all');
const [isAdding, setIsAdding] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null);
const [formData, setFormData] = useState<InventoryItemForm>({
id: '',
name: '',
product_type: ProductType.INGREDIENT,
category: '',
unit_of_measure: UnitOfMeasure.KILOGRAMS,
isSuggested: false,
});
const [formErrors, setFormErrors] = useState<Record<string, string>>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const currentTenant = useCurrentTenant();
const tenantId = currentTenant?.id || '';
// API hooks
const createIngredientMutation = useCreateIngredient();
const importSalesMutation = useImportSalesData();
// Initialize with AI suggestions
useEffect(() => {
if (initialData?.aiSuggestions) {
const items: InventoryItemForm[] = initialData.aiSuggestions.map((suggestion, index) => ({
id: `ai-${index}-${Date.now()}`,
name: suggestion.suggested_name,
product_type: suggestion.product_type as ProductType,
category: suggestion.category,
unit_of_measure: suggestion.unit_of_measure as UnitOfMeasure,
isSuggested: true,
confidence_score: suggestion.confidence_score,
sales_data: suggestion.sales_data ? {
total_quantity: suggestion.sales_data.total_quantity,
average_daily_sales: suggestion.sales_data.average_daily_sales,
} : undefined,
}));
setInventoryItems(items);
}
}, [initialData]);
// Filter items
const filteredItems = inventoryItems.filter(item => {
if (activeFilter === 'ingredients') return item.product_type === ProductType.INGREDIENT;
if (activeFilter === 'finished_products') return item.product_type === ProductType.FINISHED_PRODUCT;
return true;
});
// Count by type
const counts = {
all: inventoryItems.length,
ingredients: inventoryItems.filter(i => i.product_type === ProductType.INGREDIENT).length,
finished_products: inventoryItems.filter(i => i.product_type === ProductType.FINISHED_PRODUCT).length,
};
// Form handlers
const handleAdd = () => {
setFormData({
id: `manual-${Date.now()}`,
name: '',
product_type: ProductType.INGREDIENT,
category: '',
unit_of_measure: UnitOfMeasure.KILOGRAMS,
isSuggested: false,
});
setEditingId(null);
setIsAdding(true);
setFormErrors({});
};
const handleEdit = (item: InventoryItemForm) => {
setFormData({ ...item });
setEditingId(item.id);
setIsAdding(true);
setFormErrors({});
};
const handleDelete = (id: string) => {
setInventoryItems(items => items.filter(item => item.id !== id));
};
const validateForm = (): boolean => {
const errors: Record<string, string> = {};
if (!formData.name.trim()) {
errors.name = t('validation:name_required', 'El nombre es requerido');
}
if (!formData.category) {
errors.category = t('validation:category_required', 'La categoría es requerida');
}
setFormErrors(errors);
return Object.keys(errors).length === 0;
};
const handleSave = () => {
if (!validateForm()) return;
if (editingId) {
// Update existing
setInventoryItems(items =>
items.map(item => (item.id === editingId ? formData : item))
);
} else {
// Add new
setInventoryItems(items => [...items, formData]);
}
setIsAdding(false);
setEditingId(null);
setFormData({
id: '',
name: '',
product_type: ProductType.INGREDIENT,
category: '',
unit_of_measure: UnitOfMeasure.KILOGRAMS,
isSuggested: false,
});
};
const handleCancel = () => {
setIsAdding(false);
setEditingId(null);
setFormErrors({});
};
const handleAddTemplate = (template: IngredientTemplate) => {
// Check for duplicates by name
const existingNames = new Set(inventoryItems.map(item => item.name.toLowerCase()));
const newItems = template.items
.filter(item => !existingNames.has(item.name.toLowerCase()))
.map((item, index) => ({
id: `template-${template.id}-${index}-${Date.now()}`,
name: item.name,
product_type: item.product_type,
category: item.category,
unit_of_measure: item.unit_of_measure,
isSuggested: false,
}));
if (newItems.length > 0) {
setInventoryItems(items => [...items, ...newItems]);
}
};
const handleCompleteStep = async () => {
if (inventoryItems.length === 0) {
setFormErrors({ submit: t('validation:min_items', 'Agrega al menos 1 producto para continuar') });
return;
}
setIsSubmitting(true);
setFormErrors({});
try {
// STEP 1: Create all inventory items in parallel
// This MUST happen BEFORE sales import because sales records reference inventory IDs
console.log('📦 Creating inventory items...', inventoryItems.length);
console.log('📋 Items to create:', inventoryItems.map(item => ({
name: item.name,
product_type: item.product_type,
category: item.category,
unit_of_measure: item.unit_of_measure
})));
const createPromises = inventoryItems.map((item, index) => {
const ingredientData: IngredientCreate = {
name: item.name,
product_type: item.product_type,
category: item.category,
unit_of_measure: item.unit_of_measure as UnitOfMeasure,
// All other fields are optional now!
};
console.log(`🔄 Creating ingredient ${index + 1}/${inventoryItems.length}:`, ingredientData);
return createIngredientMutation.mutateAsync({
tenantId,
ingredientData,
}).catch(error => {
console.error(`❌ Failed to create ingredient "${item.name}":`, error);
console.error('Failed ingredient data:', ingredientData);
throw error;
});
});
await Promise.all(createPromises);
console.log('✅ Inventory items created successfully');
// STEP 2: Import sales data (only if file was uploaded)
// Now that inventory exists, sales records can reference the inventory IDs
let salesImported = false;
if (initialData?.uploadedFile && tenantId) {
try {
console.log('📊 Importing sales data from file:', initialData.uploadedFileName);
await importSalesMutation.mutateAsync({
tenantId,
file: initialData.uploadedFile,
});
salesImported = true;
console.log('✅ Sales data imported successfully');
} catch (salesError) {
console.error('⚠️ Sales import failed (non-blocking):', salesError);
// Don't block onboarding if sales import fails
// Inventory is already created, which is the critical part
}
}
// Complete the step with metadata
onComplete({
inventoryItemsCreated: inventoryItems.length,
salesDataImported: salesImported,
});
} catch (error) {
console.error('Error creating inventory items:', error);
setFormErrors({ submit: t('error:creating_items', 'Error al crear los productos. Inténtalo de nuevo.') });
setIsSubmitting(false);
}
};
// Category options based on product type
const getCategoryOptions = (productType: ProductType) => {
if (productType === ProductType.INGREDIENT) {
return Object.values(IngredientCategory).map(cat => ({
value: cat,
label: t(`inventory:enums.ingredient_category.${cat}`, cat)
}));
} else {
return Object.values(ProductCategory).map(cat => ({
value: cat,
label: t(`inventory:enums.product_category.${cat}`, cat)
}));
}
};
const unitOptions = Object.values(UnitOfMeasure).map(unit => ({
value: unit,
label: t(`inventory:enums.unit_of_measure.${unit}`, unit)
}));
return (
<div className="space-y-6">
{/* Header */}
<div>
<h2 className="text-xl md:text-2xl font-bold text-[var(--text-primary)] mb-2">
{t('onboarding:inventory_review.title', 'Revisar Inventario')}
</h2>
<p className="text-sm md:text-base text-[var(--text-secondary)]">
{t('onboarding:inventory_review.description', 'Revisa y ajusta los productos detectados. Puedes editar, eliminar o agregar más productos.')}
</p>
</div>
{/* Why This Matters */}
<div className="bg-[var(--color-info)]/10 border border-[var(--color-info)]/20 rounded-lg p-4">
<h3 className="font-semibold text-[var(--text-primary)] mb-2 flex items-center gap-2">
<AlertCircle className="w-5 h-5 text-[var(--color-info)]" />
{t('setup_wizard:why_this_matters', '¿Por qué es importante?')}
</h3>
<p className="text-sm text-[var(--text-secondary)]">
{t('onboarding:inventory_review.why', 'Estos productos serán la base de tu sistema. Diferenciamos entre Ingredientes (lo que usas para producir) y Productos Terminados (lo que vendes).')}
</p>
</div>
{/* Quick Add Templates */}
<div className="bg-gradient-to-br from-purple-50 to-blue-50 dark:from-purple-900/10 dark:to-blue-900/10 border border-purple-200 dark:border-purple-700 rounded-lg p-5">
<div className="flex items-center gap-2 mb-4">
<Sparkles className="w-5 h-5 text-purple-600 dark:text-purple-400" />
<h3 className="font-semibold text-[var(--text-primary)]">
{t('inventory:templates.title', 'Plantillas de Ingredientes')}
</h3>
</div>
<p className="text-sm text-[var(--text-secondary)] mb-4">
{t('inventory:templates.description', 'Agrega ingredientes comunes con un solo clic. Solo se agregarán los que no tengas ya.')}
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{INGREDIENT_TEMPLATES.map((template) => (
<button
key={template.id}
onClick={() => handleAddTemplate(template)}
className="text-left p-4 bg-white dark:bg-gray-800 border-2 border-purple-200 dark:border-purple-700 rounded-lg hover:border-purple-400 dark:hover:border-purple-500 hover:shadow-md transition-all group"
>
<div className="flex items-start gap-3">
<span className="text-3xl group-hover:scale-110 transition-transform">{template.icon}</span>
<div className="flex-1 min-w-0">
<h4 className="font-medium text-[var(--text-primary)] mb-1">
{template.name}
</h4>
<p className="text-xs text-[var(--text-secondary)] mb-2">
{template.description}
</p>
<p className="text-xs text-purple-600 dark:text-purple-400 font-medium">
{template.items.length} {t('inventory:templates.items', 'ingredientes')}
</p>
</div>
</div>
</button>
))}
</div>
</div>
{/* Filter Tabs */}
<div className="flex gap-2 border-b border-[var(--border-color)] overflow-x-auto scrollbar-hide -mx-2 px-2">
<button
onClick={() => setActiveFilter('all')}
className={`px-3 md:px-4 py-3 font-medium transition-colors relative whitespace-nowrap text-sm md:text-base ${
activeFilter === 'all'
? 'text-[var(--color-primary)] border-b-2 border-[var(--color-primary)] -mb-px'
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
}`}
>
{t('inventory:filter.all', 'Todos')} ({counts.all})
</button>
<button
onClick={() => setActiveFilter('finished_products')}
className={`px-3 md:px-4 py-3 font-medium transition-colors relative flex items-center gap-2 whitespace-nowrap text-sm md:text-base ${
activeFilter === 'finished_products'
? 'text-[var(--color-primary)] border-b-2 border-[var(--color-primary)] -mb-px'
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
}`}
>
<ShoppingBag className="w-5 h-5" />
<span className="hidden sm:inline">{t('inventory:filter.finished_products', 'Productos Terminados')}</span>
<span className="sm:hidden">{t('inventory:filter.finished_products_short', 'Productos')}</span> ({counts.finished_products})
</button>
<button
onClick={() => setActiveFilter('ingredients')}
className={`px-3 md:px-4 py-3 font-medium transition-colors relative flex items-center gap-2 whitespace-nowrap text-sm md:text-base ${
activeFilter === 'ingredients'
? 'text-[var(--color-primary)] border-b-2 border-[var(--color-primary)] -mb-px'
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
}`}
>
<Package className="w-5 h-5" />
{t('inventory:filter.ingredients', 'Ingredientes')} ({counts.ingredients})
</button>
</div>
{/* Inventory List */}
<div className="space-y-3">
{filteredItems.length === 0 && (
<div className="text-center py-8 text-[var(--text-secondary)]">
{activeFilter === 'all'
? t('inventory:empty_state', 'No hay productos. Agrega uno para comenzar.')
: t('inventory:no_results', 'No hay productos de este tipo.')}
</div>
)}
{filteredItems.map((item) => (
<React.Fragment key={item.id}>
{/* Item Card */}
<div className="p-3 bg-[var(--bg-secondary)] border border-[var(--border-secondary)] rounded-lg hover:border-[var(--border-primary)] transition-colors">
<div className="flex items-center justify-between">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h5 className="font-medium text-[var(--text-primary)] truncate">{item.name}</h5>
{/* Product Type Badge */}
<span className={`text-xs px-2 py-0.5 rounded-full ${
item.product_type === ProductType.FINISHED_PRODUCT
? 'bg-green-100 text-green-800'
: 'bg-blue-100 text-blue-800'
}`}>
{item.product_type === ProductType.FINISHED_PRODUCT ? (
<span className="flex items-center gap-1">
<ShoppingBag className="w-3 h-3" />
{t('inventory:type.finished_product', 'Producto')}
</span>
) : (
<span className="flex items-center gap-1">
<Package className="w-3 h-3" />
{t('inventory:type.ingredient', 'Ingrediente')}
</span>
)}
</span>
{/* AI Suggested Badge */}
{item.isSuggested && item.confidence_score && (
<span className="px-2 py-0.5 rounded text-xs bg-purple-100 text-purple-800">
IA {Math.round(item.confidence_score * 100)}%
</span>
)}
</div>
<div className="flex items-center gap-3 mt-1 text-xs text-[var(--text-secondary)]">
<span>{item.product_type === ProductType.INGREDIENT ? t(`inventory:enums.ingredient_category.${item.category}`, item.category) : t(`inventory:enums.product_category.${item.category}`, item.category)}</span>
<span></span>
<span>{t(`inventory:enums.unit_of_measure.${item.unit_of_measure}`, item.unit_of_measure)}</span>
{item.sales_data && (
<>
<span></span>
<span>{t('inventory:sales_avg', 'Ventas')}: {item.sales_data.average_daily_sales.toFixed(1)}/día</span>
</>
)}
</div>
</div>
{/* Actions */}
<div className="flex items-center gap-2 ml-2 md:ml-4">
<button
onClick={() => handleEdit(item)}
className="p-2 md:p-1.5 text-[var(--text-secondary)] hover:text-[var(--color-primary)] hover:bg-[var(--bg-primary)] rounded transition-colors min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center"
title={t('common:edit', 'Editar')}
aria-label={t('common:edit', 'Editar')}
>
<Edit2 className="w-5 h-5 md:w-4 md:h-4" />
</button>
<button
onClick={() => handleDelete(item.id)}
className="p-2 md:p-1.5 text-[var(--text-secondary)] hover:text-[var(--color-error)] hover:bg-[var(--color-error)]/10 rounded transition-colors min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center"
title={t('common:delete', 'Eliminar')}
aria-label={t('common:delete', 'Eliminar')}
>
<Trash2 className="w-5 h-5 md:w-4 md:h-4" />
</button>
</div>
</div>
</div>
{/* Inline Edit Form - appears right below the card being edited */}
{editingId === item.id && (
<div className="border-2 border-[var(--color-primary)] rounded-lg p-3 md:p-4 bg-[var(--bg-secondary)] ml-0 md:ml-4 mt-2">
<div className="flex items-center justify-between mb-4">
<h3 className="font-medium text-[var(--text-primary)]">
{t('inventory:edit_item', 'Editar Producto')}
</h3>
<button
type="button"
onClick={handleCancel}
className="text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
>
{t('common:cancel', 'Cancelar')}
</button>
</div>
<div className="space-y-4">
{/* Product Type Selector */}
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-3">
{t('inventory:product_type', 'Tipo de Producto')} *
</label>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<button
type="button"
onClick={() => setFormData(prev => ({ ...prev, product_type: ProductType.INGREDIENT, category: '' }))}
className={`relative p-3 sm:p-4 border-2 rounded-lg text-left transition-all ${
formData.product_type === ProductType.INGREDIENT
? 'border-[var(--color-primary)] bg-[var(--color-primary)]/30 shadow-lg ring-2 ring-[var(--color-primary)]/50'
: 'border-[var(--border-color)] hover:border-[var(--color-primary)]/50 hover:bg-[var(--bg-secondary)]'
}`}
>
{formData.product_type === ProductType.INGREDIENT && (
<CheckCircle2 className="absolute top-2 right-2 w-5 h-5 text-[var(--color-primary)]" />
)}
<Package className="w-6 h-6 mb-2 text-[var(--color-primary)]" />
<div className="font-medium text-[var(--text-primary)]">
{t('inventory:type.ingredient', 'Ingrediente')}
</div>
<div className="text-xs text-[var(--text-secondary)] mt-1">
{t('inventory:type.ingredient_desc', 'Materias primas para producir')}
</div>
</button>
<button
type="button"
onClick={() => setFormData(prev => ({ ...prev, product_type: ProductType.FINISHED_PRODUCT, category: '' }))}
className={`relative p-3 sm:p-4 border-2 rounded-lg text-left transition-all ${
formData.product_type === ProductType.FINISHED_PRODUCT
? 'border-[var(--color-primary)] bg-[var(--color-primary)]/30 shadow-lg ring-2 ring-[var(--color-primary)]/50'
: 'border-[var(--border-color)] hover:border-[var(--color-primary)]/50 hover:bg-[var(--bg-secondary)]'
}`}
>
{formData.product_type === ProductType.FINISHED_PRODUCT && (
<CheckCircle2 className="absolute top-2 right-2 w-5 h-5 text-[var(--color-primary)]" />
)}
<ShoppingBag className="w-6 h-6 mb-2 text-[var(--color-primary)]" />
<div className="font-medium text-[var(--text-primary)]">
{t('inventory:type.finished_product', 'Producto Terminado')}
</div>
<div className="text-xs text-[var(--text-secondary)] mt-1">
{t('inventory:type.finished_product_desc', 'Productos que vendes')}
</div>
</button>
</div>
</div>
{/* Name */}
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
{t('inventory:name', 'Nombre')} *
</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
className="w-full px-3 py-2 border border-[var(--border-color)] rounded-lg bg-[var(--bg-primary)] text-[var(--text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
placeholder={t('inventory:name_placeholder', 'Ej: Harina de trigo')}
/>
{formErrors.name && (
<p className="text-xs text-[var(--color-danger)] mt-1">{formErrors.name}</p>
)}
</div>
{/* Category */}
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
{t('inventory:category', 'Categoría')} *
</label>
<select
value={formData.category}
onChange={(e) => setFormData(prev => ({ ...prev, category: e.target.value }))}
className="w-full px-3 py-2 border border-[var(--border-color)] rounded-lg bg-[var(--bg-primary)] text-[var(--text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
>
<option value="">{t('common:select', 'Seleccionar...')}</option>
{getCategoryOptions(formData.product_type).map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
{formErrors.category && (
<p className="text-xs text-[var(--color-danger)] mt-1">{formErrors.category}</p>
)}
</div>
{/* Unit of Measure */}
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
{t('inventory:unit_of_measure', 'Unidad de Medida')} *
</label>
<select
value={formData.unit_of_measure}
onChange={(e) => setFormData(prev => ({ ...prev, unit_of_measure: e.target.value as UnitOfMeasure }))}
className="w-full px-3 py-2 border border-[var(--border-color)] rounded-lg bg-[var(--bg-primary)] text-[var(--text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
>
{unitOptions.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
</div>
{/* Form Actions */}
<div className="flex justify-end pt-2">
<Button onClick={handleSave}>
<CheckCircle2 className="w-4 h-4 mr-2" />
{t('common:save', 'Guardar')}
</Button>
</div>
</div>
</div>
)}
</React.Fragment>
))}
</div>
{/* Add Button - hidden when adding or editing */}
{!isAdding && !editingId && (
<button
onClick={handleAdd}
className="w-full border-2 border-dashed border-[var(--color-primary)]/30 rounded-lg p-4 md:p-4 hover:border-[var(--color-primary)]/50 transition-colors flex items-center justify-center gap-2 text-[var(--color-primary)] min-h-[44px] font-medium"
>
<Plus className="w-5 h-5" />
<span className="text-sm md:text-base">{t('inventory:add_item', 'Agregar Producto')}</span>
</button>
)}
{/* Add New Item Form - only shown when adding (not editing) */}
{isAdding && !editingId && (
<div className="border-2 border-[var(--color-primary)] rounded-lg p-3 md:p-4 bg-[var(--bg-secondary)]">
<div className="flex items-center justify-between mb-4">
<h3 className="font-medium text-[var(--text-primary)]">
{t('inventory:add_item', 'Agregar Producto')}
</h3>
<button
type="button"
onClick={handleCancel}
className="text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
>
{t('common:cancel', 'Cancelar')}
</button>
</div>
<div className="space-y-4">
{/* Product Type Selector */}
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-3">
{t('inventory:product_type', 'Tipo de Producto')} *
</label>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<button
type="button"
onClick={() => setFormData(prev => ({ ...prev, product_type: ProductType.INGREDIENT, category: '' }))}
className={`relative p-3 sm:p-4 border-2 rounded-lg text-left transition-all ${
formData.product_type === ProductType.INGREDIENT
? 'border-[var(--color-primary)] bg-[var(--color-primary)]/30 shadow-lg ring-2 ring-[var(--color-primary)]/50'
: 'border-[var(--border-color)] hover:border-[var(--color-primary)]/50 hover:bg-[var(--bg-secondary)]'
}`}
>
{formData.product_type === ProductType.INGREDIENT && (
<CheckCircle2 className="absolute top-2 right-2 w-5 h-5 text-[var(--color-primary)]" />
)}
<Package className="w-6 h-6 mb-2 text-[var(--color-primary)]" />
<div className="font-medium text-[var(--text-primary)]">
{t('inventory:type.ingredient', 'Ingrediente')}
</div>
<div className="text-xs text-[var(--text-secondary)] mt-1">
{t('inventory:type.ingredient_desc', 'Materias primas para producir')}
</div>
</button>
<button
type="button"
onClick={() => setFormData(prev => ({ ...prev, product_type: ProductType.FINISHED_PRODUCT, category: '' }))}
className={`relative p-3 sm:p-4 border-2 rounded-lg text-left transition-all ${
formData.product_type === ProductType.FINISHED_PRODUCT
? 'border-[var(--color-primary)] bg-[var(--color-primary)]/30 shadow-lg ring-2 ring-[var(--color-primary)]/50'
: 'border-[var(--border-color)] hover:border-[var(--color-primary)]/50 hover:bg-[var(--bg-secondary)]'
}`}
>
{formData.product_type === ProductType.FINISHED_PRODUCT && (
<CheckCircle2 className="absolute top-2 right-2 w-5 h-5 text-[var(--color-primary)]" />
)}
<ShoppingBag className="w-6 h-6 mb-2 text-[var(--color-primary)]" />
<div className="font-medium text-[var(--text-primary)]">
{t('inventory:type.finished_product', 'Producto Terminado')}
</div>
<div className="text-xs text-[var(--text-secondary)] mt-1">
{t('inventory:type.finished_product_desc', 'Productos que vendes')}
</div>
</button>
</div>
</div>
{/* Name */}
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
{t('inventory:name', 'Nombre')} *
</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
className="w-full px-3 py-2 border border-[var(--border-color)] rounded-lg bg-[var(--bg-primary)] text-[var(--text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
placeholder={t('inventory:name_placeholder', 'Ej: Harina de trigo')}
/>
{formErrors.name && (
<p className="text-xs text-[var(--color-danger)] mt-1">{formErrors.name}</p>
)}
</div>
{/* Category */}
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
{t('inventory:category', 'Categoría')} *
</label>
<select
value={formData.category}
onChange={(e) => setFormData(prev => ({ ...prev, category: e.target.value }))}
className="w-full px-3 py-2 border border-[var(--border-color)] rounded-lg bg-[var(--bg-primary)] text-[var(--text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
>
<option value="">{t('common:select', 'Seleccionar...')}</option>
{getCategoryOptions(formData.product_type).map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
{formErrors.category && (
<p className="text-xs text-[var(--color-danger)] mt-1">{formErrors.category}</p>
)}
</div>
{/* Unit of Measure */}
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
{t('inventory:unit_of_measure', 'Unidad de Medida')} *
</label>
<select
value={formData.unit_of_measure}
onChange={(e) => setFormData(prev => ({ ...prev, unit_of_measure: e.target.value as UnitOfMeasure }))}
className="w-full px-3 py-2 border border-[var(--border-color)] rounded-lg bg-[var(--bg-primary)] text-[var(--text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
>
{unitOptions.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
</div>
{/* Form Actions */}
<div className="flex justify-end pt-2">
<Button onClick={handleSave}>
<CheckCircle2 className="w-4 h-4 mr-2" />
{t('common:add', 'Agregar')}
</Button>
</div>
</div>
</div>
)}
{/* Submit Error */}
{formErrors.submit && (
<div className="bg-[var(--color-danger)]/10 border border-[var(--color-danger)]/20 rounded-lg p-4">
<p className="text-sm text-[var(--color-danger)]">{formErrors.submit}</p>
</div>
)}
{/* Summary */}
<div className="bg-[var(--bg-secondary)] rounded-lg p-4">
<p className="text-sm text-[var(--text-secondary)]">
{t('inventory:summary', 'Resumen')}: {counts.finished_products} {t('inventory:finished_products', 'productos terminados')}, {counts.ingredients} {t('inventory:ingredients_count', 'ingredientes')}
</p>
</div>
{/* Navigation */}
<div className="flex flex-col-reverse sm:flex-row justify-between gap-3 sm:gap-4 pt-6 border-t">
<Button
variant="outline"
onClick={onPrevious}
disabled={isSubmitting || isFirstStep}
className="w-full sm:w-auto"
>
<span className="hidden sm:inline">{t('common:back', '← Atrás')}</span>
<span className="sm:hidden">{t('common:back', 'Atrás')}</span>
</Button>
<Button
onClick={handleCompleteStep}
disabled={inventoryItems.length === 0 || isSubmitting}
className="w-full sm:w-auto sm:min-w-[200px]"
>
{isSubmitting
? t('common:saving', 'Guardando...')
: <>
<span className="hidden md:inline">{t('common:continue', 'Continuar')} ({inventoryItems.length} {t('common:items', 'productos')}) </span>
<span className="md:hidden">{t('common:continue', 'Continuar')} ({inventoryItems.length}) </span>
</>
}
</Button>
</div>
</div>
);
};