/* * Copyright 2025 Criptomart * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) */ (function() { 'use strict'; // Objeto global para gestión de carrito window.groupOrderShop = { orderId: null, cart: {}, labels: {}, // Will be loaded from HTML data attributes init: function() { console.log('[groupOrderShop] Initializing...'); var self = this; // Get order ID first (needed by i18nManager and other functions) var confirmBtn = document.getElementById('confirm-order-btn'); var cartContainer = document.getElementById('cart-items-container'); var orderIdElement = confirmBtn || cartContainer; if (!orderIdElement) { console.log('No elements found to get order ID'); return false; } // Get the order ID from the data attribute or from the URL this.orderId = orderIdElement.getAttribute('data-order-id'); if (!this.orderId) { var urlMatch = window.location.pathname.match(/\/eskaera\/(\d+)/); this.orderId = urlMatch ? urlMatch[1] : null; } if (!this.orderId) { console.error('Order ID not found'); if (cartContainer) { cartContainer.innerHTML = '
' + 'Error: Order ID not found
'; } return false; } console.log('Initializing cart for order:', this.orderId); // Wait for i18nManager to load translations from server i18nManager.init().then(function() { console.log('[groupOrderShop] Translations loaded from server'); self.labels = i18nManager.getAll(); // Initialize event listeners and state after translations are ready self._attachEventListeners(); self._loadCart(); self._checkConfirmationMessage(); self._initializeTooltips(); // Update display if there is cart-items-container if (cartContainer) { self._updateCartDisplay(); } // Check if we're loading from history var storageKey = 'load_from_history_' + self.orderId; if (sessionStorage.getItem(storageKey)) { // Load items from historical order self._loadFromHistory(); } else { // Auto-load draft order on page load (silent mode) self._autoLoadDraftOnInit(); } // Emit event when fully initialized document.dispatchEvent(new CustomEvent('groupOrderShopReady', { detail: { labels: self.labels } })); console.log('[groupOrderShop] ✓ Initialization complete'); }).catch(function(error) { console.error('[groupOrderShop] Failed to initialize translations:', error); // Fallback: use empty labels so app doesn't crash self.labels = {}; }); // Dispatch event to notify that cart is ready (preliminary, will be fired again when translations loaded) var event = new CustomEvent('groupOrderCartReady', { detail: { orderId: this.orderId } }); document.dispatchEvent(event); console.log('Cart ready event dispatched for order:', this.orderId); return true; }, _checkConfirmationMessage: function() { // Removed: this was showing confirmation message again in eskaera page // which confused the user. The message is shown only in the checkout page // before redirecting. var confirmationMessage = sessionStorage.getItem('confirmation_message'); if (confirmationMessage) { // Just remove it, don't show it again sessionStorage.removeItem('confirmation_message'); } }, _autoLoadDraftOnInit: function() { console.log('Attempting to auto-load draft order...'); var self = this; // Only auto-load if cart is empty var cartItemsCount = Object.keys(this.cart).length; if (cartItemsCount > 0) { console.log('Cart already has items (' + cartItemsCount + '), skipping auto-load'); return; } var orderData = { order_id: this.orderId }; var xhr = new XMLHttpRequest(); xhr.open('POST', '/eskaera/load-draft', true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onload = function() { if (xhr.status === 200) { try { var data = JSON.parse(xhr.responseText); if (data.success) { // Clear current cart self.cart = {}; // Load items from draft order var items = data.items || []; items.forEach(function(item) { var productId = item.product_id; self.cart[productId] = { name: item.product_name, price: item.product_price, qty: item.quantity }; }); // Save to localStorage self._saveCart(); // Restore pickup fields if available if (data.pickup_day) { var dayElement = document.getElementById('pickup-day'); if (dayElement) { dayElement.value = data.pickup_day; console.log('Auto-loaded pickup_day:', data.pickup_day); } } if (data.pickup_date) { var dateElement = document.getElementById('pickup-date'); if (dateElement) { dateElement.value = data.pickup_date; console.log('Auto-loaded pickup_date:', data.pickup_date); } } if (data.home_delivery !== undefined) { var homeDeliveryElement = document.getElementById('home-delivery-checkbox'); if (homeDeliveryElement) { homeDeliveryElement.checked = data.home_delivery; console.log('Auto-loaded home_delivery:', data.home_delivery); } } // Update display self._updateCartDisplay(); console.log('Auto-loaded ' + items.length + ' items from draft'); // Show a subtle notification var labels = self._getLabels(); var cartRestoredMsg = (labels.cart_restored || 'Your cart has been restored') + ' (' + items.length + ' items)'; self._showNotification('✓ ' + cartRestoredMsg, 'info', 3000); } else { // Silently ignore - no draft found (normal case) console.log('No draft found to auto-load (normal)'); } } catch (e) { console.error('Error parsing auto-load response:', e); } } else if (xhr.status === 404) { // No draft found - this is normal, not an error console.log('No draft order found (404 - normal)'); } else { console.log('Auto-load failed with status:', xhr.status); } }; xhr.onerror = function() { console.log('Auto-load connection error (non-critical)'); }; xhr.send(JSON.stringify(orderData)); }, _loadFromHistory: function() { // Load items from historical order (called from load_from_history page) var self = this; var storageKey = 'load_from_history_' + this.orderId; var orderNameKey = 'load_from_history_order_name_' + this.orderId; var pickupDayKey = 'load_from_history_pickup_day_' + this.orderId; var pickupDateKey = 'load_from_history_pickup_date_' + this.orderId; var homeDeliveryKey = 'load_from_history_home_delivery_' + this.orderId; var itemsJson = sessionStorage.getItem(storageKey); var orderName = sessionStorage.getItem(orderNameKey); var pickupDay = sessionStorage.getItem(pickupDayKey); var pickupDate = sessionStorage.getItem(pickupDateKey); var homeDelivery = sessionStorage.getItem(homeDeliveryKey) === 'true'; console.log('DEBUG: _loadFromHistory called for orderId:', this.orderId); console.log('DEBUG: sessionStorageKey:', storageKey); console.log('DEBUG: sessionStorage value type:', typeof itemsJson, 'value:', itemsJson); console.log('DEBUG: orderName:', orderName); console.log('DEBUG: pickupDay:', pickupDay, '(empty means different group order)'); console.log('DEBUG: pickupDate:', pickupDate, '(empty means different group order)'); console.log('DEBUG: homeDelivery:', homeDelivery, '(empty means different group order)'); if (!itemsJson || itemsJson === '[object Object]') { console.log('No valid items from history found in sessionStorage'); sessionStorage.removeItem(storageKey); sessionStorage.removeItem(orderNameKey); sessionStorage.removeItem(pickupDayKey); sessionStorage.removeItem(pickupDateKey); sessionStorage.removeItem(homeDeliveryKey); return; } try { // Ensure itemsJson is a string before parsing if (typeof itemsJson !== 'string') { console.log('itemsJson is not a string, converting...'); itemsJson = JSON.stringify(itemsJson); } // Parse items var items = JSON.parse(itemsJson); // Verify we got an array if (!Array.isArray(items)) { console.error('Parsed items is not an array:', items); sessionStorage.removeItem(storageKey); sessionStorage.removeItem(orderNameKey); sessionStorage.removeItem(pickupDayKey); sessionStorage.removeItem(pickupDateKey); sessionStorage.removeItem(homeDeliveryKey); return; } console.log('Loaded ' + items.length + ' items from history:', items); // Clear current cart this.cart = {}; console.log('Cart cleared, now empty'); // Add each item to cart items.forEach(function(item) { self.cart[item.product_id] = { name: item.product_name || 'Product ' + item.product_id, price: item.price || 0, qty: item.quantity || 1 }; console.log('Added to cart: product_id=' + item.product_id + ', qty=' + item.quantity); }); console.log('Cart after adding all items:', self.cart); // Save to localStorage this._saveCart(); console.log('After _saveCart(), localStorage contains:', localStorage.getItem('eskaera_' + this.orderId + '_cart')); // Restore pickup fields if available (non-empty) // Empty values mean the order was from a different group order, // so we use the current group order's pickup fields instead if (pickupDay && pickupDay.trim() !== '') { var dayElement = document.getElementById('pickup-day'); if (dayElement) { dayElement.value = pickupDay; console.log('Restored pickup_day from old order:', pickupDay); } } else { console.log('Did NOT restore pickup_day (empty = different group order, using current group order days)'); } if (pickupDate && pickupDate.trim() !== '') { var dateElement = document.getElementById('pickup-date'); if (dateElement) { dateElement.value = pickupDate; console.log('Restored pickup_date from old order:', pickupDate); } } else { console.log('Did NOT restore pickup_date (empty = different group order)'); } if (homeDelivery !== null && homeDelivery !== false) { var homeDeliveryElement = document.getElementById('home-delivery-checkbox'); if (homeDeliveryElement) { homeDeliveryElement.checked = homeDelivery; console.log('Restored home_delivery from old order:', homeDelivery); } } else { console.log('Did NOT restore home_delivery (empty/false = different group order or not set)'); } // Update display this._updateCartDisplay(); // Show notification with order reference if available var labels = this._getLabels(); var itemsLabel = labels.items || 'items'; var orderLoadedMsg = labels.order_loaded || 'Order loaded'; var message = '✓ ' + orderLoadedMsg + ' (' + items.length + ' ' + itemsLabel + ')'; if (orderName) { message += ' - ' + orderName; } this._showNotification(message, 'success', 3000); // Clear sessionStorage sessionStorage.removeItem(storageKey); sessionStorage.removeItem(orderNameKey); sessionStorage.removeItem(pickupDayKey); sessionStorage.removeItem(pickupDateKey); sessionStorage.removeItem(homeDeliveryKey); } catch (e) { console.error('Error loading from history:', e); console.error('itemsJson was:', itemsJson); // Don't show error to user if sessionStorage had invalid data - just continue sessionStorage.removeItem(storageKey); sessionStorage.removeItem(orderNameKey); sessionStorage.removeItem(pickupDayKey); sessionStorage.removeItem(pickupDateKey); sessionStorage.removeItem(homeDeliveryKey); // Only show notification if we actually tried to load something if (itemsJson && itemsJson !== '[object Object]') { this._showNotification('Error loading order: ' + e.message, 'danger'); } } }, _loadCart: function() { var cartKey = 'eskaera_' + this.orderId + '_cart'; var savedCart = localStorage.getItem(cartKey); this.cart = savedCart ? JSON.parse(savedCart) : {}; console.log('Cart loaded from localStorage[' + cartKey + ']:', this.cart); console.log('Raw localStorage value:', savedCart); }, _saveCart: function() { var cartKey = 'eskaera_' + this.orderId + '_cart'; var cartJson = JSON.stringify(this.cart); localStorage.setItem(cartKey, cartJson); console.log('Cart saved to localStorage[' + cartKey + ']:', this.cart); console.log('Verification - immediately read back:', localStorage.getItem(cartKey)); }, _showNotification: function(message, type, duration) { // type: 'success', 'error', 'warning', 'info' // duration: milliseconds (default 8000 = 8 seconds) type = type || 'info'; duration = duration || 8000; var notification = document.createElement('div'); notification.className = 'alert alert-' + type + ' alert-dismissible fade show position-fixed'; notification.style.cssText = 'top: 80px; right: 20px; z-index: 9999; min-width: 300px; max-width: 500px; font-size: 18px; padding: 1.5rem; transition: opacity 0.3s ease-out;'; notification.innerHTML = message + ''; document.body.appendChild(notification); setTimeout(function() { notification.classList.remove('show'); setTimeout(function() { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 300); }, duration); }, _getLabels: function() { // Get current labels from window.groupOrderShop which is updated by checkout_labels.js console.log('[_getLabels] Starting label resolution...'); console.log('[_getLabels] window.groupOrderShop exists:', !!window.groupOrderShop); if (window.groupOrderShop && window.groupOrderShop.labels) { console.log('[_getLabels] window.groupOrderShop.labels exists'); var keysCount = Object.keys(window.groupOrderShop.labels).length; console.log('[_getLabels] Keys count:', keysCount); if (keysCount > 0) { console.log('[_getLabels] ✅ USING window.groupOrderShop.labels (from endpoint):', window.groupOrderShop.labels); return window.groupOrderShop.labels; } } console.log('[_getLabels] window.groupOrderShop check failed, trying this.labels'); if (this.labels && Object.keys(this.labels).length > 0) { console.log('[_getLabels] ⚠️ USING this.labels (fallback):', this.labels); return this.labels; } console.log('[_getLabels] ❌ USING default labels'); return this._getDefaultLabels(); }, _initializeTooltips: function() { console.log('[_initializeTooltips] Initializing tooltips with translated labels...'); var self = this; var labels = this._getLabels(); // Map of button IDs/classes to label keys var tooltipMap = { 'save-cart-btn': 'save_cart', 'reload-cart-btn': 'reload_cart', 'confirm-order-btn': 'confirm_order', 'remove-from-cart': 'remove_item' }; // Map of href patterns to label keys var hrefPatterns = [ { pattern: /\/checkout$/, labelKey: 'proceed_to_checkout' }, { pattern: /\/eskaera\/\d+$/, labelKey: 'back_to_cart' } ]; // Find all elements with data-bs-toggle="tooltip" var tooltipElements = document.querySelectorAll('[data-bs-toggle="tooltip"]'); console.log('[_initializeTooltips] Found', tooltipElements.length, 'tooltip elements'); // Also update category select placeholder text var categorySelect = document.getElementById('realtime-category-select'); if (categorySelect && categorySelect.options[0]) { var allCategoriesLabel = labels['browse_categories'] || labels['all_categories'] || 'Browse Product Categories'; categorySelect.options[0].text = allCategoriesLabel; console.log('[_initializeTooltips] Updated category select option to:', allCategoriesLabel); } tooltipElements.forEach(function(element) { var tooltipText = null; var labelKey = null; // Check ID-based mapping if (element.id && tooltipMap[element.id]) { labelKey = tooltipMap[element.id]; tooltipText = labels[labelKey]; } // Check class-based mapping if (!tooltipText) { for (var key in tooltipMap) { if (element.classList.contains(key)) { labelKey = tooltipMap[key]; tooltipText = labels[labelKey]; break; } } } // Check href-based mapping if (!tooltipText && element.href) { for (var i = 0; i < hrefPatterns.length; i++) { if (hrefPatterns[i].pattern.test(element.href)) { labelKey = hrefPatterns[i].labelKey; tooltipText = labels[labelKey]; break; } } } // Fallback: use data-bs-title if no label found if (!tooltipText) { tooltipText = element.getAttribute('data-bs-title'); } if (tooltipText) { element.setAttribute('title', tooltipText); console.log('[_initializeTooltips] Set title on', element.id || element.className, '→', tooltipText, '(label key:', labelKey, ')'); } else { console.warn('[_initializeTooltips] No tooltip text found for', element.id || element.className); } }); }, _showConfirmation: function(message, onConfirm, onCancel) { var self = this; // Get current labels - may be updated by checkout_labels.js endpoint var labels = this._getLabels(); console.log('[_showConfirmation] Using labels:', labels); // Create modal backdrop var backdrop = document.createElement('div'); backdrop.className = 'modal-backdrop fade show'; backdrop.style.zIndex = '9998'; // Create modal var modal = document.createElement('div'); modal.className = 'modal fade show d-block'; modal.style.zIndex = '9999'; modal.innerHTML = ''; document.body.appendChild(backdrop); document.body.appendChild(modal); var closeModal = function() { modal.classList.remove('show'); backdrop.classList.remove('show'); setTimeout(function() { if (modal.parentNode) modal.parentNode.removeChild(modal); if (backdrop.parentNode) backdrop.parentNode.removeChild(backdrop); }, 150); }; document.getElementById('modal-confirm').addEventListener('click', function() { closeModal(); if (onConfirm) onConfirm(); }); document.getElementById('modal-cancel').addEventListener('click', function() { closeModal(); if (onCancel) onCancel(); }); document.getElementById('modal-close-x').addEventListener('click', function() { closeModal(); if (onCancel) onCancel(); }); }, _attachEventListeners: function() { var self = this; // Adjust quantity step based on UoM category // Categories without decimals (per unit): "Unit", "Units", etc. // Categories with decimals: "Weight", "Volume", "Length", etc. var unitInputs = document.querySelectorAll('.product-qty'); console.log('=== ADJUSTING QUANTITY STEPS ==='); console.log('Found ' + unitInputs.length + ' quantity inputs'); for (var j = 0; j < unitInputs.length; j++) { var form = unitInputs[j].closest('.add-to-cart-form'); var uomCategory = form.getAttribute('data-uom-category') || 'Unit'; // If category is "Unit" (English) or "Units" (plural), use step=1 (no decimals) // If other category (Weight, Volume, etc.), use step=0.1 (with decimals) var isUnitCategory = /^unit/i.test(uomCategory) || /^unidad/i.test(uomCategory); if (isUnitCategory) { unitInputs[j].step = '1'; unitInputs[j].min = '1'; unitInputs[j].value = '1'; unitInputs[j].dataset.isUnit = 'true'; console.log('Input #' + j + ': UoM="' + uomCategory + '" → step=1 (integer)'); } else { // Para peso, volumen, etc. unitInputs[j].step = '0.1'; unitInputs[j].min = '0.1'; unitInputs[j].value = '1'; unitInputs[j].dataset.isUnit = 'false'; console.log('Input #' + j + ': UoM="' + uomCategory + '" → step=0.1 (decimal)'); } } console.log('=== END ADJUSTING QUANTITY STEPS ==='); // Botones + y - para aumentar/disminuir cantidad // Helper function to round decimals correctly function roundDecimal(value, decimals) { var factor = Math.pow(10, decimals); return Math.round(value * factor) / factor; } // Remove old listeners by cloning elements (to avoid duplication) var decreaseButtons = document.querySelectorAll('.qty-decrease'); for (var k = 0; k < decreaseButtons.length; k++) { var newBtn = decreaseButtons[k].cloneNode(true); decreaseButtons[k].parentNode.replaceChild(newBtn, decreaseButtons[k]); } var increaseButtons = document.querySelectorAll('.qty-increase'); for (var k = 0; k < increaseButtons.length; k++) { var newBtn = increaseButtons[k].cloneNode(true); increaseButtons[k].parentNode.replaceChild(newBtn, increaseButtons[k]); } // Ahora asignar nuevos listeners decreaseButtons = document.querySelectorAll('.qty-decrease'); for (var k = 0; k < decreaseButtons.length; k++) { decreaseButtons[k].addEventListener('click', function(e) { e.preventDefault(); var productId = this.getAttribute('data-product-id'); var input = document.getElementById('qty_' + productId); if (!input) return; var step = parseFloat(input.step) || 1; var currentValue = parseFloat(input.value) || 0; var min = parseFloat(input.min) || 0; var newValue = Math.max(min, roundDecimal(currentValue - step, 1)); // Si es unidad, mostrar como entero if (input.dataset.isUnit === 'true') { input.value = Math.floor(newValue); } else { input.value = newValue; } }); } increaseButtons = document.querySelectorAll('.qty-increase'); for (var k = 0; k < increaseButtons.length; k++) { increaseButtons[k].addEventListener('click', function(e) { e.preventDefault(); var productId = this.getAttribute('data-product-id'); var input = document.getElementById('qty_' + productId); if (!input) return; var step = parseFloat(input.step) || 1; var currentValue = parseFloat(input.value) || 0; var newValue = roundDecimal(currentValue + step, 1); // Si es unidad, mostrar como entero if (input.dataset.isUnit === 'true') { input.value = Math.floor(newValue); } else { input.value = newValue; } }); } // Botones de agregar al carrito var buttons = document.querySelectorAll('.add-to-cart-btn'); for (var i = 0; i < buttons.length; i++) { buttons[i].addEventListener('click', function(e) { e.preventDefault(); var form = this.closest('.add-to-cart-form'); var productId = form.getAttribute('data-product-id'); var productName = form.getAttribute('data-product-name') || 'Product'; var productPrice = parseFloat( form.getAttribute('data-product-price')) || 0; var quantityInput = form.querySelector('.product-qty'); var quantity = quantityInput ? parseFloat(quantityInput.value) : 1; console.log('Adding:', { productId: productId, productName: productName, productPrice: productPrice, quantity: quantity }); if (quantity > 0) { self._addToCart(productId, productName, productPrice, quantity); } else { var labels = self._getLabels(); self._showNotification(labels.invalid_quantity || 'Please enter a valid quantity', 'warning'); } }); } // Button to save cart as draft (in My Cart header) var savCartBtn = document.getElementById('save-cart-btn'); if (savCartBtn) { // Remove old listeners by cloning var savCartBtnNew = savCartBtn.cloneNode(true); savCartBtn.parentNode.replaceChild(savCartBtnNew, savCartBtn); savCartBtnNew.addEventListener('click', function(e) { e.preventDefault(); self._saveCartAsDraft(); }); } // Button to reload from draft (in My Cart header) var reloadCartBtn = document.getElementById('reload-cart-btn'); if (reloadCartBtn) { // Remove old listeners by cloning var reloadCartBtnNew = reloadCartBtn.cloneNode(true); reloadCartBtn.parentNode.replaceChild(reloadCartBtnNew, reloadCartBtn); reloadCartBtnNew.addEventListener('click', function(e) { e.preventDefault(); self._loadDraftCart(); }); } // Button to save as draft var saveBtn = document.getElementById('save-order-btn'); if (saveBtn) { saveBtn.addEventListener('click', function(e) { e.preventDefault(); self._saveOrderDraft(); }); } // Confirm order button var confirmBtn = document.getElementById('confirm-order-btn'); if (confirmBtn) { confirmBtn.addEventListener('click', function(e) { e.preventDefault(); self._confirmOrder(); }); } }, _addToCart: function(productId, productName, productPrice, quantity) { console.log('_addToCart called with:', { productId: productId, productName: productName, productPrice: productPrice, quantity: quantity }); if (!this.cart[productId]) { this.cart[productId] = { name: productName, price: productPrice, qty: 0 }; } this.cart[productId].qty += quantity; console.log('Product added:', productId, this.cart[productId]); this._saveCart(); this._updateCartDisplay(); // Show animation instead of alert this._showAddedAnimation(productName); // Use aria-live to announce to screen readers var liveRegion = document.getElementById('cart-items-container'); if (liveRegion) { liveRegion.setAttribute('aria-live', 'assertive'); setTimeout(function() { liveRegion.setAttribute('aria-live', 'polite'); }, 1000); } }, _showAddedAnimation: function(productName) { var self = this; // Create toast element var toast = document.createElement('div'); toast.className = 'toast-notification'; // Get translated "added to cart" label from server var labels = this._getLabels(); var addedMsg = labels.added_to_cart || 'added to cart'; toast.innerHTML = ' ' + productName + ' ' + addedMsg; document.body.appendChild(toast); // Force reflow to activate animation toast.offsetHeight; toast.classList.add('show'); // Remove after 3 seconds setTimeout(function() { toast.classList.remove('show'); setTimeout(function() { document.body.removeChild(toast); }, 300); }, 3000); }, /** * Get default translated labels (fallback if JSON fails to parse) */ _getDefaultLabels: function() { return { confirmation: 'Confirmation', cancel: 'Cancel', confirm: 'Confirm', empty_cart: 'Your cart is empty', save_draft_confirm: 'Are you sure you want to save this cart as draft? Items to save: ', save_draft_reload: 'You will be able to reload this cart later.', error_save_draft: 'Error saving cart', save_cart: 'Save Cart', reload_cart: 'Reload Cart', proceed_to_checkout: 'Proceed to Checkout', remove_item: 'Remove Item', confirm_order: 'Confirm Order', back_to_cart: 'Back to Cart', all_categories: 'All categories', // Draft modal labels draft_already_exists: 'Draft Already Exists', draft_exists_message: 'A saved draft already exists for this week.', draft_two_options: 'You have two options:', draft_option1_title: 'Option 1: Merge with Existing Draft', draft_option1_desc: 'Combine your current cart with the existing draft.', draft_existing_items: 'Existing draft has', draft_current_items: 'Current cart has', draft_items_count: 'item(s)', draft_merge_note: 'Products will be merged by adding quantities. If a product exists in both, quantities will be combined.', draft_option2_title: 'Option 2: Replace with Current Cart', draft_option2_desc: 'Delete the old draft and save only the current cart items.', draft_replace_warning: 'The existing draft will be permanently deleted.', draft_merge_btn: 'Merge', draft_replace_btn: 'Replace' }; }, /** * Update DOM elements with translated labels */ _updateDOMLabels: function(labels) { console.log('[UPDATE_LABELS] Starting DOM update with labels:', labels); // Map of element ID to label key var elementLabelMap = { 'label-home-delivery': 'home_delivery', 'label-delivery-information': 'delivery_information', 'label-important': 'important', 'label-confirm-warning': 'confirm_order_warning' }; // Update each element for (var elementId in elementLabelMap) { var element = document.getElementById(elementId); var labelKey = elementLabelMap[elementId]; var translatedText = labels[labelKey]; console.log('[UPDATE_LABELS] Element:', elementId, '| Exists:', !!element, '| Label Key:', labelKey, '| Translated:', translatedText); if (element && translatedText) { var oldText = element.textContent; element.textContent = translatedText; console.log('[UPDATE_LABELS] ✅ Updated #' + elementId + ': "' + oldText + '" → "' + translatedText + '"'); } else if (!element) { console.log('[UPDATE_LABELS] ❌ Element not found: #' + elementId); } else if (!translatedText) { console.log('[UPDATE_LABELS] ❌ Label not found: ' + labelKey + ' (available keys: ' + Object.keys(labels).join(', ') + ')'); } } // Update delivery day text if available if (window.groupOrderShop && window.groupOrderShop.labels && window.groupOrderShop.labels.delivery_info_template) { var deliveryDayText = document.getElementById('delivery-day-text'); console.log('[UPDATE_LABELS] Delivery day text element exists:', !!deliveryDayText); if (deliveryDayText) { // Get delivery data from window.deliveryData first, then fallback to attributes var pickupDayIndex = ''; var pickupDate = ''; var deliveryNotice = ''; if (window.deliveryData) { console.log('[UPDATE_LABELS] Using window.deliveryData:', window.deliveryData); pickupDayIndex = window.deliveryData.pickupDay || ''; pickupDate = window.deliveryData.pickupDate || ''; deliveryNotice = window.deliveryData.deliveryNotice || ''; } else { console.log('[UPDATE_LABELS] window.deliveryData not found, using data attributes'); var wrap = document.getElementById('wrap'); pickupDayIndex = wrap ? wrap.getAttribute('data-pickup-day') : ''; pickupDate = wrap ? wrap.getAttribute('data-pickup-date') : ''; deliveryNotice = wrap ? wrap.getAttribute('data-delivery-notice') : ''; } // Normalize: convert "undefined" strings and null to empty for processing if (pickupDayIndex === 'undefined' || pickupDayIndex === null) pickupDayIndex = ''; if (pickupDate === 'undefined' || pickupDate === null) pickupDate = ''; if (deliveryNotice === 'undefined' || deliveryNotice === null) deliveryNotice = ''; console.log('[UPDATE_LABELS] Delivery data (final):', { pickupDayIndex: pickupDayIndex, pickupDate: pickupDate, deliveryNotice: deliveryNotice }); // Day names mapping var dayNames = { '0': 'Monday', '1': 'Tuesday', '2': 'Wednesday', '3': 'Thursday', '4': 'Friday', '5': 'Saturday', '6': 'Sunday' }; // Get translated day names if available if (window.groupOrderShop && window.groupOrderShop.day_names) { dayNames = window.groupOrderShop.day_names; } // Get the day name from index var pickupDayName = pickupDayIndex && dayNames[pickupDayIndex] ? dayNames[pickupDayIndex] : pickupDayIndex; // Build message from template var msg = window.groupOrderShop.labels.delivery_info_template; msg = msg.replace('{pickup_day}', pickupDayName); msg = msg.replace('{pickup_date}', pickupDate); console.log('[UPDATE_LABELS] Built delivery message:', msg); // Build final HTML output var htmlOutput = msg; if (deliveryNotice) { // Replace newlines with
tags for HTML display htmlOutput = msg.replace(/\n/g, '
') + '

' + deliveryNotice.replace(/\n/g, '
'); console.log('[UPDATE_LABELS] Final HTML with notice:', htmlOutput); } else { htmlOutput = msg.replace(/\n/g, '
'); } deliveryDayText.innerHTML = htmlOutput; console.log('[UPDATE_LABELS] ✅ Updated delivery day text with translated template'); } } else { console.log('[UPDATE_LABELS] ❌ delivery_info_template label not found'); } }, _updateCartDisplay: function() { var cartContainer = document.getElementById('cart-items-container'); if (!cartContainer) return; // Get labels first before using them var labels = this._getLabels(); if (Object.keys(this.cart).length === 0) { cartContainer.innerHTML = '

' + (labels.empty_cart || 'This order\'s cart is empty.') + '

'; return; } var html = '
'; var total = 0; var self = this; var removeItemLabel = labels.remove_item || 'Remove Item'; Object.keys(this.cart).forEach(function(productId) { var item = self.cart[productId]; var subtotal = item.qty * item.price; total += subtotal; html += '
' + '
' + '
' + self.escapeHtml(item.name) + '
' + '
' + '
' + '' + parseFloat(item.qty).toFixed(1) + ' x €' + item.price.toFixed(2) + '' + '
' + '€' + subtotal.toFixed(2) + '' + '' + '
' + '
' + '
'; }); html += '
'; html += '
' + 'Total: €' + total.toFixed(2) + '
'; cartContainer.innerHTML = html; // Reassign event listeners to remove buttons var removeButtons = cartContainer.querySelectorAll( '.remove-from-cart'); for (var i = 0; i < removeButtons.length; i++) { removeButtons[i].addEventListener('click', function(e) { e.preventDefault(); var productId = this.getAttribute('data-product-id'); self._removeFromCart(productId); }); } // Reinitialize tooltips for newly added remove buttons console.log('[_updateCartDisplay] Reinitializing tooltips for remove buttons'); var tooltipsToInit = cartContainer.querySelectorAll('[data-bs-toggle="tooltip"]'); tooltipsToInit.forEach(function(elem) { var bsTooltip = window.bootstrap?.Tooltip; if (bsTooltip) { // Destroy existing tooltip if any var existingTooltip = bsTooltip.getInstance(elem); if (existingTooltip) { existingTooltip.dispose(); } // Create new tooltip with updated title new bsTooltip(elem); console.log('[_updateCartDisplay] Tooltip reinitialized for element:', elem.getAttribute('title')); } }); }, _removeFromCart: function(productId) { console.log('Removing from cart:', productId); var removedItem = this.cart[productId]; var itemName = removedItem ? removedItem.name : 'Product'; delete this.cart[productId]; this._saveCart(); // Use aria-live to announce to screen readers var liveRegion = document.getElementById('cart-items-container'); if (liveRegion) { liveRegion.setAttribute('aria-live', 'assertive'); setTimeout(function() { liveRegion.setAttribute('aria-live', 'polite'); }, 1000); } this._updateCartDisplay(); }, _saveCartAsDraft: function() { console.log('Saving cart as draft'); var self = this; var items = []; Object.keys(this.cart).forEach(function(productId) { var item = self.cart[productId]; items.push({ product_id: productId, product_name: item.name, quantity: item.qty, product_price: item.price }); }); if (items.length === 0) { var labels = self._getLabels(); self._showNotification(labels.empty_cart || 'Your cart is empty', 'warning'); return; } // Call save-order endpoint which handles draft conflicts self._executeSaveCartAsDraft(items); }, _executeSaveCartAsDraft: function(items) { var self = this; var orderData = { order_id: this.orderId, items: items }; var xhr = new XMLHttpRequest(); xhr.open('POST', '/eskaera/save-order', true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onload = function() { if (xhr.status === 200) { try { var data = JSON.parse(xhr.responseText); var labels = self._getLabels(); if (data.success) { var successMsg = (labels.draft_saved_success || 'Cart saved as draft successfully') + ' (Order ID: ' + data.sale_order_id + ')'; self._showNotification('✓ ' + successMsg, 'success', 5000); } else if (data.existing_draft) { // A draft already exists - show modal with merge/replace options self._showDraftConflictModal(data); } else { self._showNotification('Error: ' + (data.error || (labels.error_unknown || 'Unknown error')), 'danger'); } } catch (e) { console.error('Error parsing response:', e); var labels = self._getLabels(); self._showNotification(labels.error_processing_response || 'Error processing response', 'danger'); } } else { try { var errorData = JSON.parse(xhr.responseText); self._showNotification('Error ' + xhr.status + ': ' + (errorData.error || 'Request error'), 'danger'); } catch (e) { var labels = self._getLabels(); self._showNotification(labels.error_saving_draft || 'Error saving cart (HTTP ' + xhr.status + ')', 'danger'); } } }; xhr.onerror = function() { console.error('Error:', xhr); var labels = self._getLabels(); self._showNotification(labels.connection_error || 'Connection error', 'danger'); }; xhr.send(JSON.stringify(orderData)); }, _loadDraftCart: function() { console.log('Loading draft cart (manual)'); var self = this; // Check if cart has items var hasItems = Object.keys(this.cart).length > 0; if (hasItems) { // Ask for confirmation if cart has items var labels = this._getLabels(); var confirmMessage = (labels.reload_draft_confirm || 'Are you sure you want to load your last saved draft?') + '\n\n' + (labels.reload_draft_replace || 'This will replace your current cart items') + ' (' + Object.keys(this.cart).length + ' ' + (labels.items_placeholder || 'items') + ') ' + (labels.reload_draft_with || 'with the saved draft.'); self._showConfirmation(confirmMessage, function() { // User confirmed - continue with load self._executeLoadDraftCart(); }); } else { // Cart is empty, load directly without confirmation self._executeLoadDraftCart(); } }, _executeLoadDraftCart: function() { var self = this; var orderData = { order_id: this.orderId }; var xhr = new XMLHttpRequest(); xhr.open('POST', '/eskaera/load-draft', true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onload = function() { if (xhr.status === 200) { try { var data = JSON.parse(xhr.responseText); if (data.success) { // Clear current cart self.cart = {}; // Load items from draft order var items = data.items || []; items.forEach(function(item) { var productId = item.product_id; self.cart[productId] = { name: item.product_name, price: item.product_price, qty: item.quantity }; }); // Save to localStorage self._saveCart(); // Update display self._updateCartDisplay(); var labels = self._getLabels(); var loadMsg = (labels.draft_loaded_success || 'Loaded') + ' ' + items.length + ' items from draft order (ID: ' + data.sale_order_id + ')'; self._showNotification('✓ ' + loadMsg, 'success', 5000); } else { self._showNotification('Error: ' + (data.error || (labels.error_unknown || 'Unknown error')), 'danger'); } } catch (e) { console.error('Error parsing response:', e); var labels = self._getLabels(); self._showNotification(labels.error_processing_response || 'Error processing response', 'danger'); } } else { try { var errorData = JSON.parse(xhr.responseText); self._showNotification('Error ' + xhr.status + ': ' + (errorData.error || 'Request error'), 'danger'); } catch (e) { var labels = self._getLabels(); self._showNotification(labels.error_loading_draft || 'Error loading draft (HTTP ' + xhr.status + ')', 'danger'); } } }; xhr.onerror = function() { console.error('Error:', xhr); var labels = self._getLabels(); self._showNotification(labels.connection_error || 'Connection error', 'danger'); }; xhr.send(JSON.stringify(orderData)); }, _saveOrderDraft: function() { console.log('Saving order as draft:', this.orderId); var self = this; var items = []; Object.keys(this.cart).forEach(function(productId) { var item = self.cart[productId]; items.push({ product_id: productId, product_name: item.name, quantity: item.qty, product_price: item.price }); }); var orderData = { order_id: this.orderId, items: items }; var xhr = new XMLHttpRequest(); xhr.open('POST', '/eskaera/save-order', true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onload = function() { if (xhr.status === 200) { try { var data = JSON.parse(xhr.responseText); console.log('Response:', data); if (data.success) { self._showNotification('✓ Order saved as draft successfully', 'success', 5000); } else if (data.existing_draft) { // A draft already exists - show modal with merge/replace options self._showDraftConflictModal(data); } else { self._showNotification('Error: ' + (data.error || (labels.error_unknown || 'Unknown error')), 'danger'); } } catch (e) { console.error('Error parsing response:', e); self._showNotification('Error processing response', 'danger'); } } else { try { var errorData = JSON.parse(xhr.responseText); console.error('HTTP error:', xhr.status, errorData); self._showNotification('Error ' + xhr.status + ': ' + (errorData.error || 'Request error'), 'danger'); } catch (e) { console.error('HTTP error:', xhr.status, xhr.responseText); self._showNotification('Error saving order (HTTP ' + xhr.status + ')', 'danger'); } } }; xhr.onerror = function() { console.error('Error:', xhr); var labels = self._getLabels(); self._showNotification(labels.connection_error || 'Connection error', 'danger'); }; xhr.send(JSON.stringify(orderData)); }, _showDraftConflictModal: function(data) { /** * Show modal with merge/replace options for existing draft. * Uses labels from window.groupOrderShop.labels or falls back to defaults. */ var self = this; // Get labels - they should already be loaded by page init var labels = (window.groupOrderShop && window.groupOrderShop.labels) ? window.groupOrderShop.labels : self._getDefaultLabels(); console.log('[_showDraftConflictModal] Using labels:', Object.keys(labels).length, 'keys available'); var existing_items = data.existing_items || []; var current_items = data.current_items || []; var existing_draft_id = data.existing_draft_id; // Create modal HTML with inline styles (no Bootstrap needed) var modalHTML = `
${labels.draft_already_exists || 'Draft Already Exists'}

${labels.draft_exists_message || 'A draft already exists'}

${labels.draft_two_options || 'You have two options:'}

${labels.draft_option1_title || 'Option 1'}

${labels.draft_option1_desc || 'Merge with existing'}

  • ${existing_items.length} ${labels.draft_items_count || 'items'} - ${labels.draft_existing_items || 'Existing'}
  • ${current_items.length} ${labels.draft_items_count || 'items'} - ${labels.draft_current_items || 'Current'}

${labels.draft_merge_note || 'Products will be merged'}

${labels.draft_option2_title || 'Option 2'}

${labels.draft_option2_desc || 'Replace with current'}

${labels.draft_replace_warning || 'Old draft will be deleted'}

`; // Remove existing modal if any var existingModal = document.getElementById('draftConflictModal'); if (existingModal) { existingModal.remove(); } // Add modal to body document.body.insertAdjacentHTML('beforeend', modalHTML); var modalElement = document.getElementById('draftConflictModal'); // Handle close buttons document.querySelectorAll('.draft-modal-close').forEach(function(btn) { btn.addEventListener('click', function() { modalElement.remove(); }); }); // Handle merge button document.getElementById('mergeBtn').addEventListener('click', function() { var existingId = this.getAttribute('data-existing-id'); modalElement.remove(); self._executeSaveDraftWithAction('merge', existingId); }); // Handle replace button document.getElementById('replaceBtn').addEventListener('click', function() { var existingId = this.getAttribute('data-existing-id'); modalElement.remove(); self._executeSaveDraftWithAction('replace', existingId); }); // Close modal when clicking outside modalElement.addEventListener('click', function(e) { if (e.target === modalElement) { modalElement.remove(); } }); }, _executeSaveDraftWithAction: function(action, existingDraftId) { /** * Execute save draft with merge or replace action. */ var self = this; var items = []; Object.keys(this.cart).forEach(function(productId) { var item = self.cart[productId]; items.push({ product_id: productId, product_name: item.name, quantity: item.qty, product_price: item.price }); }); var orderData = { order_id: this.orderId, items: items, merge_action: action, existing_draft_id: existingDraftId }; var xhr = new XMLHttpRequest(); xhr.open('POST', '/eskaera/save-order', true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onload = function() { if (xhr.status === 200) { try { var data = JSON.parse(xhr.responseText); console.log('Response:', data); if (data.success) { // Use server-provided labels instead of hardcoding var labels = self._getLabels(); // Use the translated messages from server var msg = data.merged ? ('✓ ' + (labels.draft_merged_success || 'Draft merged successfully')) : ('✓ ' + (labels.draft_replaced_success || 'Draft replaced successfully')); self._showNotification(msg, 'success', 5000); } else { var labels = self._getLabels(); self._showNotification('Error: ' + (data.error || (labels.error_unknown || 'Unknown error')), 'danger'); } } catch (e) { console.error('Error parsing response:', e); self._showNotification('Error processing response', 'danger'); } } else { try { var errorData = JSON.parse(xhr.responseText); self._showNotification('Error ' + xhr.status + ': ' + (errorData.error || 'Request error'), 'danger'); } catch (e) { self._showNotification('Error saving order (HTTP ' + xhr.status + ')', 'danger'); } } }; xhr.onerror = function() { self._showNotification('Connection error', 'danger'); }; xhr.send(JSON.stringify(orderData)); }, _confirmOrder: function() { console.log('=== _confirmOrder started ==='); console.log('orderId:', this.orderId); // IMPORTANT: Read cart directly from localStorage, not from this.cart // because home_delivery.js updates localStorage directly var cartKey = 'eskaera_' + this.orderId + '_cart'; var storedCart = localStorage.getItem(cartKey); console.log('localStorage[' + cartKey + ']:', storedCart); // Parse cart from localStorage (more reliable than this.cart) var cart = storedCart ? JSON.parse(storedCart) : this.cart; console.log('Parsed cart from localStorage:', cart); var self = this; var items = []; Object.keys(cart).forEach(function(productId) { var item = cart[productId]; console.log('Processing cart item: productId=' + productId + ', item=', item); items.push({ product_id: productId, product_name: item.name, quantity: item.qty, unit_price: item.price }); }); console.log('Items to send to server:', items); console.log('Total items count:', items.length); // Check if home delivery is enabled var deliveryCheckbox = document.getElementById('home-delivery-checkbox'); var isDelivery = deliveryCheckbox ? deliveryCheckbox.checked : false; var orderData = { order_id: this.orderId, items: items, is_delivery: isDelivery }; var xhr = new XMLHttpRequest(); xhr.open('POST', '/eskaera/confirm', true); xhr.setRequestHeader('Content-Type', 'application/json'); console.log('Sending request to /eskaera/confirm with data:', orderData); xhr.onload = function() { if (xhr.status === 200) { try { var data = JSON.parse(xhr.responseText); console.log('Response:', data); var labels = self._getLabels(); if (data.success) { var message = data.message || (labels.order_confirmed || 'Order confirmed'); self._showNotification('✓ ' + message, 'success', 4000); // Clear cart from localStorage localStorage.removeItem('eskaera_' + self.orderId + '_cart'); console.log('Cart cleared from localStorage after confirmation'); // Save confirmation message in sessionStorage to display on eskaera page sessionStorage.setItem('confirmation_message', message); // Wait a moment before redirecting to let user see the message setTimeout(function() { window.location.href = data.redirect_url || '/eskaera'; }, 4000); } else { var unknownErrorMsg = labels.error_unknown || (labels.error_unknown || 'Unknown error'); self._showNotification('Error: ' + (data.error || unknownErrorMsg), 'danger'); } } catch (e) { console.error('Error parsing response:', e); var labels = self._getLabels(); self._showNotification(labels.error_processing_response || 'Error processing response', 'danger'); } } else { try { var errorData = JSON.parse(xhr.responseText); console.error('HTTP error:', xhr.status, errorData); self._showNotification('Error ' + xhr.status + ': ' + (errorData.error || 'Request error'), 'danger'); } catch (e) { console.error('HTTP error:', xhr.status, xhr.responseText); self._showNotification('Error confirming order (HTTP ' + xhr.status + ')', 'danger'); } } }; xhr.onerror = function() { console.error('Error:', xhr); var labels = self._getLabels(); self._showNotification(labels.connection_error || 'Connection error', 'danger'); }; xhr.send(JSON.stringify(orderData)); }, escapeHtml: function(text) { var div = document.createElement('div'); div.textContent = text; return div.innerHTML; } }; // Initialize when DOM is ready document.addEventListener('DOMContentLoaded', function() { console.log('DOM loaded'); console.log('Looking for #cart-items-container...'); console.log('Looking for #confirm-order-btn...'); var cartContainer = document.getElementById('cart-items-container'); var confirmBtn = document.getElementById('confirm-order-btn'); console.log('cart-items-container found:', !!cartContainer); console.log('confirm-order-btn found:', !!confirmBtn); if (cartContainer || confirmBtn) { console.log('Calling init()'); var result = window.groupOrderShop.init(); console.log('init() result:', result); } else { console.warn( 'No elements found to initialize cart' ); } }); // Handle confirm order buttons in portal (My Orders page) document.addEventListener('click', function(e) { if (e.target.closest('.confirm-order-btn')) { e.preventDefault(); var btn = e.target.closest('.confirm-order-btn'); var groupOrderId = btn.getAttribute('data-group-order-id'); var orderId = btn.getAttribute('data-order-id'); console.log('Confirm order clicked: order=' + orderId + ', group=' + groupOrderId); if (!groupOrderId || !orderId) { if (window.groupOrderShop && window.groupOrderShop._showNotification) { window.groupOrderShop._showNotification('Error: Missing order or group information', 'danger'); } else { alert('Error: Missing order or group information'); } return; } // Show loading state var originalText = btn.innerHTML; btn.disabled = true; btn.innerHTML = ''; // Send AJAX request to confirm the order var xhr = new XMLHttpRequest(); xhr.open('POST', '/eskaera/' + groupOrderId + '/confirm/' + orderId, true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onload = function() { btn.disabled = false; if (xhr.status === 200) { try { var response; if (typeof xhr.responseText === 'string' && xhr.responseText.trim() !== '') { response = JSON.parse(xhr.responseText); } else { response = {}; } console.log('Response from confirm endpoint:', response); // Odoo JSON-RPC wraps the result in response.result var result = response.result || response; if (result && result.success) { console.log('Order confirmed successfully: order_id=' + result.order_id); btn.innerHTML = ''; btn.classList.remove('btn-success'); btn.classList.add('btn-secondary'); btn.disabled = true; btn.title = 'Order confirmed'; // Show success notification with order_id var message = 'Order #' + result.order_id + ' confirmed successfully'; if (result.message) { message = result.message + ' (Order #' + result.order_id + ')'; } if (window.groupOrderShop && window.groupOrderShop._showNotification) { window.groupOrderShop._showNotification(message, 'success', 4000); } else { alert(message); } } else { console.error('Error confirming order:', result); btn.innerHTML = originalText; var errorMsg = 'Error: ' + (result && result.error ? result.error : (labels.error_unknown || 'Unknown error')); if (window.groupOrderShop && window.groupOrderShop._showNotification) { window.groupOrderShop._showNotification(errorMsg, 'danger'); } else { alert(errorMsg); } } } catch (e) { console.error('Error parsing response:', e); console.error('Response text was:', xhr.responseText); btn.innerHTML = originalText; var errorMsg = 'Error processing response: ' + e.message; if (window.groupOrderShop && window.groupOrderShop._showNotification) { window.groupOrderShop._showNotification(errorMsg, 'danger'); } else { alert(errorMsg); } } } else { console.error('HTTP error:', xhr.status, xhr.responseText); btn.innerHTML = originalText; var errorMsg = 'Error ' + xhr.status + ': Failed to confirm order'; if (window.groupOrderShop && window.groupOrderShop._showNotification) { window.groupOrderShop._showNotification(errorMsg, 'danger'); } else { alert(errorMsg); } } }; xhr.onerror = function() { btn.disabled = false; btn.innerHTML = originalText; var errorMsg = 'Error: Network request failed'; if (window.groupOrderShop && window.groupOrderShop._showNotification) { window.groupOrderShop._showNotification(errorMsg, 'danger'); } else { alert(errorMsg); } }; xhr.send(JSON.stringify({})); } }); // Also try to initialize after a delay in case DOM // takes longer to load setTimeout(function() { if (!window.groupOrderShop.orderId) { console.log('Reintentando init() después de delay...'); var cartContainer = document.getElementById('cart-items-container'); var confirmBtn = document.getElementById('confirm-order-btn'); if (cartContainer || confirmBtn) { console.log('Llamando a init() en delay'); var result = window.groupOrderShop.init(); console.log('init() en delay resultado:', result); } } }, 1000); })();