import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { Card, Button, Input, Select, Modal, Badge, Tooltip } from '../../ui'; import { SalesChannel, PaymentMethod } from '../../../types/sales.types'; import { salesService } from '../../../services/api/sales.service'; // Order form interfaces interface Product { id: string; name: string; description?: string; category: string; price: number; available_quantity: number; image_url?: string; allergens: string[]; nutritional_info?: NutritionalInfo; preparation_time: number; // in minutes is_available: boolean; tags: string[]; } interface NutritionalInfo { calories_per_100g: number; protein: number; carbs: number; fat: number; fiber: number; sugar: number; } interface Customer { id: string; name: string; email?: string; phone?: string; address?: string; city?: string; postal_code?: string; loyalty_points: number; preferred_channel: SalesChannel; notes?: string; } interface OrderItem { product_id: string; product_name: string; quantity: number; unit_price: number; total_price: number; special_instructions?: string; customizations?: OrderCustomization[]; } interface OrderCustomization { name: string; value: string; price_adjustment: number; } interface DeliveryInfo { type: DeliveryType; address?: string; city?: string; postal_code?: string; delivery_date?: string; delivery_time_slot?: string; delivery_notes?: string; delivery_fee: number; } interface OrderFormData { customer?: Customer; items: OrderItem[]; sales_channel: SalesChannel; payment_method?: PaymentMethod; delivery_info?: DeliveryInfo; special_instructions?: string; discount_code?: string; discount_amount: number; tax_rate: number; subtotal: number; tax_amount: number; delivery_fee: number; total_amount: number; scheduled_date?: string; scheduled_time?: string; loyalty_points_to_use: number; notes?: string; } enum DeliveryType { PICKUP = 'pickup', HOME_DELIVERY = 'home_delivery', SCHEDULED_PICKUP = 'scheduled_pickup' } enum PaymentMethodType { CASH = 'cash', CREDIT_CARD = 'credit_card', DEBIT_CARD = 'debit_card', DIGITAL_WALLET = 'digital_wallet', BANK_TRANSFER = 'bank_transfer', LOYALTY_POINTS = 'loyalty_points', STORE_CREDIT = 'store_credit' } interface OrderFormProps { orderId?: string; // For editing existing orders initialCustomer?: Customer; onOrderSave?: (orderData: OrderFormData) => Promise; onOrderCancel?: () => void; allowCustomerCreation?: boolean; showPricing?: boolean; className?: string; } const ChannelLabels = { [SalesChannel.STORE_FRONT]: 'Tienda Física', [SalesChannel.ONLINE]: 'Tienda Online', [SalesChannel.PHONE_ORDER]: 'Pedido Telefónico', [SalesChannel.DELIVERY]: 'Servicio Delivery', [SalesChannel.CATERING]: 'Catering', [SalesChannel.WHOLESALE]: 'Venta Mayorista', [SalesChannel.FARMERS_MARKET]: 'Mercado Local', [SalesChannel.THIRD_PARTY]: 'Plataforma Terceros' }; const PaymentMethodLabels = { [PaymentMethodType.CASH]: 'Efectivo', [PaymentMethodType.CREDIT_CARD]: 'Tarjeta de Crédito', [PaymentMethodType.DEBIT_CARD]: 'Tarjeta de Débito', [PaymentMethodType.DIGITAL_WALLET]: 'Wallet Digital', [PaymentMethodType.BANK_TRANSFER]: 'Transferencia', [PaymentMethodType.LOYALTY_POINTS]: 'Puntos de Fidelización', [PaymentMethodType.STORE_CREDIT]: 'Crédito de Tienda' }; const DeliveryTypeLabels = { [DeliveryType.PICKUP]: 'Recogida en Tienda', [DeliveryType.HOME_DELIVERY]: 'Entrega a Domicilio', [DeliveryType.SCHEDULED_PICKUP]: 'Recogida Programada' }; // Mock data for bakery products const mockProducts: Product[] = [ { id: 'pan-integral-001', name: 'Pan Integral Artesano', description: 'Pan elaborado con harina integral 100%, masa madre natural', category: 'Panadería', price: 2.50, available_quantity: 25, allergens: ['gluten'], preparation_time: 180, is_available: true, tags: ['artesano', 'integral', 'masa-madre'] }, { id: 'croissant-mantequilla-002', name: 'Croissant de Mantequilla', description: 'Croissant francés tradicional con mantequilla de Normandía', category: 'Bollería', price: 1.80, available_quantity: 40, allergens: ['gluten', 'lactosa', 'huevo'], preparation_time: 240, is_available: true, tags: ['frances', 'mantequilla', 'tradicional'] }, { id: 'tarta-santiago-003', name: 'Tarta de Santiago', description: 'Tarta tradicional gallega con almendra marcona', category: 'Repostería', price: 18.90, available_quantity: 8, allergens: ['almendra', 'huevo'], preparation_time: 360, is_available: true, tags: ['tradicional', 'almendra', 'gallega'] }, { id: 'magdalenas-limon-004', name: 'Magdalenas de Limón', description: 'Magdalenas esponjosas con ralladura de limón ecológico', category: 'Bollería', price: 0.90, available_quantity: 60, allergens: ['gluten', 'huevo', 'lactosa'], preparation_time: 45, is_available: true, tags: ['limon', 'esponjosas', 'ecologico'] }, { id: 'empanada-atun-005', name: 'Empanada de Atún', description: 'Empanada gallega rellena de atún, pimiento y huevo', category: 'Salado', price: 12.50, available_quantity: 6, allergens: ['gluten', 'pescado', 'huevo'], preparation_time: 300, is_available: true, tags: ['gallega', 'atun', 'tradicional'] }, { id: 'brownie-chocolate-006', name: 'Brownie de Chocolate Negro', description: 'Brownie intenso con chocolate negro 70% y nueces', category: 'Repostería', price: 3.20, available_quantity: 20, allergens: ['gluten', 'huevo', 'frutos-secos', 'lactosa'], preparation_time: 60, is_available: true, tags: ['chocolate', 'nueces', 'intenso'] } ]; const mockCustomers: Customer[] = [ { id: 'customer-001', name: 'María García López', email: 'maria.garcia@email.com', phone: '+34 612 345 678', address: 'Calle Mayor, 123', city: 'Madrid', postal_code: '28001', loyalty_points: 2450, preferred_channel: SalesChannel.ONLINE, notes: 'Cliente VIP. Prefiere productos sin gluten.' }, { id: 'customer-002', name: 'Carlos Rodríguez Martín', email: 'carlos.rodriguez@email.com', phone: '+34 687 654 321', address: 'Avenida de la Constitución, 45', city: 'Madrid', postal_code: '28002', loyalty_points: 1230, preferred_channel: SalesChannel.STORE_FRONT }, { id: 'customer-003', name: 'Ana Fernández Ruiz', email: 'ana.fernandez@email.com', phone: '+34 698 765 432', address: 'Plaza de España, 12', city: 'Madrid', postal_code: '28008', loyalty_points: 890, preferred_channel: SalesChannel.DELIVERY } ]; export const OrderForm: React.FC = ({ orderId, initialCustomer, onOrderSave, onOrderCancel, allowCustomerCreation = true, showPricing = true, className = '' }) => { // Form data state const [orderData, setOrderData] = useState({ customer: initialCustomer, items: [], sales_channel: SalesChannel.STORE_FRONT, discount_amount: 0, tax_rate: 0.21, // 21% IVA in Spain subtotal: 0, tax_amount: 0, delivery_fee: 0, total_amount: 0, loyalty_points_to_use: 0 }); // UI state const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); // Product selection const [products, setProducts] = useState(mockProducts); const [filteredProducts, setFilteredProducts] = useState(mockProducts); const [selectedCategory, setSelectedCategory] = useState(''); const [productSearchTerm, setProductSearchTerm] = useState(''); const [showProductModal, setShowProductModal] = useState(false); // Customer management const [customers, setCustomers] = useState(mockCustomers); const [customerSearchTerm, setCustomerSearchTerm] = useState(''); const [showCustomerModal, setShowCustomerModal] = useState(false); const [newCustomerForm, setNewCustomerForm] = useState>({}); const [showNewCustomerForm, setShowNewCustomerForm] = useState(false); // Validation const [validationErrors, setValidationErrors] = useState>({}); // Available categories const categories = useMemo(() => { const cats = [...new Set(products.map(p => p.category))]; return cats; }, [products]); // Available time slots for delivery/pickup const timeSlots = [ '09:00 - 10:00', '10:00 - 11:00', '11:00 - 12:00', '12:00 - 13:00', '13:00 - 14:00', '15:00 - 16:00', '16:00 - 17:00', '17:00 - 18:00', '18:00 - 19:00' ]; // Filter products useEffect(() => { let filtered = products.filter(product => product.is_available); if (selectedCategory) { filtered = filtered.filter(product => product.category === selectedCategory); } if (productSearchTerm) { const searchLower = productSearchTerm.toLowerCase(); filtered = filtered.filter(product => product.name.toLowerCase().includes(searchLower) || product.description?.toLowerCase().includes(searchLower) || product.tags.some(tag => tag.toLowerCase().includes(searchLower)) ); } setFilteredProducts(filtered); }, [products, selectedCategory, productSearchTerm]); // Calculate totals useEffect(() => { const subtotal = orderData.items.reduce((sum, item) => sum + item.total_price, 0); const discountAmount = orderData.discount_code ? calculateDiscount(subtotal, orderData.discount_code) : 0; const subtotalAfterDiscount = subtotal - discountAmount; const taxAmount = subtotalAfterDiscount * orderData.tax_rate; const deliveryFee = calculateDeliveryFee(); const loyaltyPointsDiscount = orderData.loyalty_points_to_use * 0.01; // 1 point = 1 cent const totalAmount = Math.max(0, subtotalAfterDiscount + taxAmount + deliveryFee - loyaltyPointsDiscount); setOrderData(prev => ({ ...prev, subtotal, discount_amount: discountAmount, tax_amount: taxAmount, delivery_fee: deliveryFee, total_amount: totalAmount })); }, [orderData.items, orderData.discount_code, orderData.loyalty_points_to_use, orderData.delivery_info, orderData.tax_rate]); // Load existing order for editing useEffect(() => { if (orderId) { loadExistingOrder(orderId); } }, [orderId]); // Functions const loadExistingOrder = async (id: string) => { setLoading(true); try { // In real app, load from API // const response = await salesService.getOrder(id); console.log('Loading order:', id); } catch (err) { setError('Error al cargar el pedido'); } finally { setLoading(false); } }; const calculateDiscount = (subtotal: number, discountCode: string): number => { // Mock discount calculation const discountCodes = { 'BIENVENIDO10': 0.10, // 10% 'FIDELIDAD15': 0.15, // 15% 'CUMPLE20': 0.20 // 20% }; const discountRate = discountCodes[discountCode as keyof typeof discountCodes] || 0; return subtotal * discountRate; }; const calculateDeliveryFee = (): number => { if (!orderData.delivery_info || orderData.delivery_info.type === DeliveryType.PICKUP) { return 0; } if (orderData.delivery_info.type === DeliveryType.SCHEDULED_PICKUP) { return 0; } // Base delivery fee let fee = 3.50; // Free delivery for orders over €25 if (orderData.subtotal >= 25) { fee = 0; } return fee; }; const addProductToOrder = (product: Product, quantity: number = 1) => { const existingItemIndex = orderData.items.findIndex(item => item.product_id === product.id); if (existingItemIndex >= 0) { // Update existing item const updatedItems = [...orderData.items]; updatedItems[existingItemIndex].quantity += quantity; updatedItems[existingItemIndex].total_price = updatedItems[existingItemIndex].quantity * updatedItems[existingItemIndex].unit_price; setOrderData(prev => ({ ...prev, items: updatedItems })); } else { // Add new item const newItem: OrderItem = { product_id: product.id, product_name: product.name, quantity, unit_price: product.price, total_price: product.price * quantity }; setOrderData(prev => ({ ...prev, items: [...prev.items, newItem] })); } setShowProductModal(false); }; const updateOrderItem = (productId: string, updates: Partial) => { const updatedItems = orderData.items.map(item => { if (item.product_id === productId) { const updatedItem = { ...item, ...updates }; if (updates.quantity !== undefined) { updatedItem.total_price = updatedItem.quantity * updatedItem.unit_price; } return updatedItem; } return item; }); setOrderData(prev => ({ ...prev, items: updatedItems })); }; const removeOrderItem = (productId: string) => { const updatedItems = orderData.items.filter(item => item.product_id !== productId); setOrderData(prev => ({ ...prev, items: updatedItems })); }; const selectCustomer = (customer: Customer) => { setOrderData(prev => ({ ...prev, customer })); setShowCustomerModal(false); setCustomerSearchTerm(''); }; const createNewCustomer = async () => { if (!newCustomerForm.name || !newCustomerForm.phone) { setError('Nombre y teléfono son obligatorios'); return; } const newCustomer: Customer = { id: `customer-${Date.now()}`, name: newCustomerForm.name, email: newCustomerForm.email, phone: newCustomerForm.phone, address: newCustomerForm.address, city: newCustomerForm.city || 'Madrid', postal_code: newCustomerForm.postal_code, loyalty_points: 0, preferred_channel: orderData.sales_channel, notes: newCustomerForm.notes }; setCustomers(prev => [...prev, newCustomer]); setOrderData(prev => ({ ...prev, customer: newCustomer })); setNewCustomerForm({}); setShowNewCustomerForm(false); setShowCustomerModal(false); }; const validateForm = (): boolean => { const errors: Record = {}; if (orderData.items.length === 0) { errors.items = 'Debe agregar al menos un producto al pedido'; } if (!orderData.customer) { errors.customer = 'Debe seleccionar un cliente'; } if (orderData.delivery_info?.type === DeliveryType.HOME_DELIVERY) { if (!orderData.delivery_info.address) { errors.delivery_address = 'La dirección de entrega es obligatoria'; } if (!orderData.delivery_info.delivery_date) { errors.delivery_date = 'La fecha de entrega es obligatoria'; } } if (orderData.delivery_info?.type === DeliveryType.SCHEDULED_PICKUP) { if (!orderData.scheduled_date) { errors.scheduled_date = 'La fecha de recogida es obligatoria'; } if (!orderData.scheduled_time) { errors.scheduled_time = 'La hora de recogida es obligatoria'; } } setValidationErrors(errors); return Object.keys(errors).length === 0; }; const handleSaveOrder = async () => { if (!validateForm()) { return; } setLoading(true); setError(null); try { const success = await onOrderSave?.(orderData); if (success) { setSuccess('Pedido guardado correctamente'); // Reset form or navigate away } } catch (err) { setError('Error al guardar el pedido'); } finally { setLoading(false); } }; if (loading) { return (
{orderId ? 'Cargando pedido...' : 'Guardando pedido...'}
); } return (
{/* Header */}

{orderId ? 'Editar Pedido' : 'Nuevo Pedido'}

{orderId ? `Pedido #${orderId.slice(-8)}` : 'Crear un nuevo pedido'}

{/* Left Column - Order Details */}
{/* Customer Selection */}

Cliente

{orderData.customer ? (

{orderData.customer.name}

{orderData.customer.email &&
📧 {orderData.customer.email}
} {orderData.customer.phone &&
📞 {orderData.customer.phone}
} {orderData.customer.address && (
📍 {orderData.customer.address}, {orderData.customer.city}
)}
{orderData.customer.loyalty_points} puntos {ChannelLabels[orderData.customer.preferred_channel]}
) : (

No hay cliente seleccionado

)} {validationErrors.customer && (

{validationErrors.customer}

)}
{/* Product Selection */}

Productos

{orderData.items.length === 0 ? (

No hay productos en el pedido

) : ( orderData.items.map((item) => (

{item.product_name}

€{item.unit_price.toFixed(2)} × {item.quantity} = €{item.total_price.toFixed(2)}

{item.special_instructions && (

📝 {item.special_instructions}

)}
{item.quantity}
)) )}
{validationErrors.items && (

{validationErrors.items}

)}
{/* Delivery & Pickup Options */}

Entrega y Recogida

setOrderData(prev => ({ ...prev, delivery_info: { ...prev.delivery_info!, address: e.target.value } }))} error={validationErrors.delivery_address} required /> setOrderData(prev => ({ ...prev, delivery_info: { ...prev.delivery_info!, city: e.target.value } }))} />
setOrderData(prev => ({ ...prev, delivery_info: { ...prev.delivery_info!, delivery_date: e.target.value } }))} min={new Date().toISOString().split('T')[0]} error={validationErrors.delivery_date} required /> setOrderData(prev => ({ ...prev, delivery_info: { ...prev.delivery_info!, delivery_notes: e.target.value } }))} placeholder="Instrucciones especiales para el repartidor..." />

💡 Envío gratuito en pedidos superiores a €25. Tu pedido: €{orderData.subtotal.toFixed(2)} {orderData.subtotal < 25 && ( - Faltan €{(25 - orderData.subtotal).toFixed(2)} para envío gratuito )}

)} {orderData.delivery_info?.type === DeliveryType.SCHEDULED_PICKUP && (
setOrderData(prev => ({ ...prev, scheduled_date: e.target.value }))} min={new Date().toISOString().split('T')[0]} error={validationErrors.scheduled_date} required /> setOrderData(prev => ({ ...prev, sales_channel: value as SalesChannel }))} options={Object.values(SalesChannel).map(channel => ({ value: channel, label: ChannelLabels[channel] }))} />