import React, { useState, useEffect } from 'react'; import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal'; import { Users, Plus, Package, Truck, CreditCard, Search, CheckCircle2, Calendar, MapPin, Loader2, } from 'lucide-react'; import { useTenant } from '../../../../stores/tenant.store'; import OrdersService from '../../../../api/services/orders'; import { inventoryService } from '../../../../api/services/inventory'; import { showToast } from '../../../../utils/toast'; import { CustomerCreate, CustomerType, DeliveryMethod, PaymentTerms, CustomerSegment, PriorityLevel, OrderCreate, OrderItemCreate, OrderType, OrderSource, SalesChannel, PaymentMethod, } from '../../../../api/types/orders'; import { ProductType } from '../../../../api/types/inventory'; interface WizardDataProps extends WizardStepProps { data: Record; onDataChange: (data: Record) => void; } // Step 1: Customer Selection const CustomerSelectionStep: React.FC = ({ data, onDataChange, onNext }) => { const { currentTenant } = useTenant(); const [searchQuery, setSearchQuery] = useState(''); const [showNewCustomerForm, setShowNewCustomerForm] = useState(false); const [selectedCustomer, setSelectedCustomer] = useState(data.customer || null); const [customers, setCustomers] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [creatingCustomer, setCreatingCustomer] = useState(false); const [newCustomer, setNewCustomer] = useState({ name: '', type: CustomerType.BUSINESS, phone: '', email: '', }); useEffect(() => { fetchCustomers(); }, []); const fetchCustomers = async () => { if (!currentTenant?.id) return; setLoading(true); setError(null); try { const result = await OrdersService.getCustomers({ tenant_id: currentTenant.id, active_only: true, }); setCustomers(result); } catch (err: any) { console.error('Error loading customers:', err); setError('Error al cargar clientes'); } finally { setLoading(false); } }; const filteredCustomers = customers.filter((customer) => customer.name.toLowerCase().includes(searchQuery.toLowerCase()) ); const handleSelectCustomer = (customer: any) => { setSelectedCustomer(customer); setShowNewCustomerForm(false); }; const handleContinue = async () => { if (!currentTenant?.id) { setError('No se pudo obtener información del tenant'); return; } if (showNewCustomerForm) { // Create new customer via API setCreatingCustomer(true); setError(null); try { const customerData: CustomerCreate = { tenant_id: currentTenant.id, customer_code: `CUST-${Date.now()}`, name: newCustomer.name, customer_type: newCustomer.type, phone: newCustomer.phone, email: newCustomer.email, country: 'ES', is_active: true, preferred_delivery_method: DeliveryMethod.DELIVERY, payment_terms: PaymentTerms.IMMEDIATE, discount_percentage: 0, customer_segment: CustomerSegment.REGULAR, priority_level: PriorityLevel.NORMAL, }; const createdCustomer = await OrdersService.createCustomer(customerData); showToast.success('Cliente creado exitosamente'); onDataChange({ ...data, customer: createdCustomer, isNewCustomer: true }); onNext(); } catch (err: any) { console.error('Error creating customer:', err); const errorMessage = err.response?.data?.detail || 'Error al crear el cliente'; setError(errorMessage); showToast.error(errorMessage); } finally { setCreatingCustomer(false); } } else { onDataChange({ ...data, customer: selectedCustomer, isNewCustomer: false }); onNext(); } }; return (

¿Para quién es este pedido?

Busca un cliente existente o crea uno nuevo

{error && (
{error}
)} {loading ? (
Cargando clientes...
) : !showNewCustomerForm ? ( <> {/* Search Bar */}
setSearchQuery(e.target.value)} placeholder="Buscar cliente por nombre..." className="w-full pl-10 pr-4 py-3 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)]" />
{/* Customer List */}
{filteredCustomers.map((customer) => ( ))}
{/* Create New Customer Button */} ) : ( <> {/* New Customer Form */}

Nuevo Cliente

setNewCustomer({ ...newCustomer, name: e.target.value })} placeholder="Ej: Restaurante El Molino" className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" />
setNewCustomer({ ...newCustomer, phone: e.target.value })} placeholder="+34 123 456 789" className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" />
setNewCustomer({ ...newCustomer, email: e.target.value })} placeholder="contacto@restaurante.com" className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" />
)} {/* Continue Button */}
); }; // Step 2: Order Items const OrderItemsStep: React.FC = ({ data, onDataChange, onNext }) => { const { currentTenant } = useTenant(); const [orderItems, setOrderItems] = useState(data.orderItems || []); const [products, setProducts] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { fetchProducts(); }, []); const fetchProducts = async () => { if (!currentTenant?.id) return; setLoading(true); setError(null); try { const allIngredients = await inventoryService.getIngredients(currentTenant.id); // Filter for finished products only const finishedProducts = allIngredients.filter( (ingredient) => ingredient.product_type === ProductType.FINISHED_PRODUCT ); setProducts(finishedProducts); } catch (err: any) { console.error('Error loading products:', err); setError('Error al cargar productos'); } finally { setLoading(false); } }; const handleAddItem = () => { setOrderItems([ ...orderItems, { id: Date.now(), productId: '', productName: '', quantity: 1, unitPrice: 0, customRequirements: '', subtotal: 0 }, ]); }; const handleUpdateItem = (index: number, field: string, value: any) => { const updated = orderItems.map((item: any, i: number) => { if (i === index) { const newItem = { ...item, [field]: value }; // If product selected, update price and name if (field === 'productId') { const product = products.find((p) => p.id === value); if (product) { newItem.productName = product.name; newItem.unitPrice = product.average_cost || product.last_purchase_price || 0; newItem.unitOfMeasure = product.unit_of_measure; } } // Auto-calculate subtotal if (field === 'quantity' || field === 'unitPrice' || field === 'productId') { newItem.subtotal = (newItem.quantity || 0) * (newItem.unitPrice || 0); } return newItem; } return item; }); setOrderItems(updated); }; const handleRemoveItem = (index: number) => { setOrderItems(orderItems.filter((_: any, i: number) => i !== index)); }; const calculateTotal = () => { return orderItems.reduce((sum: number, item: any) => sum + (item.subtotal || 0), 0); }; const handleContinue = () => { onDataChange({ ...data, orderItems, totalAmount: calculateTotal() }); onNext(); }; return (

¿Qué productos incluye el pedido?

Cliente: {data.customer?.name}

{error && (
{error}
)} {loading ? (
Cargando productos...
) : ( <> {/* Order Items */}
{orderItems.length === 0 ? (

No hay productos en el pedido

Haz clic en "Agregar Producto" para comenzar

) : (
{orderItems.map((item: any, index: number) => (
Producto #{index + 1}
handleUpdateItem(index, 'quantity', parseFloat(e.target.value) || 0)} className="w-full px-3 py-2 text-sm border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" min="0" step="1" />
handleUpdateItem(index, 'unitPrice', parseFloat(e.target.value) || 0)} className="w-full px-3 py-2 text-sm border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" min="0" step="0.01" />
handleUpdateItem(index, 'customRequirements', e.target.value)} placeholder="Ej: Sin nueces, extra chocolate..." className="w-full px-3 py-2 text-sm border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" />
Subtotal: €{item.subtotal.toFixed(2)}
))}
)} {/* Total */} {orderItems.length > 0 && (
Total del Pedido: €{calculateTotal().toFixed(2)}
)}
)} {/* Continue Button */}
); }; // Step 3: Delivery & Payment const DeliveryPaymentStep: React.FC = ({ data, onDataChange, onComplete }) => { const { currentTenant } = useTenant(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [deliveryData, setDeliveryData] = useState({ deliveryDate: data.deliveryDate || '', deliveryTime: data.deliveryTime || '', deliveryMethod: data.deliveryMethod || 'pickup', deliveryAddress: data.deliveryAddress || '', paymentMethod: data.paymentMethod || 'invoice', specialInstructions: data.specialInstructions || '', orderStatus: data.orderStatus || 'pending', }); const handleConfirm = async () => { if (!currentTenant?.id) { setError('No se pudo obtener información del tenant'); return; } setLoading(true); setError(null); try { // Map UI delivery method to API enum const deliveryMethodMap: Record = { pickup: DeliveryMethod.PICKUP, delivery: DeliveryMethod.DELIVERY, shipping: DeliveryMethod.DELIVERY, }; // Map UI payment method to API enum const paymentMethodMap: Record = { cash: PaymentMethod.CASH, card: PaymentMethod.CARD, transfer: PaymentMethod.BANK_TRANSFER, invoice: PaymentMethod.ACCOUNT, 'invoice-30': PaymentMethod.ACCOUNT, paid: PaymentMethod.CARD, }; // Map UI payment method to payment terms const paymentTermsMap: Record = { cash: PaymentTerms.IMMEDIATE, card: PaymentTerms.IMMEDIATE, transfer: PaymentTerms.IMMEDIATE, invoice: PaymentTerms.NET_30, 'invoice-30': PaymentTerms.NET_30, paid: PaymentTerms.IMMEDIATE, }; // Prepare order items const orderItems: OrderItemCreate[] = data.orderItems.map((item: any) => ({ product_id: item.productId, product_name: item.productName, quantity: item.quantity, unit_of_measure: item.unitOfMeasure || 'unit', unit_price: item.unitPrice, line_discount: 0, customization_details: item.customRequirements || undefined, special_instructions: item.customRequirements || undefined, })); // Prepare delivery address const deliveryAddress = (deliveryData.deliveryMethod === 'delivery' || deliveryData.deliveryMethod === 'shipping') ? { address: deliveryData.deliveryAddress } : undefined; // Create order data const orderData: OrderCreate = { tenant_id: currentTenant.id, customer_id: data.customer.id, order_type: OrderType.STANDARD, priority: PriorityLevel.NORMAL, requested_delivery_date: deliveryData.deliveryDate, delivery_method: deliveryMethodMap[deliveryData.deliveryMethod], delivery_address: deliveryAddress, delivery_instructions: deliveryData.specialInstructions || undefined, discount_percentage: 0, delivery_fee: 0, payment_method: paymentMethodMap[deliveryData.paymentMethod], payment_terms: paymentTermsMap[deliveryData.paymentMethod], special_instructions: deliveryData.specialInstructions || undefined, order_source: OrderSource.MANUAL, sales_channel: SalesChannel.DIRECT, items: orderItems, }; await OrdersService.createOrder(orderData); showToast.success('Pedido creado exitosamente'); onDataChange({ ...data, ...deliveryData }); onComplete(); } catch (err: any) { console.error('Error creating order:', err); const errorMessage = err.response?.data?.detail || 'Error al crear el pedido'; setError(errorMessage); showToast.error(errorMessage); } finally { setLoading(false); } }; return (

Entrega y Pago

Configura los detalles de entrega y forma de pago

{error && (
{error}
)}
{/* Delivery Date & Time */}
setDeliveryData({ ...deliveryData, deliveryDate: e.target.value })} min={new Date().toISOString().split('T')[0]} className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" />
setDeliveryData({ ...deliveryData, deliveryTime: e.target.value })} className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" />
{/* Delivery Method */}
{/* Delivery Address (conditional) */} {(deliveryData.deliveryMethod === 'delivery' || deliveryData.deliveryMethod === 'shipping') && (