Instantly share code, notes, and snippets.
Created
November 3, 2025 19:13
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save iDavidMorales/fc50a18a6164dcc3338e3636c18e1425 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| // Configuración centralizada del negocio | |
| // Centralized business configuration | |
| $config = [ | |
| 'store_name' => 'Wings and Fries', | |
| 'phone_number' => '228-121-1450', | |
| 'whatsapp_number' => '522281211450', | |
| 'email' => '[email protected]', | |
| 'address' => 'Desiderio Pavón 6 Martires de Chicago', | |
| 'logo_url' => 'https://i.imgur.com/XJjAOrE.jpeg', | |
| 'hours' => [ | |
| // Horario de atención por día en formato 24h | |
| // Open hours per day in 24h format | |
| 'monday' => ['open' => '11:00', 'close' => '22:00'], | |
| 'tuesday' => ['open' => '11:00', 'close' => '22:00'], | |
| 'wednesday' => ['open' => '11:00', 'close' => '22:00'], | |
| 'thursday' => ['open' => '11:00', 'close' => '22:00'], | |
| 'friday' => ['open' => '11:00', 'close' => '22:00'], | |
| 'saturday' => ['open' => '11:00', 'close' => '22:00'], | |
| 'sunday' => ['open' => '00:00', 'close' => '00:00'], // Horario '00:00' significa cerrado | |
| // '00:00' hours means closed | |
| ], | |
| ]; | |
| ?> | |
| <!DOCTYPE html> | |
| <html lang="es"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title><?php echo htmlspecialchars($config['store_name']); ?> - Menú Digital</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| /* Custom styles for the scrollbar to match the dark theme */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #111827; /* Dark background */ | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background-color: #f97316; /* Orange color */ | |
| border-radius: 4px; | |
| } | |
| /* Animation for the cart button pulse effect */ | |
| .pulse-once { | |
| animation: pulse 0.5s cubic-bezier(0.4, 0, 0.6, 1) 1; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { | |
| transform: scale(1); | |
| } | |
| 50% { | |
| transform: scale(1.1); | |
| } | |
| } | |
| /* Preloader styles */ | |
| #preloader { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: #111827; /* Dark background */ | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 9999; /* Make sure it's on top of everything */ | |
| transition: opacity 0.5s ease-in-out; | |
| } | |
| .preloader-icon { | |
| width: 150px; | |
| height: 150px; | |
| border-radius: 50%; /* Make the image round */ | |
| animation: spin 2s linear infinite; /* Spin animation for the icon */ | |
| } | |
| /* Spin animation for the preloader icon */ | |
| @keyframes spin { | |
| from { | |
| transform: rotate(0deg); | |
| } | |
| to { | |
| transform: rotate(360deg); | |
| } | |
| } | |
| /* Hide the preloader with opacity and pointer events */ | |
| .fade-out { | |
| opacity: 0; | |
| pointer-events: none; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-gray-100 min-h-screen"> | |
| <div id="preloader"> | |
| <img src="<?php echo htmlspecialchars($config['logo_url']); ?>" alt="Loading" class="preloader-icon"> | |
| <p class="mt-4 text-gray-400 text-lg">Cargando...</p> | |
| </div> | |
| <div id="main-content" class="hidden"> | |
| <header class="py-6 px-4 sm:px-8 text-center"> | |
| <img src="<?php echo htmlspecialchars($config['logo_url']); ?>" alt="<?php echo htmlspecialchars($config['store_name']); ?> Logo" class="h-24 w-24 rounded-full mx-auto mb-4 border-2 border-orange-500 shadow-lg"> | |
| <h1 class="text-4xl sm:text-5xl font-extrabold text-orange-500 tracking-wider"> | |
| <?php echo htmlspecialchars($config['store_name']); ?> | |
| </h1> | |
| <p class="mt-2 text-xl sm:text-2xl text-orange-200"> | |
| ¡Descubre nuestras deliciosas opciones!<br> | |
| Visitanos en <?php echo htmlspecialchars($config['address']); ?> | |
| </p> | |
| <div class="mt-4 flex flex-col items-center justify-center"> | |
| <p class="text-lg text-gray-300"> | |
| Teléfono: <span class="font-bold text-orange-300"><?php echo htmlspecialchars($config['phone_number']); ?></span> | |
| </p> | |
| <p class="text-lg text-gray-300"> | |
| Horario: | |
| <span class="font-bold">Lunes a Sábado</span> | |
| <span class="text-sm">(11:00 AM - 10:00 PM)</span> | |
| </p> | |
| <div class="flex items-center space-x-2 mt-1"> | |
| <div id="status-dot" class="w-3 h-3 rounded-full"></div> | |
| <span id="store-status" class="text-lg font-semibold"></span> | |
| </div> | |
| </div> | |
| </header> | |
| <div class="px-4 sm:px-8 max-w-6xl mx-auto mb-6"> | |
| <div class="relative"> | |
| <input type="text" id="search-input" placeholder="Buscar productos..." class="w-full pl-12 pr-4 py-3 bg-gray-800 text-gray-200 placeholder-gray-400 rounded-xl focus:outline-none focus:ring-2 focus:ring-orange-500 transition-colors"> | |
| <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> | |
| <svg class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> | |
| </svg> | |
| </div> | |
| </div> | |
| </div> | |
| <main id="menu-container" class="p-4 max-w-6xl mx-auto grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> | |
| <div id="loading-spinner" class="col-span-1 sm:col-span-2 lg:col-span-3 text-center p-8"> | |
| <div class="flex flex-col items-center justify-center"> | |
| <svg class="animate-spin -ml-1 mr-3 h-10 w-10 text-orange-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> | |
| <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> | |
| <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> | |
| </svg> | |
| <p class="mt-4 text-gray-400 text-lg">Cargando menú...</p> | |
| </div> | |
| </div> | |
| </main> | |
| <button id="open-cart-btn" class="fixed bottom-6 right-6 p-4 bg-orange-500 hover:bg-orange-600 text-white rounded-full shadow-lg transition-transform transform hover:scale-110 focus:outline-none focus:ring-4 focus:ring-orange-500 focus:ring-opacity-50 z-50"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" viewBox="0 0 24 24" fill="currentColor"> | |
| <path d="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zm10 0c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2zm-9.35-1.55l.85-1.13.11-.15c.16-.23.4-.37.66-.37h10.74c.48 0 .85.38.93.85L21.49 14.8c.18.9-.45 1.7-1.35 1.7H8.57c-.52 0-.96-.32-1.14-.8L4.2 4.41C4.05 3.79 3.44 3.4 2.81 3.4H1c-.55 0-1 .45-1 1s.45 1 1 1h1.56L5.27 15.65c.18.52.62.85 1.14.85h11.23c.52 0 .96-.32 1.14-.8L18.89 10H8.91c-.55 0-1-.45-1-1s.45-1 1-1h10.9c.55 0 1 .45 1 1v1.16l-3.35 5.25c-.18.28-.48.45-.82.45H7.65c-.34 0-.65-.17-.82-.45L5.7 12.16zM7.5 14h10.36L16 9H9l-1.5 5z"/> | |
| </svg> | |
| <span id="cart-counter" class="absolute top-0 right-0 transform translate-x-1/2 -translate-y-1/2 bg-red-500 text-white text-sm font-bold rounded-full h-6 w-6 flex items-center justify-center">0</span> | |
| </button> | |
| <div id="cart-modal" class="fixed inset-0 bg-black bg-opacity-70 hidden items-center justify-center p-4 z-[100]"> | |
| <div class="bg-gray-800 rounded-xl shadow-xl p-6 w-full max-w-xl relative transition-all duration-300 transform scale-95 opacity-0"> | |
| <button id="close-cart-btn" class="absolute top-3 right-3 text-gray-400 hover:text-gray-100 text-3xl transition-colors"> | |
| × | |
| </button> | |
| <h2 class="text-3xl font-bold mb-4 text-center text-orange-400">Tu pedido</h2> | |
| <div id="cart-items" class="max-h-[50vh] overflow-y-auto mb-4 border-b border-gray-700 pb-4 space-y-3"> | |
| <p class="text-center text-gray-400 text-base">El carrito está vacío.</p> | |
| </div> | |
| <div class="flex justify-between items-center text-2xl font-semibold mb-6"> | |
| <span>Total:</span> | |
| <span id="cart-total">$0.00</span> | |
| </div> | |
| <p class="text-center text-gray-400 text-sm mb-4"> | |
| * Todos los pedidos son + envío de ser necesario | |
| </p> | |
| <div class="flex flex-col space-y-4"> | |
| <button id="send-whatsapp-btn" class="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-6 rounded-xl transition-colors shadow-lg text-xl"> | |
| Enviar pedido por WhatsApp | |
| </button> | |
| <button id="send-email-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-xl transition-colors shadow-lg text-xl"> | |
| Enviar pedidos por correo electrónico | |
| </button> | |
| <button id="prepare-order-btn" class="w-full bg-orange-600 hover:bg-orange-700 text-white font-bold py-3 px-6 rounded-xl transition-colors shadow-lg text-xl"> | |
| Preparar orden | |
| </button> | |
| <button id="clear-cart-btn" class="w-full bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-xl transition-colors shadow-lg text-xl"> | |
| Vaciar carrito | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="confirmation-modal" class="fixed inset-0 bg-black bg-opacity-70 hidden items-center justify-center p-4 z-[100]"> | |
| <div class="bg-gray-800 rounded-xl shadow-xl p-6 w-full max-w-xl relative transition-all duration-300 transform scale-95 opacity-0 text-center"> | |
| <h2 class="text-3xl font-bold mb-4 text-orange-400">¡Pedido Enviado!</h2> | |
| <div id="confirmation-details" class="text-left space-y-2 text-lg text-gray-300"> | |
| <p><strong>Número de Orden:</strong> <span id="order-number-display" class="font-bold"></span></p> | |
| <p><strong>Fecha:</strong> <span id="order-date-display"></span></p> | |
| <p><strong>Hora:</strong> <span id="order-time-display"></span></p> | |
| <p class="mt-4 text-green-400">Tu pedido ha sido registrado con éxito. ¡Pronto estará listo!</p> | |
| </div> | |
| <button id="close-confirmation-btn" class="mt-6 w-full bg-orange-500 hover:bg-orange-600 text-white font-bold py-3 px-6 rounded-xl transition-colors shadow-lg"> | |
| Cerrar | |
| </button> | |
| </div> | |
| </div> | |
| <div id="toast-notification" class="fixed top-6 left-1/2 transform -translate-x-1/2 bg-gray-800 text-green-400 px-6 py-3 rounded-xl shadow-lg transition-all duration-300 ease-out opacity-0 translate-y-4 z-[200]"> | |
| <span id="toast-message" class="text-lg"></span> | |
| </div> | |
| </div> <center> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // PHP configuration is passed to JavaScript | |
| const config = <?php echo json_encode($config); ?>; | |
| const daysOfWeek = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; | |
| // Global variable for menu data | |
| let menuData = []; | |
| // State variables | |
| let cart = []; | |
| let total = 0; | |
| let totalItems = 0; | |
| // DOM elements | |
| const preloader = document.getElementById('preloader'); | |
| const mainContent = document.getElementById('main-content'); | |
| const menuContainer = document.getElementById('menu-container'); | |
| const loadingSpinner = document.getElementById('loading-spinner'); | |
| const searchInput = document.getElementById('search-input'); | |
| const cartModal = document.getElementById('cart-modal'); | |
| const openCartBtn = document.getElementById('open-cart-btn'); | |
| const closeCartBtn = document.getElementById('close-cart-btn'); | |
| const cartItemsContainer = document.getElementById('cart-items'); | |
| const cartTotalEl = document.getElementById('cart-total'); | |
| const sendWhatsappBtn = document.getElementById('send-whatsapp-btn'); | |
| const sendEmailBtn = document.getElementById('send-email-btn'); | |
| const prepareOrderBtn = document.getElementById('prepare-order-btn'); | |
| const clearCartBtn = document.getElementById('clear-cart-btn'); | |
| const cartCounterEl = document.getElementById('cart-counter'); | |
| const toastNotificationEl = document.getElementById('toast-notification'); | |
| const toastMessageEl = document.getElementById('toast-message'); | |
| // Store status elements | |
| const statusDot = document.getElementById('status-dot'); | |
| const storeStatusEl = document.getElementById('store-status'); | |
| // NEW: Confirmation Modal elements | |
| const confirmationModal = document.getElementById('confirmation-modal'); | |
| const closeConfirmationBtn = document.getElementById('close-confirmation-btn'); | |
| const orderNumberDisplay = document.getElementById('order-number-display'); | |
| const orderDateDisplay = document.getElementById('order-date-display'); | |
| const orderTimeDisplay = document.getElementById('order-time-display'); | |
| // Function to check and display the store's open/closed status using the config | |
| function checkStoreStatus() { | |
| const now = new Date(); | |
| const dayIndex = now.getDay(); | |
| const currentDay = daysOfWeek[dayIndex]; | |
| const currentHour = now.getHours(); | |
| const currentMinute = now.getMinutes(); | |
| const hours = config.hours[currentDay]; | |
| // Convert open and close times to minutes for easy comparison | |
| const openTimeInMinutes = parseInt(hours.open.split(':')[0]) * 60 + parseInt(hours.open.split(':')[1]); | |
| const closeTimeInMinutes = parseInt(hours.close.split(':')[0]) * 60 + parseInt(hours.close.split(':')[1]); | |
| const currentTimeInMinutes = currentHour * 60 + currentMinute; | |
| // Check if the store is open | |
| const isOpen = (currentTimeInMinutes >= openTimeInMinutes && currentTimeInMinutes < closeTimeInMinutes); | |
| if (isOpen) { | |
| // Open | |
| statusDot.className = 'w-3 h-3 rounded-full bg-green-500'; | |
| storeStatusEl.textContent = 'Abierto'; | |
| storeStatusEl.className = 'text-lg font-semibold text-green-500'; | |
| } else { | |
| // Closed | |
| statusDot.className = 'w-3 h-3 rounded-full bg-red-500'; | |
| storeStatusEl.textContent = 'Cerrado'; | |
| storeStatusEl.className = 'text-lg font-semibold text-red-500'; | |
| } | |
| } | |
| // Function to render menu items | |
| function renderMenu(productsToRender) { | |
| // Clear the container | |
| menuContainer.innerHTML = ''; | |
| const products = productsToRender || menuData; | |
| if (products.length === 0) { | |
| menuContainer.innerHTML = ` | |
| <div class="col-span-1 sm:col-span-2 lg:col-span-3 text-center p-8"> | |
| <p class="text-gray-400 text-lg">No se encontraron productos para mostrar.</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| products.forEach(item => { | |
| // Find the current quantity in the cart or start at 0 | |
| const cartItem = cart.find(i => i.id === item.id); | |
| const currentQuantity = cartItem ? cartItem.quantity : 0; | |
| const card = document.createElement('div'); | |
| card.className = 'bg-gray-800 rounded-2xl shadow-xl overflow-hidden transform hover:scale-105 transition-transform duration-300 ease-in-out cursor-pointer'; | |
| card.innerHTML = ` | |
| <img src="${item.image}" alt="${item.name}" class="w-full h-48 object-cover" onerror="this.src='${item.fallbackImage}'"> | |
| <div class="p-4"> | |
| <h3 class="text-2xl font-bold text-orange-400 mb-1">${item.name}</h3> | |
| <p class="text-gray-300 text-base mb-3">${item.description}</p> | |
| <div class="flex items-center justify-between"> | |
| <span class="text-xl font-bold text-white">$${item.price.toFixed(2)}</span> | |
| <div class="flex items-center space-x-1" data-id="${item.id}"> | |
| <button class="quantity-btn-minus bg-gray-700 hover:bg-gray-600 text-white text-2xl font-bold w-10 h-10 rounded-full shadow-md transition-colors transform active:scale-95"> | |
| - | |
| </button> | |
| <span class="quantity-display text-lg font-semibold w-10 text-center">${currentQuantity}</span> | |
| <button class="quantity-btn-plus bg-orange-500 hover:bg-orange-600 text-white text-2xl font-bold w-10 h-10 rounded-full shadow-md transition-colors transform active:scale-95"> | |
| + | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| menuContainer.appendChild(card); | |
| }); | |
| } | |
| // Function to fetch menu data from the server | |
| async function fetchMenuData() { | |
| try { | |
| const response = await fetch('https://routicket.com/menu/dataMenu.php?user_id=1184'); | |
| if (!response.ok) { | |
| throw new Error('Error al cargar los productos'); | |
| } | |
| menuData = await response.json(); | |
| renderMenu(menuData); | |
| loadingSpinner.style.display = 'none'; // Hide the original spinner | |
| // Delay hiding the preloader to ensure it shows for at least 5 seconds | |
| setTimeout(() => { | |
| preloader.classList.add('fade-out'); | |
| setTimeout(() => { | |
| preloader.style.display = 'none'; | |
| mainContent.classList.remove('hidden'); | |
| }, 500); // Wait for the fade-out animation to finish | |
| }, 5000); // 5000 milliseconds = 5 seconds | |
| } catch (error) { | |
| console.error('Error:', error); | |
| loadingSpinner.innerHTML = ` | |
| <div class="col-span-1 sm:col-span-2 lg:col-span-3 text-center p-8"> | |
| <p class="text-red-400 text-lg">Ocurrió un error al cargar el menú. Por favor, intenta de nuevo más tarde.</p> | |
| </div> | |
| `; | |
| // Also hide the preloader on error after 5 seconds | |
| setTimeout(() => { | |
| preloader.classList.add('fade-out'); | |
| setTimeout(() => { | |
| preloader.style.display = 'none'; | |
| mainContent.classList.remove('hidden'); | |
| }, 500); | |
| }, 5000); | |
| } | |
| } | |
| // Function to update the cart and refresh the UI | |
| function updateCart(itemId, quantityChange) { | |
| const existingItemIndex = cart.findIndex(item => item.id === itemId); | |
| const menuItem = menuData.find(item => item.id === itemId); | |
| if (!menuItem) return; | |
| if (existingItemIndex > -1) { | |
| cart[existingItemIndex].quantity += quantityChange; | |
| } else if (quantityChange > 0) { | |
| cart.push({ ...menuItem, quantity: quantityChange }); | |
| } | |
| // Filter out items with a quantity of 0 or less | |
| cart = cart.filter(item => item.quantity > 0); | |
| // Recalculate total and number of items | |
| total = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); | |
| totalItems = cart.reduce((sum, item) => sum + item.quantity, 0); | |
| renderCart(); | |
| updateCartCounter(); | |
| // Update the quantity on the menu card | |
| const card = menuContainer.querySelector(`[data-id="${itemId}"]`); | |
| if (card) { | |
| const quantityDisplay = card.querySelector('.quantity-display'); | |
| const cartItem = cart.find(i => i.id === itemId); | |
| quantityDisplay.textContent = cartItem ? cartItem.quantity : 0; | |
| } | |
| } | |
| // Function to update the cart counter badge | |
| function updateCartCounter() { | |
| cartCounterEl.textContent = totalItems; | |
| if (totalItems > 0) { | |
| cartCounterEl.classList.remove('hidden'); | |
| cartCounterEl.classList.add('flex'); | |
| openCartBtn.classList.add('pulse-once'); | |
| } else { | |
| cartCounterEl.classList.add('hidden'); | |
| cartCounterEl.classList.remove('flex'); | |
| } | |
| } | |
| // Function to render the cart modal content | |
| function renderCart() { | |
| cartItemsContainer.innerHTML = ''; | |
| if (cart.length === 0) { | |
| cartItemsContainer.innerHTML = '<p class="text-center text-gray-400 py-4 text-base">El carrito está vacío.</p>'; | |
| } else { | |
| cart.forEach(item => { | |
| const cartItemEl = document.createElement('div'); | |
| cartItemEl.className = 'flex items-center justify-between py-3 border-b border-gray-700'; | |
| cartItemEl.innerHTML = ` | |
| <div class="flex-1"> | |
| <span class="text-lg font-semibold text-gray-200">${item.name}</span> | |
| <p class="text-gray-400">Cantidad: ${item.quantity}</p> | |
| </div> | |
| <div class="flex items-center space-x-3"> | |
| <span class="text-lg font-bold text-orange-400">$${(item.price * item.quantity).toFixed(2)}</span> | |
| <button class="remove-from-cart-btn bg-red-600 hover:bg-red-700 text-white text-sm font-bold w-8 h-8 rounded-full shadow-md transition-colors transform active:scale-95" data-id="${item.id}"> | |
| × | |
| </button> | |
| </div> | |
| `; | |
| cartItemsContainer.appendChild(cartItemEl); | |
| }); | |
| // NEW: Add a listener to the remove buttons | |
| cartItemsContainer.querySelectorAll('.remove-from-cart-btn').forEach(button => { | |
| button.addEventListener('click', (e) => { | |
| const itemId = parseInt(e.target.dataset.id); | |
| // Set quantity change to a negative value to remove the item from the cart | |
| const itemToRemove = cart.find(i => i.id === itemId); | |
| if (itemToRemove) { | |
| updateCart(itemId, -itemToRemove.quantity); | |
| showToast(`${itemToRemove.name} eliminado del carrito.`); | |
| } | |
| }); | |
| }); | |
| } | |
| cartTotalEl.textContent = `$${total.toFixed(2)}`; | |
| } | |
| // Function to show a toast notification message | |
| function showToast(message) { | |
| toastMessageEl.textContent = message; | |
| toastNotificationEl.classList.remove('opacity-0', 'translate-y-4'); | |
| toastNotificationEl.classList.add('opacity-100', 'translate-y-0'); | |
| setTimeout(() => { | |
| toastNotificationEl.classList.remove('opacity-100', 'translate-y-0'); | |
| toastNotificationEl.classList.add('opacity-0', 'translate-y-4'); | |
| }, 3000); // Hide after 3 seconds | |
| } | |
| // Function to build the order message for sharing | |
| function buildOrderMessage() { | |
| if (cart.length === 0) { | |
| return null; | |
| } | |
| let message = "¡Hola! Me gustaría hacer el siguiente pedido:\n\n"; | |
| cart.forEach(item => { | |
| message += `- ${item.name} (${item.quantity} unidad${item.quantity > 1 ? 'es' : ''}) - $${(item.price * item.quantity).toFixed(2)}\n`; | |
| }); | |
| message += `\nTotal: $${total.toFixed(2)}\n\n`; | |
| return message; | |
| } | |
| // Function to send the order via WhatsApp | |
| function sendWhatsApp() { | |
| const message = buildOrderMessage(); | |
| if (!message) { | |
| showToast("El carrito está vacío. Agrega productos antes de enviar el pedido."); | |
| return; | |
| } | |
| const encodedMessage = encodeURIComponent(message); | |
| const phoneNumber = config.whatsapp_number; // Use phone number from config | |
| const whatsappUrl = `https://wa.me/${phoneNumber}?text=${encodedMessage}`; | |
| window.open(whatsappUrl, '_blank'); | |
| } | |
| // Function to send the order via email | |
| function sendEmail() { | |
| const message = buildOrderMessage(); | |
| if (!message) { | |
| showToast("El carrito está vacío. Agrega productos antes de enviar el pedido."); | |
| return; | |
| } | |
| const subject = "Nuevo Pedido de Menú Digital"; | |
| const body = message + "Espero tu confirmación. ¡Gracias!"; | |
| const emailAddress = config.email; // Use email from config | |
| const mailtoUrl = `mailto:${emailAddress}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; | |
| window.open(mailtoUrl, '_blank'); | |
| } | |
| // NEW: Function to send the order to the server for registration | |
| async function sendOrderToServer() { | |
| if (cart.length === 0) { | |
| showToast("El carrito está vacío. Agrega productos antes de finalizar el pedido."); | |
| return; | |
| } | |
| // Close the cart modal | |
| const modalContent = cartModal.querySelector('div'); | |
| modalContent.classList.remove('scale-100', 'opacity-100'); | |
| modalContent.classList.add('scale-95', 'opacity-0'); | |
| setTimeout(() => { | |
| cartModal.classList.remove('flex'); | |
| cartModal.classList.add('hidden'); | |
| }, 300); | |
| showToast("Enviando pedido..."); | |
| // Construct the payload for the server | |
| const payload = { | |
| cart: cart.map(item => ({ | |
| id: item.id, | |
| name: item.name, | |
| quantity: item.quantity, | |
| price: item.price | |
| })), | |
| total: total, | |
| tienda: { | |
| id: 1184, // This ID must match the dataMenu.php URL param | |
| user_id: 1184, // This ID must match the dataMenu.php URL param | |
| nombre: config.store_name, | |
| telefono: config.phone_number | |
| }, | |
| // Optional user information (could be gathered via a form) | |
| userName: '0', | |
| userPhone: '0', | |
| userEmail: '0' | |
| }; | |
| try { | |
| const response = await fetch('registrar_orden.php', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify(payload) | |
| }); | |
| const result = await response.json(); | |
| if (result.status === 'success') { | |
| // Display confirmation modal | |
| orderNumberDisplay.textContent = result.order_number; | |
| orderDateDisplay.textContent = result.order_date; | |
| orderTimeDisplay.textContent = result.order_time; | |
| confirmationModal.classList.remove('hidden'); | |
| confirmationModal.classList.add('flex'); | |
| setTimeout(() => { | |
| const modalContent = confirmationModal.querySelector('div'); | |
| modalContent.classList.remove('scale-95', 'opacity-0'); | |
| modalContent.classList.add('scale-100', 'opacity-100'); | |
| }, 10); | |
| // Clear the cart after successful order | |
| cart = []; | |
| total = 0; | |
| totalItems = 0; | |
| renderMenu(); // Refresh the menu to reset counters | |
| updateCartCounter(); | |
| } else { | |
| showToast(`Error: ${result.message}`); | |
| } | |
| } catch (error) { | |
| console.error('Error al enviar la orden:', error); | |
| showToast("Error al procesar el pedido. Por favor, intenta de nuevo."); | |
| } | |
| } | |
| // Event listeners | |
| // Listen for clicks on the main menu container to handle quantity buttons | |
| menuContainer.addEventListener('click', (e) => { | |
| const plusBtn = e.target.closest('.quantity-btn-plus'); | |
| const minusBtn = e.target.closest('.quantity-btn-minus'); | |
| if (plusBtn) { | |
| const itemId = parseInt(plusBtn.closest('[data-id]').dataset.id); | |
| updateCart(itemId, 1); | |
| const menuItem = menuData.find(item => item.id === itemId); | |
| if (menuItem) { | |
| showToast(`${menuItem.name} agregado al carrito.`); | |
| } | |
| } else if (minusBtn) { | |
| const itemId = parseInt(minusBtn.closest('[data-id]').dataset.id); | |
| // Only update if the quantity is greater than 0 | |
| const cartItem = cart.find(i => i.id === itemId); | |
| if (cartItem && cartItem.quantity > 0) { | |
| updateCart(itemId, -1); | |
| const menuItem = menuData.find(item => item.id === itemId); | |
| if (menuItem) { | |
| showToast(`${menuItem.name} eliminado del carrito.`); | |
| } | |
| } else { | |
| showToast("No hay elementos para eliminar."); | |
| } | |
| } | |
| }); | |
| // Listen for input on the search bar | |
| searchInput.addEventListener('input', (e) => { | |
| const searchTerm = e.target.value.toLowerCase(); | |
| const filteredProducts = menuData.filter(item => { | |
| return item.name.toLowerCase().includes(searchTerm) || | |
| item.description.toLowerCase().includes(searchTerm); | |
| }); | |
| renderMenu(filteredProducts); | |
| }); | |
| // Open the cart modal | |
| openCartBtn.addEventListener('click', () => { | |
| cartModal.classList.remove('hidden'); | |
| cartModal.classList.add('flex'); | |
| setTimeout(() => { | |
| const modalContent = cartModal.querySelector('div'); | |
| modalContent.classList.remove('scale-95', 'opacity-0'); | |
| modalContent.classList.add('scale-100', 'opacity-100'); | |
| }, 10); | |
| renderCart(); | |
| }); | |
| // Close the cart modal | |
| closeCartBtn.addEventListener('click', () => { | |
| const modalContent = cartModal.querySelector('div'); | |
| modalContent.classList.remove('scale-100', 'opacity-100'); | |
| modalContent.classList.add('scale-95', 'opacity-0'); | |
| setTimeout(() => { | |
| cartModal.classList.remove('flex'); | |
| cartModal.classList.add('hidden'); | |
| }, 300); | |
| }); | |
| // Clear the cart | |
| clearCartBtn.addEventListener('click', () => { | |
| cart = []; | |
| total = 0; | |
| totalItems = 0; | |
| renderCart(); | |
| renderMenu(); // Refresh the menu to reset counters | |
| updateCartCounter(); | |
| showToast("El carrito ha sido vaciado."); | |
| }); | |
| // Set up listeners for the action buttons | |
| sendWhatsappBtn.addEventListener('click', sendWhatsApp); | |
| sendEmailBtn.addEventListener('click', sendEmail); | |
| prepareOrderBtn.addEventListener('click', sendOrderToServer); // Use the new function | |
| // Close the confirmation modal | |
| closeConfirmationBtn.addEventListener('click', () => { | |
| const modalContent = confirmationModal.querySelector('div'); | |
| modalContent.classList.remove('scale-100', 'opacity-100'); | |
| modalContent.classList.add('scale-95', 'opacity-0'); | |
| setTimeout(() => { | |
| confirmationModal.classList.remove('flex'); | |
| confirmationModal.classList.add('hidden'); | |
| }, 300); | |
| }); | |
| // Close the modal if the user clicks outside the content area | |
| cartModal.addEventListener('click', (e) => { | |
| if (e.target === cartModal) { | |
| const modalContent = cartModal.querySelector('div'); | |
| modalContent.classList.remove('scale-100', 'opacity-100'); | |
| modalContent.classList.add('scale-95', 'opacity-0'); | |
| setTimeout(() => { | |
| cartModal.classList.remove('flex'); | |
| cartModal.classList.add('hidden'); | |
| }, 300); | |
| } | |
| }); | |
| // Close confirmation modal if user clicks outside | |
| confirmationModal.addEventListener('click', (e) => { | |
| if (e.target === confirmationModal) { | |
| const modalContent = confirmationModal.querySelector('div'); | |
| modalContent.classList.remove('scale-100', 'opacity-100'); | |
| modalContent.classList.add('scale-95', 'opacity-0'); | |
| setTimeout(() => { | |
| confirmationModal.classList.remove('flex'); | |
| confirmationModal.classList.add('hidden'); | |
| }, 300); | |
| } | |
| }); | |
| // Initial setup | |
| fetchMenuData(); // Call the function to load menu data | |
| updateCartCounter(); | |
| checkStoreStatus(); // Check and display store status on load | |
| setInterval(checkStoreStatus, 60000); // Check every minute to keep the status updated | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| <p><a style="color:#1F1F1F" target="_blank" href="https://routicket.com/menu/admin/lista-productos.php"> Admin </a></p> | |
| </center><br><br><br> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment