//--- Imports --- import { getOnlineUserCount, getUserList } from './service/chat.js'; import { getProducts, sendOrder } from './service/product.js'; import { login, existsTable } from './service/auth.js'; import { createGlobalLoader, showGlobalLoader, hideGlobalLoader } from './utils/loader.js'; import { updateProgress, claimReward, getProgress } from './utils/progressBar.js'; import { showError } from './utils/error.js'; import { addHistoryRow, setupShoppingCart } from './utils/shoppingCart.js'; import { hideGUI, showGUI } from './utils/gui.js'; import { smartSearch } from './utils/searching.js'; import { getUserData } from './service/user.js'; import { getComment, COMMENT_TYPES } from './utils/get_comment.js'; import { imageObserver } from './utils/observer.js'; // --- Variables Globales --- // --- User Data --- let userId = -1; let userName = "Cliente"; let chatUserName = "prueba_mesa43" let userTable = null; let userToken = null; let chatWebsocket = null; // --- Products & Cart --- let Allproducts = []; let cart = []; let itsEmpty = true; let cacheMode = false; // --- Configuration --- const favoriteCategories = ["Shop", "Pizzas Familiares", "Pizza Medianas"]; const productPriority = { "Shop": [ 6, 12, 163, 168, 15, 665, 1 ], "Cervezas": [ 17, ], "Coctelería Gin Klein": [ 655 ], "Pizzas Familiares": [ 27 ], "Pizza Medianas": [ 2 ] } const userColors = [ // Rojos "text-red-500", "text-red-600", "text-rose-500", "text-rose-600", "text-orange-500", "text-orange-600", "text-amber-500", "text-amber-600", // Amarillos / dorados "text-yellow-500", "text-yellow-600", "text-lime-500", "text-lime-600", // Verdes "text-green-500", "text-green-600", "text-emerald-500", "text-emerald-600", "text-teal-500", "text-teal-600", "text-cyan-500", "text-cyan-600", // Azules "text-sky-500", "text-sky-600", "text-blue-500", "text-blue-600", "text-indigo-500", "text-indigo-600", // Violetas / púrpuras æ "text-violet-500", "text-violet-600", "text-purple-500", "text-purple-600", "text-fuchsia-500", "text-fuchsia-600", "text-pink-500", "text-pink-600", // Neutros variados "text-gray-500", "text-gray-600", "text-zinc-500", "text-zinc-600", "text-neutral-500", "text-neutral-600", "text-stone-500", "text-stone-600" ]; const productsWithVariety = { "480": [ "Frambuesa", "Mango", "Maracuyá", "Piña", "Tradicional" ] } //--- Elementos del DOM --- // --- Product & Cart Elements --- const productListElement = document.getElementById("productList"); const cartItemsElement = document.getElementById("cartItems"); const cartTotalElement = document.getElementById("cartTotal"); const emptyCartTextElement = document.getElementById("emptyCartText"); const checkoutButton = document.getElementById("checkoutButton"); const originalCheckoutButtonText = checkoutButton ? checkoutButton.textContent : "Finalizar Pedido"; const cartCountElement = document.getElementById("cartCount"); // --- Chat Elements --- const chatMessagesElement = document.getElementById("chatMessages"); const chatInputElement = document.getElementById("chatInput"); const chatForm = document.getElementById("chatForm"); const userList = document.getElementById("userList") const onlineUsersElement = document.querySelector("#onlineUsers h4"); // --- Reward Elements --- const rewardBtn = document.getElementById('rewardBtn'); const rewardModal = document.getElementById('rewardModal'); const closeRewardModal = document.getElementById('closeRewardModal'); const closeSuccessRewardModalButton = document.getElementById('closeSuccessRewardModal'); const cancelRewardBtn = document.getElementById('cancelRewardBtn'); const acceptTermsCheckbox = document.getElementById('acceptTermsCheckbox'); const claimRewardBtn = document.getElementById('claimRewardBtn'); //--- Inicialización y Configuración --- /** * Main application initialization */ async function initializeApp() { showGlobalLoader("Cargando productos..."); chatUserName = userName.split(" ")[0].toLowerCase() + "_mesa"+userTable initializeChat(); initializeWebSocket(); await initializeProducts(); await renderProducts(Allproducts); setupSearchListener(); setupTabSwitching(); updateCartDisplay(); setupShoppingCart(userId, userToken, userName); setupBasicListeners(); showGUI(); hideGlobalLoader(); } /** * Initialize login modal and authentication */ function initializeLoginModal() { const sessionModal = document.getElementById('sessionModal'); const loginForm = document.getElementById('loginForm'); const logoutBtn = document.getElementById('logoutBtn'); sessionModal.classList.remove('hidden'); // Login form submission loginForm.addEventListener('submit', async (event) => { event.preventDefault(); event.stopPropagation(); showGlobalLoader("Iniciando sesión..."); const fd = new FormData(loginForm); userTable = Number(fd.get('table').trim()); if (!(await existsTable(userTable))) { showError("No existe la mesa seleccionada."); hideGlobalLoader(); return; } if (cacheMode) { sessionModal.classList.add('hidden'); initializeApp(); return; } const email = fd.get('email').trim(); const pin = fd.get('pin').trim(); if (!email || !pin || !userTable) { showError("Por favor, completa todos los campos."); hideGlobalLoader(); return; } try { const data = await login(email, pin); userToken = data.token; userName = data.name; userId = data.id; setCookie("userToken", userToken, 7); updateProgress(data.reward_progress || 0); if (!userToken || data.id === undefined) { showError("Error al iniciar sesión."); hideGlobalLoader(); return; } sessionModal.classList.add('hidden'); initializeApp(); } catch (error) { console.error(error); } }); // Logout functionality if (logoutBtn) { logoutBtn.addEventListener('click', () => { setCookie("userToken", "", -1); userId = -1; userName = "Cliente"; userTable = null; userToken = null; cacheMode = false; hideGUI(); sessionModal.classList.remove('hidden');false // Reset UI elements document.querySelector("#emailInputContainer").classList.remove("hidden"); document.querySelector("#pinInputContainer").classList.remove("hidden"); document.getElementById("loginTitle").textContent = "¡Bienvenido!"; document.querySelector("#emailInput").setAttribute("required", ""); document.querySelector("#pinInput").setAttribute("required", ""); logoutBtn.classList.add("hidden"); document.querySelector("#recoveryPIN").classList.remove("hidden"); }); } } function initializeWebSocket() { if (!chatWebsocket) { const wsProtocol = location.protocol === 'https:' ? 'wss' : 'ws'; chatWebsocket = new WebSocket(`${wsProtocol}://${location.host}/api/chat/ws?token=${userToken}`); chatWebsocket.onopen = () => { chatWebsocket.send(JSON.stringify({ type: "join", username: chatUserName })); } getOnlineUserCount(userToken).then(count => { onlineUsersElement.innerText = `${count} Usuario${count !== 1 ? 's' : ''} en línea`; }); chatWebsocket.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === "message") { displayChatMessage(data.username, data.message); } else if (data.type === "join") { newUserInChat(data.username); getOnlineUserCount(userToken).then(count => { onlineUsersElement.innerText = `${count} Usuario${count !== 1 ? 's' : ''} en línea`; }); } else if (data.type === "leave") { userLeftChat(data.username); getOnlineUserCount(userToken).then(count => { onlineUsersElement.innerText = `${count} Usuario${count !== 1 ? 's' : ''} en línea`; }); } else if (data.type === "mentioned") { if (data.username && data.username !== chatUserName) return; // not for me showMentioned(); } else if (data.type === "ping") { chatWebsocket.send(JSON.stringify({ type: "pong" })); } } } } async function sendMessage(message) { if (chatWebsocket) { if (chatWebsocket.readyState !== WebSocket.OPEN) { console.error("WebSocket is not open, reopening..."); chatWebsocket = null; initializeWebSocket(); } await chatWebsocket.send(JSON.stringify({ type: "message", username: chatUserName, message: message })); let mentions_repeated = []; await setTimeout(() => { const mentionRegex = /@([a-zA-Z0-9_]+_mesa\d+|IAKlein)/g; const mentions = message.match(mentionRegex); if (mentions) { mentions.forEach(mention => { const username = mention.slice(1); const exist = mentions_repeated.find(u => u === username); console.log("Mentioned user:", username,"List", mentions_repeated, "Exist before:", exist); if (exist || username === chatUserName) return; // don't mention myself if multiple mentions mentions_repeated.push(username); if (username === "IAKlein"){ sendMessageToAI(message); }else { userMentioned(username); } }); } }, 1000); // small delay to ensure message order // regex text_mesa111 or IAKlein } } function userMentioned(username) { chatWebsocket.send(JSON.stringify({ type: "mention", username: username })); } /** * Initialize chat functionality */ function initializeChat() { if (!chatForm) return; chatForm.addEventListener("submit", (event) => { event.preventDefault(); if (chatInputElement.value.trim() === "") return; chatForm.querySelector("button").animate( [{ transform: 'scale(1)' }, { transform: 'scale(0.9)' }, { transform: 'scale(1)' }], { duration: 200, iterations: 1, easing: 'ease-in-out' } ) sendMessage(chatInputElement.value.trim()); chatInputElement.value = ""; }); let debounceTimer; chatInputElement.addEventListener("input", () => { const lastWord = chatInputElement.value.split(" ").at(-1); if (lastWord.trim().startsWith("@")) { clearTimeout(debounceTimer); debounceTimer = setTimeout(async () => { userList.classList.remove("hidden"); let users = await getUserList(lastWord.trim().substring(1), userToken); users = users.filter(u => u !== chatUserName); users.push("IAKlein"); userList.innerHTML = ""; users.forEach(user => { const userItem = document.createElement("button"); userItem.type = "button"; userItem.onclick = () => { // Replace the last occurrence of lastWord with @user const inputValue = chatInputElement.value; const lastWordIndex = inputValue.lastIndexOf(lastWord); if (lastWordIndex !== -1) { chatInputElement.value = inputValue.slice(0, lastWordIndex) + `@${user}` + inputValue.slice(lastWordIndex + lastWord.length); } userList.classList.add("hidden"); chatInputElement.value += " "; chatInputElement.focus(); }; const color = getUserColor(user); userItem.classList.add("list-user-name", "cursor-pointer", color); userItem.textContent = user; userList.appendChild(userItem); }); }, 300); // Debounce delay (300ms) } else { userList.classList.add("hidden"); clearTimeout(debounceTimer); } }); } export function beforeUnloadHandler() { if (userToken) { logout(); } } /** * Setup basic event listeners */ function setupBasicListeners() { if (!checkoutButton) return; checkoutButton.addEventListener("click", processOrder); initializeRewards(); // Close model - Back button on phone window.addEventListener('popstate', function (event) { alert("back"); this.history.pushState(null, null, this.location.href); if (!rewardModal.classList.contains('hidden')) { closeModal(); }else { window.switchTab('menuTab'); } }); window.addEventListener('beforeunload', beforeUnloadHandler); } /** * Setup search functionality with debouncing */ function setupSearchListener() { const searchInput = document.getElementById("searchInput"); if (!searchInput) return; let debounceTimer; searchInput.addEventListener("input", () => { clearTimeout(debounceTimer); // Visual feedback during search productListElement.style.opacity = "0.7"; productListElement.style.transform = "scale(0.98)"; productListElement.style.transition = "opacity 0.2s ease, transform 0.2s ease"; // Debounce search for 200ms debounceTimer = setTimeout(() => { const searchTerm = searchInput.value.toLowerCase(); if (searchTerm.trim() === "") { renderProductsWithAnimation(Allproducts); return; } const foundProducts = smartSearch(Allproducts, searchTerm); renderProductsWithAnimation(foundProducts, true, searchTerm); }, 200); }); } // Tab switching variables const animation_time = 200; let transitioning = false; /** * Switch to a specific tab programmatically * @param {string} targetTabId - The ID of the target tab (e.g., 'chatTab', 'productsTab') */ window.switchTab = function(targetTabId) { const targetButton = document.querySelector(`[data-target="${targetTabId}"]`); if (!targetButton) { console.warn(`Tab button with target "${targetTabId}" not found`); return; } if (targetTabId === "chatTab") { window.hideMentioned(); } const active = document.querySelector(':not(.hidden)[data-tab]'); if (!active) { console.warn('No active tab found'); return; } const activeIndex = active.dataset.index; const to = document.querySelector(`#${targetTabId}[data-tab]`); if (!to) { console.warn(`Target tab "${targetTabId}" not found`); return; } const toIndex = to.dataset.index; if (activeIndex === toIndex || transitioning) return; // Update button states const buttons = document.querySelectorAll('.tab-btn'); buttons.forEach(button => { button.classList.remove('active'); }); targetButton.classList.add('active'); // Perform tab transition performTabTransition(active, to, activeIndex, toIndex, targetButton); }; /** * Perform the actual tab transition with animations * @param {Element} active - Currently active tab * @param {Element} to - Target tab to switch to * @param {string} activeIndex - Index of active tab * @param {string} toIndex - Index of target tab * @param {Element} targetButton - Button that triggered the switch */ function performTabTransition(active, to, activeIndex, toIndex, targetButton) { active.style.height = "100%"; active.style.width = "100vw"; to.style.height = "100%"; to.style.width = "100vw"; to.style.zIndex = "1"; active.style.zIndex = "0"; transitioning = true; const otherTabs = document.querySelectorAll('[data-tab]'); otherTabs.forEach(tab => { if (tab !== active && tab !== to) { tab.classList.add('hidden'); tab.classList.remove(`animate-[slideLeft_${animation_time}ms_ease-out]`, `animate-[slideRight_${animation_time}ms_ease-out]`); tab.classList.remove(`animate-[slideLeftIn_${animation_time}ms_ease-out]`, `animate-[slideRightIn_${animation_time}ms_ease-out]`); } }); to.classList.remove('hidden'); // Animate tab transition if (activeIndex < toIndex) { // Slide left active.classList.add(`animate-[slideLeft_${animation_time}ms_ease-out]`); to.classList.add(`animate-[slideRightIn_${animation_time}ms_ease-out]`); } else if (activeIndex > toIndex) { // Slide right active.classList.add(`animate-[slideRight_${animation_time}ms_ease-out]`); to.classList.add(`animate-[slideLeftIn_${animation_time}ms_ease-out]`); } setTimeout(() => { active.classList.remove(`animate-[slideLeft_${animation_time}ms_ease-out]`, `animate-[slideRight_${animation_time}ms_ease-out]`); active.classList.add('hidden'); to.classList.remove(`animate-[slideLeftIn_${animation_time}ms_ease-out]`, `animate-[slideRightIn_${animation_time}ms_ease-out]`); transitioning = false; }, animation_time); // Update header title if needed const title = targetButton.dataset.title; if (title) { const mainTitle = document.getElementById('mainTitle'); if (mainTitle) { mainTitle.textContent = title; } } } /** * Setup tab switching functionality with animations */ function setupTabSwitching() { // Tab switching functionality const buttons = document.querySelectorAll('.tab-btn'); buttons.forEach(btn => { btn.addEventListener('click', () => { const target = btn.dataset.target; window.switchTab(target); }); }); } //--- Funciones de Utilidad --- /** * Set a cookie with expiration */ function setCookie(name, value, days) { const expires = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString(); document.cookie = `${name}=${value}; expires=${expires}; path=/`; } /** * Get a cookie value by name */ function getCookie(name) { const cookies = document.cookie.split('; '); for (const cookie of cookies) { const [cookieName, cookieValue] = cookie.split('='); if (cookieName === name) { return decodeURIComponent(cookieValue); } } return null; } /** * Check and validate cached user data */ async function checkCache() { const tokenCache = getCookie("userToken"); let user = null; if (tokenCache) { userToken = tokenCache; try { const data = await getUserData(userToken); user = data.data; } catch (error) { console.error("Error fetching user data:", error); console.error("Invalid user token, clearing cache."); setCookie("userToken", "", -1); return false; } if (user) { userId = user.id; userName = user.name; updateProgress(user.reward_progress || 0); // Update UI for cached user const [emailInputContainer, pinInputContainer] = [ document.getElementById("emailInputContainer"), document.getElementById("pinInputContainer") ]; const [emailInput, pinInput] = [ document.getElementById("emailInput"), document.getElementById("pinInput") ]; const loginTitle = document.getElementById("loginTitle"); loginTitle.textContent = `¡Bienvenido ${user.name.split(" ")[0]}!`; emailInputContainer.classList.add("hidden"); emailInput.removeAttribute("required"); pinInputContainer.classList.add("hidden"); pinInput.removeAttribute("required"); document.querySelector("#logoutBtn").classList.remove("hidden"); document.querySelector("#recoveryPIN").classList.add("hidden"); cacheMode = true; return true; } } return cacheMode; } /** * Format price to Chilean peso currency */ function formatPrice(price) { return price.toLocaleString("es-CL", { style: "currency", currency: "CLP" }); } //--- Gestión de Productos --- /** * Initialize products from API */ async function initializeProducts() { Allproducts = await getProducts(userToken); } /** * Create category containers for products */ async function createCategories(products) { let categories = new Set(products.map(product => product.type || "Sin categoría")); categories = Array.from(categories).sort((a, b) => a.localeCompare(b)); // Prioritize favorite categories for (const category of favoriteCategories.reverse()) { if (categories.includes(category)) { categories = categories.filter(cat => cat !== category); categories.unshift(category); } } if (!productListElement) return; const categoryContainers = categories.map(category => { const container = document.createElement("div"); container.classList.add("category-container", "mb-8", "p-4"); const title = document.createElement("h2"); title.classList.add("category-title", "text-3xl", "font-bold", "border-b-2", "pb-3", "mb-4"); // Pluralize category name const lastChar = category.charAt(category.length - 1).toLowerCase(); const pluralSuffix = ["a", "e", "i", "o", "u", "á", "é", "í", "ó", "ú", "p"].includes(lastChar) ? "s" : lastChar === "s" ? "" : "es"; title.textContent = category + pluralSuffix; container.appendChild(title); const productList = document.createElement("div"); productList.classList.add("product-list", "space-y-6"); productList.id = `productList-${category}`; container.appendChild(productList); productListElement.appendChild(container); return { category, container: productList }; }); return categoryContainers; } /** * Render products in their respective categories */ async function renderProducts(products, groupInCategories = true, searchTerm = "") { if (!productListElement) return; const template = document.getElementById("product-card-template"); if (!template) return; productListElement.innerHTML = ""; let categoryContainers = []; if (groupInCategories) { categoryContainers = await createCategories(products); } else { categoryContainers = [{ category: searchTerm, container: productListElement }]; } if (products.length === 0) { const noProductsMessage = document.createElement("p"); noProductsMessage.textContent = "No hay productos disponibles."; productListElement.appendChild(noProductsMessage); return; } for (const { category, container } of categoryContainers) { let productsInCategory = groupInCategories ? products.filter(product => product.type === category) : products; // Prioritize products in favorite categories if (favoriteCategories.includes(category) && productPriority[category]) { productsInCategory.sort((a, b) => { const aIndex = productPriority[category].indexOf(a.id); const bIndex = productPriority[category].indexOf(b.id); // If both products are in priority list, sort by their priority order if (aIndex !== -1 && bIndex !== -1) { return aIndex - bIndex; } // If only 'a' is in priority list, it should come first if (aIndex !== -1 && bIndex === -1) { return -1; } // If only 'b' is in priority list, it should come first if (aIndex === -1 && bIndex !== -1) { return 1; } // If neither is in priority list, maintain original order return 0; }); } if (productsInCategory.length === 0) continue; productsInCategory.forEach(product => { const clone = template.content.cloneNode(true).firstElementChild; clone.querySelector(".product-type").textContent = product.type || "Sin categoría"; clone.querySelector(".product-name").textContent = product.name; clone.querySelector(".product-description").textContent = product.description; clone.querySelector(".product-price").textContent = formatPrice(product.price); const image = clone.querySelector(".product-image"); image.dataset.src = product.image const addBtn = clone.querySelector(".add-to-cart-btn"); addBtn.dataset.productId = product.id; addBtn.addEventListener('click', (event) => { const productId = parseInt(event.target.dataset.productId); addToCart(productId, event.target); }); container.appendChild(clone); imageObserver.observe(image); }); } } /** * Render products with smooth animations */ async function renderProductsWithAnimation(products, groupInCategories = true, searchTerm = "") { if (!productListElement) return; const template = document.getElementById("product-card-template"); if (!template) return; // Fade out current content productListElement.style.opacity = "0"; productListElement.style.transform = "scale(0.95)"; setTimeout(async () => { productListElement.innerHTML = ""; let categoryContainers = []; if (groupInCategories) { categoryContainers = await createCategories(products); } else { categoryContainers = [{ category: searchTerm, container: productListElement }]; } // ordenar las categorias por favoriteCategories categoryContainers.sort((a, b) => { const aIndex = favoriteCategories.indexOf(a.category); const bIndex = favoriteCategories.indexOf(b.category); return aIndex - bIndex; }); if (products.length === 0) { const noProductsMessage = document.createElement("p"); noProductsMessage.textContent = "No hay productos disponibles."; noProductsMessage.style.opacity = "0"; noProductsMessage.style.transform = "translateY(20px)"; noProductsMessage.style.transition = "opacity 0.3s ease, transform 0.3s ease"; productListElement.appendChild(noProductsMessage); // Restore container productListElement.style.opacity = "1"; productListElement.style.transform = "scale(1)"; // Animate message setTimeout(() => { noProductsMessage.style.opacity = "1"; noProductsMessage.style.transform = "translateY(0)"; }, 50); return; } let animationDelay = 0; for (const { category, container } of categoryContainers) { let productsInCategory = groupInCategories ? products.filter(product => product.type === category) : products; if (productsInCategory.length === 0) continue; if (favoriteCategories.includes(category) && productPriority[category]) { productsInCategory.sort((a, b) => { const aIndex = productPriority[category].indexOf(a.id); const bIndex = productPriority[category].indexOf(b.id); // If both products are in priority list, sort by their priority order if (aIndex !== -1 && bIndex !== -1) { return aIndex - bIndex; } // If only 'a' is in priority list, it should come first if (aIndex !== -1 && bIndex === -1) { return -1; } // If only 'b' is in priority list, it should come first if (aIndex === -1 && bIndex !== -1) { return 1; } // If neither is in priority list, maintain original order return 0; }); } productsInCategory.forEach((product, index) => { const clone = template.content.cloneNode(true).firstElementChild; clone.querySelector(".product-type").textContent = product.type || "Sin categoría"; clone.querySelector(".product-name").textContent = product.name; clone.querySelector(".product-description").textContent = product.description; clone.querySelector(".product-price").textContent = formatPrice(product.price); const image = clone.querySelector(".product-image"); image.dataset.src = product.image const addBtn = clone.querySelector(".add-to-cart-btn"); addBtn.dataset.productId = product.id; addBtn.addEventListener('click', (event) => { const productId = parseInt(event.target.dataset.productId); addToCart(productId, event.target); }); // Get the first child (the product card element) const productCard = clone.children[0]; // Set initial animation state productCard.style.opacity = "0"; productCard.style.transform = "translateY(20px) scale(0.95)"; productCard.style.transition = "opacity 0.4s ease, transform 0.4s ease"; container.appendChild(clone); imageObserver.observe(image); // Animate in with staggered delay setTimeout(() => { productCard.style.opacity = "1"; productCard.style.transform = "translateY(0) scale(1)"; }, animationDelay); animationDelay += 50; // 50ms delay between each product }); } // Restore container with smooth transition productListElement.style.opacity = "1"; productListElement.style.transform = "scale(1)"; }, 150); // Wait for fade out to complete } //--- Gestión del Carrito --- /** * Add product to cart with visual feedback */ window.addComment = async function(productId) { const cartItem = cart.find(p => p.id === productId); if (!cartItem) return; const comment = await getComment(COMMENT_TYPES.LIBRE, [cartItem.comment]).catch(e => console.error(e)); if (!comment) return; cartItem.comment += ` ${comment}`; console.log(cartItem); } window.addToCart = async function(productId, buttonElement = null) { let comment = ""; if (productsWithVariety[String(productId)]) { comment = await getComment(COMMENT_TYPES.DESPLEGABLE, productsWithVariety[String(productId)]); } const product = Allproducts.find(p => p.id === productId); if (!product) return; const cartItem = cart.find(item => item.id === productId); if (cartItem) { cartItem.quantity++; cartItem.comment += `, ${comment}`; } else { cart.push({ ...product, quantity: 1, comment }); } // Visual feedback for button if (buttonElement) { const originalHTML = buttonElement.innerHTML; buttonElement.textContent = "✔ Agregado!"; buttonElement.disabled = true; setTimeout(() => { buttonElement.innerHTML = originalHTML; buttonElement.disabled = false; }, 300); } updateCartDisplay(); // Show toast notification if available if (typeof showToast === "function") { showToast(`${product.name} agregado al carrito`); } }; /** * Remove product from cart */ window.removeFromCart = function(productId, removeAll = false) { const itemIndex = cart.findIndex(item => item.id === productId); if (itemIndex > -1) { if (removeAll || cart[itemIndex].quantity === 1) { cart.splice(itemIndex, 1); } else { cart[itemIndex].quantity--; } } updateCartDisplay(); }; /** * Calculate total cart amount */ function calculateTotal() { if (!cartTotalElement) return; const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0); cartTotalElement.textContent = formatPrice(total); } /** * Update cart display with animations */ function updateCartDisplay() { if (!cartItemsElement || !emptyCartTextElement || !checkoutButton || !cartCountElement) return; cartItemsElement.innerHTML = ""; cartCountElement.textContent = cart.reduce((sum, item) => sum + item.quantity, 0); if (cart.length === 0) { cartCountElement.classList.add("hidden"); emptyCartTextElement.classList.remove("hidden"); checkoutButton.disabled = true; itsEmpty = true; } else { cartCountElement.classList.remove("hidden"); // Animate cart count badge if (cartCountElement && itsEmpty) { itsEmpty = false; cartCountElement.animate([ { transform: 'scale(0)' }, { transform: 'scale(1)' } ], { duration: 300, iterations: 1, easing: 'ease-in-out' }); } else { cartCountElement.animate([ { transform: 'scale(1) rotate(0deg)' }, { transform: 'scale(1.2) rotate(180deg)' }, { transform: 'scale(1) rotate(360deg)' } ], { duration: 300, iterations: 1, easing: 'ease-in-out' }); } emptyCartTextElement.classList.add("hidden"); checkoutButton.disabled = false; // Render cart items cart.forEach(item => { const cartItemHTML = `

${item.name} (x${item.quantity})

${formatPrice(item.price * item.quantity)}

`; cartItemsElement.innerHTML += cartItemHTML; }); } calculateTotal(); } //--- Procesamiento de Pedidos --- /** * Process and send order to backend */ async function processOrder() { if (cart.length === 0) return; showGlobalLoader(); if (checkoutButton) { checkoutButton.disabled = true; checkoutButton.textContent = "Procesando..."; } try { const orderData = { customerId: userId, table: userTable, items: cart.map(item => ({ id: item.id, price: item.price, quantity: item.quantity, comment: item.comment ?? "" })), totalAmount: cart.reduce((sum, item) => sum + item.price * item.quantity, 0), orderDate: new Date().toLocaleString('sv-SE').replace(' ', 'T') }; const data = await sendOrder(orderData, userToken); if (data && data.new_progress && data.new_progress !== getProgress()) { updateProgress(data.new_progress); } alert("Pedido enviado con éxito."); // Add items to history cart.forEach(item => { addHistoryRow({ productName: item.name, quantity: item.quantity, price: item.price, }); }); // Clear cart cart = []; updateCartDisplay(); } catch (error) { console.error("Error al procesar la orden:", error); alert(`Hubo un problema: ${error.message || "Por favor, inténtalo de nuevo."}`); } finally { hideGlobalLoader(); checkoutButton.disabled = cart.length === 0; checkoutButton.textContent = originalCheckoutButtonText; } } //--- Funcionalidad del Chat --- /** * Display chat message with proper styling */ function showMentioned(){ const chatButton = document.querySelector("#chatIcon span"); if (chatButton) { //get after element chatButton.classList.remove("hidden"); chatButton.animate( [ { transform: 'scale(1)' }, { transform: 'scale(1.5)' }, { transform: 'scale(1)' } ], { duration: 300, iterations: 3, easing: 'ease-in-out' } ); } } window.hideMentioned = function(){ const chatButton = document.querySelector("#chatIcon span"); if (chatButton) { chatButton.classList.add("hidden"); } } function getUserColor(username) { const hash = [...username].reduce((acc, char) => acc + char.charCodeAt(0), 0); const color = userColors[hash % userColors.length]; return color; } function displayChatMessage(sender, message, time = undefined) { let realtime = `[${time ? time : new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })}] `; let messageTemplate = chatMessagesElement.querySelector("#chatMessageTemplate"); if (!messageTemplate) return; let messageClone = messageTemplate.content.cloneNode(true).firstElementChild; messageClone.querySelector(".chat-message-text").innerHTML = message; messageClone.querySelector(".chat-message-time").innerHTML = realtime; messageClone.querySelector(".chat-message-user").innerHTML = sender messageClone.querySelector(".chat-message-user").classList.add(getUserColor(sender)); chatMessagesElement.appendChild(messageClone); chatMessagesElement.scrollTop = chatMessagesElement.scrollHeight; } function newUserInChat(userName) { let userTemplate = chatMessagesElement.querySelector("#systemMessageTemplate"); if (!userTemplate) return; const realtime = `[${new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })}] `; let userClone = userTemplate.content.cloneNode(true).firstElementChild; userClone.classList.add("text-green-600"); userClone.querySelector(".chat-message-time").innerHTML = realtime; userClone.querySelector(".chat-message-text").innerHTML = `*** ${userName} se ha unido al chat`; chatMessagesElement.appendChild(userClone); } function userLeftChat(userName) { let userTemplate = chatMessagesElement.querySelector("#systemMessageTemplate"); if (!userTemplate) return; const realtime = `[${new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })}] `; let userClone = userTemplate.content.cloneNode(true).firstElementChild; userClone.classList.add("text-red-600"); userClone.querySelector(".chat-message-time").innerHTML = realtime; userClone.querySelector(".chat-message-text").innerHTML = `*** ${userName} se ha ido del chat`; chatMessagesElement.appendChild(userClone); } function sendMessageToAI(userMessage) { if (userMessage === "") return; const messages = Array.from(chatMessagesElement.querySelectorAll(".chat-message")).slice(-10); let formattedMessages = messages.map(msg => { const sender = msg.querySelector(".chat-message-user").textContent; const content = msg.querySelector(".chat-message-text").innerHTML; return { user: sender, content: content }; }); chatWebsocket.send(JSON.stringify({ type: "ai_message", username: chatUserName, messages: formattedMessages })); chatInputElement.value = ""; } //--- Sistema de Recompensas --- /** * Initialize rewards functionality */ function initializeRewards() { // Event listeners for reward system closeSuccessRewardModalButton.addEventListener("click", closeSuccessRewardModal); rewardBtn.addEventListener('click', function () { if (!rewardBtn.disabled) { rewardModal.classList.remove('hidden'); document.body.style.overflow = 'hidden'; } }); // Close modal - X button closeRewardModal.addEventListener('click', function () { closeModal(); }); // Close modal - Cancel button cancelRewardBtn.addEventListener('click', function () { closeModal(); }); // Close modal - click outside rewardModal.addEventListener('click', function (e) { if (e.target === rewardModal) { closeModal(); } }); // Close modal - Escape key document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !rewardModal.classList.contains('hidden')) { closeModal(); } }); // Handle reward claim claimRewardBtn.addEventListener('click', function () { if (!this.disabled && acceptTermsCheckbox.checked) { claimReward(userToken, userTable); closeModal(); updateProgress(0); } }); // Handle terms checkbox acceptTermsCheckbox.addEventListener('change', function () { if (this.checked) { claimRewardBtn.disabled = false; claimRewardBtn.classList.remove('opacity-50', 'cursor-not-allowed'); } else { claimRewardBtn.disabled = true; claimRewardBtn.classList.add('opacity-50', 'cursor-not-allowed'); } }); } /** * Close success reward modal */ function closeSuccessRewardModal() { const successRewardModal = document.getElementById("successRewardModal"); successRewardModal.classList.add("hidden"); document.body.style.overflow = ''; } /** * Close reward modal and reset form */ function closeModal() { rewardModal.classList.add('hidden'); document.body.style.overflow = ''; // Reset form acceptTermsCheckbox.checked = false; claimRewardBtn.disabled = true; claimRewardBtn.classList.add('opacity-50', 'cursor-not-allowed'); } //--- Inicialización de la Aplicación --- /** * Initialize the application when DOM is loaded */ document.addEventListener("DOMContentLoaded", async () => { hideGUI(); const isCacheValid = await checkCache(); if (!isCacheValid) { console.warn("Cache is invalid"); } createGlobalLoader(); // Uncomment these lines when ready to initialize the full app: initializeLoginModal(); // initializeApp(); });