import React, { useState, useMemo } from 'react'; import { Plus, Minus, ShoppingCart, CreditCard, Banknote, Calculator, User, Receipt, Package, Euro, TrendingUp, Clock, ToggleLeft, ToggleRight, Settings, Zap, Wifi, WifiOff, AlertCircle, CheckCircle, Loader, Trash2, X, ChevronRight, ChevronLeft } from 'lucide-react'; import { Button, Card, StatusCard, getStatusColor, Badge } from '../../../../components/ui'; import { PageHeader } from '../../../../components/layout'; import { LoadingSpinner } from '../../../../components/ui'; import { formatters } from '../../../../components/ui/Stats/StatsPresets'; import { useIngredients } from '../../../../api/hooks/inventory'; import { useTenantId } from '../../../../hooks/useTenantId'; import { ProductType, ProductCategory, IngredientResponse } from '../../../../api/types/inventory'; import { showToast } from '../../../../utils/toast'; import { usePOSConfigurationData, usePOSConfigurationManager, usePOSTransactions, usePOSTransactionsDashboard, usePOSTransaction } from '../../../../api/hooks/pos'; import { POSConfiguration } from '../../../../api/types/pos'; import { posService } from '../../../../api/services/pos'; import { bakeryColors } from '../../../../styles/colors'; // Import new POS components import { POSProductCard } from '../../../../components/domain/pos/POSProductCard'; import { POSCart } from '../../../../components/domain/pos/POSCart'; import { POSPayment } from '../../../../components/domain/pos/POSPayment'; import { CreatePOSConfigModal } from '../../../../components/domain/pos/CreatePOSConfigModal'; interface CartItem { id: string; name: string; price: number; quantity: number; category: string; stock: number; } // Transactions Section Component const TransactionsSection: React.FC<{ tenantId: string }> = ({ tenantId }) => { const [page, setPage] = useState(0); const [selectedTransactionId, setSelectedTransactionId] = useState(null); const [showDetailModal, setShowDetailModal] = useState(false); const limit = 10; // Fetch transactions const { data: transactionsData, isLoading: transactionsLoading } = usePOSTransactions({ tenant_id: tenantId, limit, offset: page * limit, }); // Fetch dashboard summary const { data: dashboardData, isLoading: dashboardLoading } = usePOSTransactionsDashboard({ tenant_id: tenantId, }); // Fetch selected transaction details const { data: selectedTransaction, isLoading: detailLoading } = usePOSTransaction( { tenant_id: tenantId, transaction_id: selectedTransactionId || '', }, { enabled: !!selectedTransactionId, } ); const handleViewDetails = (transactionId: string) => { setSelectedTransactionId(transactionId); setShowDetailModal(true); }; const handleCloseDetail = () => { setShowDetailModal(false); setSelectedTransactionId(null); }; if (transactionsLoading || dashboardLoading) { return (
); } const transactions = transactionsData?.transactions || []; const summary = transactionsData?.summary; const dashboard = dashboardData; return ( <> {/* Dashboard Stats */} {dashboard && (

Resumen de Transacciones

Hoy
{dashboard.total_transactions_today}
{formatters.currency(dashboard.revenue_today)}
Esta Semana
{dashboard.total_transactions_this_week}
{formatters.currency(dashboard.revenue_this_week)}
Este Mes
{dashboard.total_transactions_this_month}
{formatters.currency(dashboard.revenue_this_month)}
)} {/* Transactions List */}

Transacciones Recientes

{summary && (
{summary.sync_status.synced} sincronizadas
{summary.sync_status.pending} pendientes
{summary.sync_status.failed > 0 && (
{summary.sync_status.failed} fallidas
)}
)}
{transactions.length === 0 ? (

No hay transacciones

Las transacciones sincronizadas desde tus sistemas POS aparecerán aquí

) : ( <> {/* Desktop Table View - Hidden on mobile */}
{transactions.map((transaction) => ( ))}
ID Transacción Fecha Total Método Pago Estado Sync Acciones
{transaction.external_transaction_id} {new Date(transaction.transaction_date).toLocaleString('es-ES', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', })} {formatters.currency(transaction.total_amount)} {transaction.payment_method || 'N/A'} {transaction.status} {transaction.is_synced_to_sales ? ( ) : ( )}
{/* Mobile Card View - Hidden on desktop */}
{transactions.map((transaction) => (
handleViewDetails(transaction.id)} > {/* Header Row */}
{transaction.external_transaction_id}
{new Date(transaction.transaction_date).toLocaleString('es-ES', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', })}
{transaction.is_synced_to_sales ? ( ) : ( )} {transaction.status}
{/* Amount and Payment */}
{formatters.currency(transaction.total_amount)}
{transaction.payment_method || 'N/A'}
{/* Items Count */} {transaction.items && transaction.items.length > 0 && (
{transaction.items.length} {transaction.items.length === 1 ? 'artículo' : 'artículos'}
)}
))}
{/* Pagination */} {transactionsData && (transactionsData.has_more || page > 0) && (
Página {page + 1}
)} )}
{/* Transaction Detail Modal */} {showDetailModal && (
{/* Modal Header */}

Detalles de Transacción

{/* Modal Content */}
{detailLoading ? (
) : selectedTransaction ? (
{/* Transaction Header */}
ID Transacción
{selectedTransaction.external_transaction_id}
{selectedTransaction.status}
Fecha
{new Date(selectedTransaction.transaction_date).toLocaleString('es-ES', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', })}
Sistema POS
{selectedTransaction.pos_system}
{/* Payment Information */}

Información de Pago

Método de pago {selectedTransaction.payment_method || 'N/A'}
Subtotal {formatters.currency(selectedTransaction.subtotal)}
Impuestos {formatters.currency(selectedTransaction.tax_amount)}
{selectedTransaction.discount_amount && parseFloat(String(selectedTransaction.discount_amount)) > 0 && (
Descuento -{formatters.currency(selectedTransaction.discount_amount)}
)} {selectedTransaction.tip_amount && parseFloat(String(selectedTransaction.tip_amount)) > 0 && (
Propina {formatters.currency(selectedTransaction.tip_amount)}
)}
Total {formatters.currency(selectedTransaction.total_amount)}
{/* Transaction Items */} {selectedTransaction.items && selectedTransaction.items.length > 0 && (

Artículos ({selectedTransaction.items.length})

{selectedTransaction.items.map((item) => (
{item.product_name}
{item.sku && (
SKU: {item.sku}
)}
{item.quantity} × {formatters.currency(item.unit_price)}
{formatters.currency(item.total_price)}
{item.is_synced_to_sales ? (
Sincronizado
) : (
Pendiente
)}
))}
)} {/* Sync Status */}
{selectedTransaction.is_synced_to_sales ? ( ) : ( )} Estado de Sincronización
{selectedTransaction.is_synced_to_sales ? ( <> Sincronizado exitosamente {selectedTransaction.sync_completed_at && ( {new Date(selectedTransaction.sync_completed_at).toLocaleString('es-ES')} )} ) : ( 'Pendiente de sincronización con sistema de ventas' )}
{selectedTransaction.sync_error && (
Error: {selectedTransaction.sync_error}
)}
) : (
No se encontraron detalles de la transacción
)}
{/* Modal Footer */}
)} ); }; const POSPage: React.FC = () => { const [cart, setCart] = useState([]); const [selectedCategory, setSelectedCategory] = useState('all'); const [showStats, setShowStats] = useState(false); // POS Configuration State const [showPosConfigModal, setShowPosConfigModal] = useState(false); const [selectedPosConfig, setSelectedPosConfig] = useState(null); const [posConfigMode, setPosConfigMode] = useState<'create' | 'edit'>('create'); const [testingConnection, setTestingConnection] = useState(null); const tenantId = useTenantId(); // POS Configuration hooks const posData = usePOSConfigurationData(tenantId); const posManager = usePOSConfigurationManager(tenantId); // Set initial POS mode based on whether there are configured integrations // Default to 'automatic' if POS configurations exist, otherwise 'manual' const [posMode, setPosMode] = useState<'manual' | 'automatic'>(() => { return posData.configurations.length > 0 ? 'automatic' : 'manual'; }); // Update posMode when configurations change (e.g., when first config is added) React.useEffect(() => { if (!posData.isLoading && posData.configurations.length > 0 && posMode === 'manual') { setPosMode('automatic'); } }, [posData.configurations.length, posData.isLoading]); // Fetch finished products from API const { data: ingredientsData, isLoading: productsLoading, error: productsError } = useIngredients(tenantId, { category: undefined, search: undefined }); // Filter for finished products and convert to POS format const products = useMemo(() => { if (!ingredientsData) return []; return ingredientsData .filter(ingredient => ingredient.product_type === ProductType.FINISHED_PRODUCT) .map(ingredient => ({ id: ingredient.id, name: ingredient.name, price: Number(ingredient.average_cost) || 0, category: ingredient.category?.toLowerCase() || 'uncategorized', stock: Number(ingredient.current_stock) || 0, ingredient: ingredient })) .filter(product => product.stock > 0); }, [ingredientsData]); // Generate categories from actual product data with bakery colors const categories = useMemo(() => { const categoryMap = new Map(); categoryMap.set('all', { id: 'all', name: 'Todos', color: bakeryColors.flour, icon: '🛍️' }); // Define category colors from bakery palette const categoryColors: Record = { bread: bakeryColors.sourdough, pastry: bakeryColors.brioche, cake: bakeryColors.strawberry, cookie: bakeryColors.caramel, beverage: bakeryColors.espresso, default: bakeryColors.wheat }; const categoryIcons: Record = { bread: '🍞', pastry: '🥐', cake: '🎂', cookie: '🍪', beverage: '☕', default: '📦' }; products.forEach(product => { if (!categoryMap.has(product.category)) { const categoryName = product.category.charAt(0).toUpperCase() + product.category.slice(1); categoryMap.set(product.category, { id: product.category, name: categoryName, color: categoryColors[product.category] || categoryColors.default, icon: categoryIcons[product.category] || categoryIcons.default }); } }); return Array.from(categoryMap.values()); }, [products]); // Load POS configurations function for refetching after updates const loadPosConfigurations = () => { console.log('POS configurations updated, consider implementing refetch if needed'); }; const filteredProducts = useMemo(() => { return products.filter(product => selectedCategory === 'all' || product.category === selectedCategory ); }, [products, selectedCategory]); // POS Configuration Handlers const handleAddPosConfiguration = () => { setSelectedPosConfig(null); setPosConfigMode('create'); setShowPosConfigModal(true); }; const handleEditPosConfiguration = (config: POSConfiguration) => { setSelectedPosConfig(config); setPosConfigMode('edit'); setShowPosConfigModal(true); }; const handlePosConfigSuccess = () => { loadPosConfigurations(); setShowPosConfigModal(false); }; const handleTestPosConnection = async (configId: string) => { try { setTestingConnection(configId); const response = await posService.testPOSConnection({ tenant_id: tenantId, config_id: configId, }); if (response.success) { showToast.success('Conexión exitosa'); } else { showToast.error(`Error en la conexión: ${response.message || 'Error desconocido'}`); } } catch (error) { showToast.error('Error al probar la conexión'); } finally { setTestingConnection(null); } }; const handleDeletePosConfiguration = async (configId: string) => { if (!window.confirm('¿Estás seguro de que deseas eliminar esta configuración?')) { return; } try { await posService.deletePOSConfiguration({ tenant_id: tenantId, config_id: configId, }); showToast.success('Configuración eliminada correctamente'); loadPosConfigurations(); } catch (error) { showToast.error('Error al eliminar la configuración'); } }; const addToCart = (product: typeof products[0]) => { setCart(prevCart => { const existingItem = prevCart.find(item => item.id === product.id); if (existingItem) { if (existingItem.quantity >= product.stock) { return prevCart; } return prevCart.map(item => item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item ); } else { return [...prevCart, { id: product.id, name: product.name, price: product.price, quantity: 1, category: product.category, stock: product.stock }]; } }); }; const updateQuantity = (id: string, quantity: number) => { if (quantity <= 0) { setCart(prevCart => prevCart.filter(item => item.id !== id)); } else { setCart(prevCart => prevCart.map(item => { if (item.id === id) { const maxQuantity = Math.min(quantity, item.stock); return { ...item, quantity: maxQuantity }; } return item; }) ); } }; const clearCart = () => { setCart([]); }; const subtotal = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); const taxRate = 0.21; const tax = subtotal * taxRate; const total = subtotal + tax; const processPayment = (paymentData: any) => { if (cart.length === 0) return; console.log('Processing payment:', { cart, ...paymentData, total, }); setCart([]); showToast.success('Venta procesada exitosamente'); }; // Loading and error states if (productsLoading || !tenantId) { return (
); } if (productsError) { return (

Error al cargar productos

{productsError.message || 'Ha ocurrido un error inesperado'}

); } return (
{/* POS Mode Toggle */}

Modo de Operación

{posMode === 'manual' ? 'Sistema POS manual interno - gestiona las ventas directamente desde la aplicación' : 'Integración automática con sistemas POS externos - sincroniza datos automáticamente' }

Manual Automático
{posMode === 'manual' ? ( <> {/* Main 2-Column Layout */}
{/* Left Column: Products (2/3 width on desktop) */}
{/* Category Pills with Bakery Colors */}
{categories.map(category => ( ))}
{/* Products Grid - Large Touch-Friendly Cards */}
{filteredProducts.map(product => { const cartItem = cart.find(item => item.id === product.id); const cartQuantity = cartItem?.quantity || 0; return ( addToCart(product)} /> ); })}
{/* Empty State */} {filteredProducts.length === 0 && (

No hay productos disponibles

{selectedCategory === 'all' ? 'No hay productos en stock en este momento' : `No hay productos en la categoría "${categories.find(c => c.id === selectedCategory)?.name}"` }

)}
{/* Right Column: Cart & Payment (1/3 width on desktop) */}
{/* Cart Component */} {/* Payment Component */}
) : ( /* Automatic POS Integration Section with StatusCard */

Sistemas POS Integrados

Gestiona tus sistemas POS externos y configuraciones de integración

{posData.isLoading ? (
) : posData.configurations.length === 0 ? (

No hay sistemas POS configurados

Configura tu primer sistema POS para comenzar a sincronizar datos de ventas.

) : (
{posData.configurations.map(config => { const provider = posData.supportedSystems.find(p => p.id === config.pos_system); const isConnected = config.is_connected && config.is_active; return ( handleTestPosConnection(config.id), priority: 'secondary' as const, disabled: testingConnection === config.id, }, { label: 'Editar', icon: Settings, onClick: () => handleEditPosConfiguration(config), priority: 'secondary' as const, }, { label: 'Eliminar', icon: Trash2, onClick: () => handleDeletePosConfiguration(config.id), priority: 'tertiary' as const, destructive: true, }, ]} /> ); })}
)}
{/* Transactions Section - Only show if there are configurations */} {posData.configurations.length > 0 && ( )}
)} {/* POS Configuration Modal */} setShowPosConfigModal(false)} tenantId={tenantId} onSuccess={handlePosConfigSuccess} existingConfig={selectedPosConfig} mode={posConfigMode} />
); }; export default POSPage;