addons-cm/website_sale_aplicoop/views/website_templates.xml
snt 5eb039ffe0 [FIX] website_sale_aplicoop: Complete infinite scroll and search filter integration
Major fixes:
- Fix JSON body parsing in load_products_ajax with type='http' route
  * Parse JSON from request.httprequest.get_data() instead of post params
  * Correctly read page, search, category from JSON request body

- Fix search and category filter combination
  * Use intersection (&) instead of replacement to preserve both filters
  * Now respects search AND category simultaneously

- Integrate realtime_search.js with infinite_scroll.js
  * Add resetWithFilters() method to reset scroll to page 1 with new filters
  * When search/category changes, reload products from server
  * Clear grid and load fresh results

- Fix pagination reset logic
  * Set currentPage = 0 in resetWithFilters() so loadNextPage() increments to 1
  * Prevents loading empty page 2 when resetting filters

Results:
 Infinite scroll loads all pages correctly (1, 2, 3...)
 Search filters work across all products (not just loaded)
 Category filters work correctly
 Search AND category filters work together
 Page resets to 1 when filters change
2026-02-17 01:10:47 +01:00

1308 lines
80 KiB
XML

<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<!-- Template: Group Orders Page (Eskaera) -->
<template id="eskaera_page" name="Eskaera Page">
<t t-call="website.layout">
<div id="wrap" class="eskaera-page oe_structure oe_empty" data-name="Eskaera Orders">
<div class="container">
<div class="row">
<div class="col-lg-12">
<h1>Available Orders</h1>
<p class="text-muted" role="status">Browse and select an order to view its products.</p>
</div>
</div>
<div class="row mt-4">
<div class="col-lg-12">
<!-- Editable area: Above orders list -->
<div class="oe_structure oe_empty" data-name="Before Orders" />
<t t-if="active_orders">
<div
class="eskaera-orders eskaera-orders-grid"
>
<t
t-foreach="active_orders"
t-as="order"
>
<div
class="eskaera-order-card-wrapper"
>
<div class="eskaera-order-card">
<div class="card-body">
<!-- Product count badge - top right corner -->
<div
class="position-absolute order-badge-position"
>
<span
class="badge bg-primary d-flex align-items-center gap-2 order-badge-custom"
>
<i
class="fa fa-shopping-bag"
/>
<strong><t
t-esc="order.available_products_count"
/></strong>
</span>
</div>
<!-- Order header with image and name (as link) -->
<a
t-attf-href="/eskaera/{{ order.id }}"
class="eskaera-order-card-link"
t-attf-aria-label="View products for order {{ order.name }}"
>
<div
class="card-header-top d-flex gap-2 align-items-center order-header-margin"
>
<t
t-set="image_to_show"
t-value="order.image or (order.group_ids[0].image_1920 if order.group_ids else False)"
/>
<t
t-if="image_to_show"
>
<img
t-att-src="image_data_uri(image_to_show)"
alt="Order image"
class="order-thumbnail-sm"
/>
</t>
<div
class="flex-grow-1"
>
<h5
class="card-title mb-1"
><t
t-esc="order.name"
/></h5>
<t
t-if="order.description"
>
<p
class="text-muted small mb-0 order-desc-text"
>
<t
t-esc="(order.description[:150] + '...') if len(order.description) > 200 else order.description"
/>
</p>
</t>
</div>
</div>
</a>
<!-- Metadata section - outside link -->
<div
class="card-meta-compact mt-3"
>
<table
class="meta-table"
>
<tbody>
<!-- Order Type - ALWAYS SHOW ROW -->
<tr
class="meta-row"
>
<td
class="meta-label-cell"
t-if="order.type"
>
<span
>Order Type</span>
</td>
<td
class="meta-value-cell"
>
<t
t-if="order.type"
>
<t
t-esc="dict(order.fields_get('type', ['selection'])['type']['selection']).get(order.type, order.type)"
/>
</t>
</td>
</tr>
<!-- Period - ALWAYS SHOW ROW -->
<tr
class="meta-row"
>
<td
class="meta-label-cell"
t-if="order.period"
>
<span
>Order Period</span>
</td>
<td
class="meta-value-cell"
>
<t
t-if="order.period"
>
<t
t-esc="dict(order.fields_get('period', ['selection'])['period']['selection']).get(order.period, order.period)"
/>
</t>
</td>
</tr>
<!-- Cutoff Day - ALWAYS SHOW ROW -->
<tr
class="meta-row"
>
<td
class="meta-label-cell"
t-if="order.cutoff_day"
>
<span
>Cutoff Day</span>
</td>
<td
class="meta-value-cell"
>
<t
t-if="order.cutoff_day"
>
<t
t-esc="day_names[int(order.cutoff_day) % 7]"
/> - <t
t-esc="order.cutoff_date.strftime('%d/%m')"
/>
</t>
</td>
</tr>
<tr
class="meta-row"
>
<td
class="meta-label-cell"
t-if="order.pickup_day and order.pickup_date"
>
<span
>Pickup Day</span>
</td>
<td
class="meta-value-cell"
>
<t
t-if="order.pickup_day and order.pickup_date"
>
<t
t-esc="day_names[int(order.pickup_day) % 7]"
/> - <t
t-esc="order.pickup_date.strftime('%d/%m')"
/>
</t>
</td>
</tr>
<!-- End Date - ALWAYS SHOW ROW -->
<tr
class="meta-row"
>
<td
class="meta-label-cell"
t-if="order.end_date"
>
<span
>Open until</span>
</td>
<td
class="meta-value-cell"
>
<t
t-if="order.end_date"
>
<t
t-esc="order.end_date.strftime('%d/%m/%Y')"
/>
</t>
</td>
</tr>
<!-- Home Delivery - ALWAYS SHOW ROW -->
<tr
class="meta-row"
>
<td
class="meta-label-cell"
>
<span
>Home Delivery</span>
</td>
<td
class="meta-value-cell"
>
<t
t-if="order.home_delivery"
>
<span
class="badge bg-success"
>Yes</span>
</t>
<t
t-else=""
>
<span
class="badge bg-warning"
>No</span>
</t>
</td>
</tr>
<!-- Delivery Date - ALWAYS SHOW ROW -->
<tr
class="meta-row"
>
<td
class="meta-label-cell"
t-if="order.delivery_date and order.home_delivery"
>
<span
>Delivery</span>
</td>
<td
class="meta-value-cell"
>
<t
t-if="order.delivery_date and order.home_delivery"
>
<t
t-esc="day_names[order.delivery_date.weekday()]"
/> - <t
t-esc="order.delivery_date.strftime('%d/%m')"
/>
</t>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Browse button - outside link, properly centered -->
<a
t-attf-href="/eskaera/{{ order.id }}"
class="btn btn-primary btn-sm"
aria-label="Browse products for {{ order.name }}"
>
<i
class="fa fa-shopping-bag"
aria-hidden="true"
t-translation="off"
/>
<span
>Browse Products</span>
</a>
</div>
</div>
</div>
</t>
</div>
</t>
<t t-else="">
<div class="eskaera-empty-state">
<div class="alert alert-info" role="status" aria-live="polite">
<p>No group orders available this week.</p>
</div>
</div>
</t>
<!-- Editable area: Below orders list -->
<div
class="oe_structure oe_empty mt-4"
data-name="After Orders"
/>
</div>
</div>
</div>
</div>
<!-- Load translated labels for category selector -->
<script
type="text/javascript"
><![CDATA[
(function() {
'use strict';
console.log('[eskaera_page] Loading translated labels for category selector');
// Fetch translated labels from endpoint
fetch('/eskaera/labels', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': odoo.csrf_token || ''
}
})
.then(response => response.json())
.then(labels => {
console.log('[eskaera_page] Labels received:', labels);
// Update category selector first option text
var categorySelect = document.getElementById('realtime-category-select');
if (categorySelect && categorySelect.options[0] && labels && labels.all_categories) {
categorySelect.options[0].text = labels.all_categories;
console.log('[eskaera_page] Updated category selector to:', labels.all_categories);
} else {
console.log('[eskaera_page] Could not update category selector');
console.log(' categorySelect:', !!categorySelect);
console.log(' categorySelect.options[0]:', categorySelect ? !!categorySelect.options[0] : false);
console.log(' labels:', !!labels);
console.log(' labels.all_categories:', labels ? labels.all_categories : 'N/A');
}
})
.catch(error => {
console.error('[eskaera_page] Error fetching labels:', error);
});
})();
]]></script>
</t>
</template>
<!-- Small QWeb snippets used to render translated confirmation strings
Rendered via ir.ui.view._render_template() with lang in context
to ensure server-side translation regardless of call stack. -->
<template id="confirm_message_snippet" name="Confirm Message Snippet">
<t t-esc="_('Thank you! Your order has been confirmed.')" />
</template>
<template id="confirm_pickup_label_snippet" name="Confirm Pickup Label Snippet">
<t t-esc="_('Pickup Day')" />
</template>
<!-- Shared template: Order Header -->
<template id="order_header" name="Order Header">
<div t-att-class="header_class or 'eskaera-order-header'">
<div class="d-flex gap-5 align-items-center mb-4">
<t t-set="image_to_show" t-value="group_order.image or (group_order.group_ids[0].image_1920 if group_order.group_ids else False)" />
<t t-if="image_to_show">
<img t-att-src="image_data_uri(image_to_show)" alt="Order image" class="order-thumbnail-md" />
</t>
<div class="flex-grow-1">
<h1 class="mb-2"><t t-esc="header_title or group_order.name" /></h1>
<t t-if="group_order.description">
<p class="text-muted mb-0 order-desc-full"><t t-esc="group_order.description" /></p>
</t>
</div>
</div>
</div>
</template>
<!-- Template: Group Order Shop (Eskaera) -->
<template id="eskaera_shop" name="Eskaera Shop">
<t t-call="website.layout">
<div id="wrap" class="eskaera-shop-page oe_structure oe_empty" data-name="Eskaera Shop">
<div class="container">
<!-- Order Header Info Panel -->
<div class="row mb-4">
<div class="col-lg-12">
<t t-call="website_sale_aplicoop.order_header">
<t t-set="header_class" t-value="'eskaera-order-header'" />
</t>
<div class="eskaera-order-header">
<div class="order-info-grid">
<div class="info-item">
<span t-att-class="'info-label'">Consumer Groups</span>
<span class="info-value"><t t-esc="', '.join(group_order.group_ids.mapped('name'))" /></span>
</div>
<t t-if="group_order.cutoff_day">
<div class="info-item">
<span t-att-class="'info-label'">Cutoff Day</span>
<span class="info-value"><t t-esc="day_names[int(group_order.cutoff_day) % 7]" /> (<t t-esc="group_order.cutoff_date.strftime('%d/%m/%Y')" />)</span>
</div>
</t>
<t t-if="group_order.pickup_day">
<div class="info-item">
<span
t-att-class="'info-label'"
>Store Pickup Day</span>
<span class="info-value"><t
t-esc="day_names[int(group_order.pickup_day) % 7]"
/> (<t
t-esc="group_order.pickup_date.strftime('%d/%m/%Y')"
/>)</span>
</div>
</t>
<t
t-if="group_order.delivery_date and group_order.home_delivery"
>
<div class="info-item">
<span
t-att-class="'info-label'"
>Home Delivery Day</span>
<span class="info-value"><t
t-esc="day_names[group_order.delivery_date.weekday()]"
/> (<t
t-esc="group_order.delivery_date.strftime('%d/%m/%Y')"
/>)</span>
</div>
</t>
<t t-if="group_order.start_date">
<div class="info-item">
<span
t-att-class="'info-label'"
>Open From</span>
<span class="info-value"><t
t-esc="group_order.start_date.strftime('%d/%m/%Y')"
/></span>
</div>
</t>
<t t-if="group_order.end_date">
<div class="info-item">
<span
t-att-class="'info-label'"
>Open Until</span>
<span class="info-value"><t
t-esc="group_order.end_date.strftime('%d/%m/%Y')"
/></span>
</div>
</t>
</div>
</div>
</div>
</div>
<!-- Search and Filter Bar (Full Width, Above Products/Cart) -->
<div class="mb-3" id="realtimeSearch-filters">
<div class="row g-2">
<div class="col-md-7">
<!-- CRITICAL: This input is NOT inside a form to prevent Odoo from transforming it -->
<!-- It must remain a pure HTML input element for realtime_search.js to detect value changes -->
<input type="text" id="realtime-search-input" class="form-control realtime-search-box search-input-styled" placeholder="Search products..." autocomplete="off" />
</div>
<div class="col-md-5">
<select name="category" id="realtime-category-select" class="form-select">
<option value="">Browse Product Categories</option>
<!-- Macro para renderizar categorías recursivamente -->
<t t-call="website_sale_aplicoop.category_hierarchy_options">
<t t-set="categories" t-value="category_hierarchy" />
<t t-set="depth" t-value="0" />
</t>
</select>
</div>
</div>
<t t-if="available_tags">
<div class="row mt-3">
<div class="col-12">
<div
id="tag-filter-container"
class="tag-filter-badges"
>
<t
t-foreach="available_tags"
t-as="tag"
>
<t t-if="tag['color']">
<button
type="button"
class="badge tag-filter-badge"
t-att-data-tag-id="tag['id']"
t-att-data-tag-name="tag['name']"
t-att-data-tag-color="tag['color']"
t-attf-style="background-color: {{ tag['color'] }} !important; border-color: {{ tag['color'] }} !important; color: #ffffff !important;"
data-toggle="tag-filter"
>
<span
t-esc="tag['name']"
/> (<span
class="tag-count"
t-esc="tag['count']"
/>)
</button>
</t>
<t t-else="">
<button
type="button"
class="badge tag-filter-badge tag-use-theme-color"
t-att-data-tag-id="tag['id']"
t-att-data-tag-name="tag['name']"
data-tag-color=""
data-toggle="tag-filter"
>
<span
t-esc="tag['name']"
/> (<span
class="tag-count"
t-esc="tag['count']"
/>)
</button>
</t>
</t>
</div>
</div>
</div>
</t>
</div>
<!-- Products and Cart Row -->
<div class="row g-2">
<!-- Products Column -->
<div class="col-lg-9">
<!-- Editable area: Above search/filter -->
<div class="oe_structure oe_empty" data-name="Before Products Filter" />
<t t-if="products">
<div class="products-grid" id="products-grid">
<t t-call="website_sale_aplicoop.eskaera_shop_products" />
</div>
<!-- Infinite scroll container -->
<t t-if="lazy_loading_enabled and has_next">
<div id="infinite-scroll-container" class="row mt-4">
<div class="col-12 text-center">
<!-- Spinner (hidden by default) -->
<div id="loading-spinner" class="d-none" style="padding: 20px;">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-2">Loading more products...</p>
</div>
<!-- Fallback Load More button (shown if auto-load fails) -->
<button
id="load-more-btn"
class="btn btn-primary btn-lg d-none"
t-attf-data-page="{{ current_page + 1 }}"
t-attf-data-order-id="{{ group_order.id }}"
t-attf-data-search="{{ search_query }}"
t-attf-data-category="{{ selected_category }}"
t-attf-data-per-page="{{ per_page }}"
aria-label="Load more products"
style="display: none;"
>
<i class="fa fa-download me-2" />Load More Products
</button>
</div>
</div>
<!-- Data attributes for infinite scroll configuration -->
<div id="eskaera-config"
t-attf-data-order-id="{{ group_order.id }}"
t-attf-data-search="{{ search_query }}"
t-attf-data-category="{{ selected_category }}"
t-attf-data-per-page="{{ per_page }}"
t-attf-data-current-page="{{ current_page }}"
class="d-none">
</div>
</t>
</t>
<t t-else="">
<div class="alert alert-warning" role="status" aria-live="polite">
<p>No products available in this order.</p>
</div>
</t>
<!-- Editable area: Below products list -->
<div
class="oe_structure oe_empty mt-4"
data-name="After Products"
/>
</div>
<!-- Cart Column -->
<div class="col-lg-3">
<div
class="card sticky-top cart-sticky-position"
aria-label="Cart Summary"
>
<div
class="card-header d-flex justify-content-between align-items-center gap-1"
>
<h6 class="mb-0 cart-title-sm" id="cart-title">My Cart</h6>
<div class="btn-group cart-btn-group gap-0" role="group">
<button type="button" class="btn btn-primary cart-btn-compact" id="save-cart-btn" t-attf-data-order-id="{{ group_order.id }}" data-bs-title="Save Cart" data-bs-toggle="tooltip">
<i class="fa fa-save cart-icon-size" />
</button>
<button
type="button"
class="btn btn-info cart-btn-compact"
id="reload-cart-btn"
t-attf-data-order-id="{{ group_order.id }}"
data-bs-title="Reload Cart"
data-bs-toggle="tooltip"
>
<i
class="fa fa-refresh cart-icon-size"
/>
</button>
<a
t-attf-href="/eskaera/{{ group_order.id }}/checkout"
class="btn btn-success cart-btn-compact"
aria-label="Proceed to checkout"
data-bs-title="Proceed to Checkout"
data-bs-toggle="tooltip"
>
<i
class="fa fa-check cart-icon-size"
aria-hidden="true"
/>
</a>
</div>
</div>
<div class="card-body cart-body-lg" id="cart-items-container" t-attf-data-order-id="{{ group_order.id }}" aria-labelledby="cart-title" aria-live="polite" aria-relevant="additions removals">
<p class="text-muted">This order's cart is empty</p>
</div>
<div class="card-footer bg-white text-center">
<a t-attf-href="/eskaera/{{ group_order.id }}/checkout" class="btn btn-success checkout-btn-lg" data-bs-title="Proceed to Checkout" data-bs-toggle="tooltip">
Proceed to Checkout
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Scripts (in dependency order) -->
<!-- Load i18n_manager first - fetches translations from server -->
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/i18n_manager.js" />
<!-- Keep legacy helpers for backwards compatibility -->
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/i18n_helpers.js" />
<!-- Main shop functionality (depends on i18nManager) -->
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/website_sale.js" />
<!-- UI enhancements -->
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/checkout_labels.js" />
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/home_delivery.js" />
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/realtime_search.js" />
<!-- Infinite scroll for lazy loading products -->
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/infinite_scroll.js" />
<!-- Initialize tooltips using native title attribute -->
<script type="text/javascript">
(function() {
'use strict';
function initializeTooltips() {
console.log('[TOOLTIP] Initializing tooltips using native title attribute...');
var tooltipElements = document.querySelectorAll('[data-bs-toggle="tooltip"]');
console.log('[TOOLTIP] Found', tooltipElements.length, 'tooltip elements');
var successCount = 0;
tooltipElements.forEach(function(element) {
var title = element.getAttribute('data-bs-title');
if (title) {
// Set native title attribute for browser-native tooltip
element.setAttribute('title', title);
successCount++;
console.log('[TOOLTIP] ✅ Set title for', element.id || element.className, ':', title);
} else {
console.warn('[TOOLTIP] ⚠️ No data-bs-title found for element:', element.id || element.className);
}
});
console.log('[TOOLTIP] Tooltip initialization complete:', successCount, 'elements updated');
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeTooltips);
} else {
// DOM is already loaded
initializeTooltips();
}
})();
</script>
</t>
</template>
<!-- Sub-template: Checkout Order Summary Table with Translations -->
<template id="eskaera_checkout_summary" name="Checkout Order Summary">
<div class="checkout-summary-container">
<table class="table table-hover checkout-summary-table" id="checkout-summary-table">
<thead class="table-dark">
<tr>
<th class="col-name">Product</th>
<th class="col-qty text-center">Quantity</th>
<th class="col-price text-right">Price</th>
<th class="col-subtotal text-right">Subtotal</th>
</tr>
</thead>
<tbody id="checkout-summary-tbody">
<tr id="checkout-empty-row" class="empty-message">
<td colspan="4" class="text-center text-muted py-4">
<i class="fa fa-inbox fa-2x mb-2" />
<p>This order's cart is empty</p>
</td>
</tr>
</tbody>
</table>
<div class="checkout-total-section">
<div class="total-row">
<span class="total-label">Total</span>:
<span class="total-amount" id="checkout-total-amount">0.00</span>
<span class="currency"></span>
</div>
</div>
</div>
</template>
<!-- Template: Group Order Checkout (Eskaera) -->
<template id="eskaera_checkout" name="Eskaera Checkout">
<t t-call="website.layout">
<div id="wrap" class="eskaera-checkout-page oe_structure oe_empty" data-name="Eskaera Checkout" t-attf-data-delivery-product-id="{{ delivery_product_id }}" t-attf-data-delivery-product-name="{{ delivery_product_name }}" t-attf-data-delivery-product-price="{{ delivery_product_price }}" t-attf-data-home-delivery-enabled="{{ 'true' if group_order.home_delivery else 'false' }}" t-attf-data-pickup-day="{{ group_order.pickup_day }}" t-attf-data-pickup-date="{{ group_order.pickup_date.strftime('%d/%m/%Y') if group_order.pickup_date else '' }}" t-attf-data-delivery-notice="{{ (group_order.delivery_notice or '').replace(chr(10), ' ').replace(chr(13), ' ') }}">
<div class="container mt-5">
<div class="row">
<div class="col-lg-10 offset-lg-1">
<!-- Header Section -->
<div class="mb-4">
<t t-call="website_sale_aplicoop.order_header">
<t t-set="header_class" t-value="'checkout-header'" />
<t t-set="header_title">Confirm Order: <t t-esc="group_order.name" /></t>
</t>
</div>
<!-- Order Info Card -->
<div
class="order-info-card card border-0 shadow-sm mb-4"
>
<div class="card-body">
<div class="row mb-4">
<div class="col-md-4">
<div class="info-item">
<label
t-att-class="'info-label'"
>Cutoff Day</label>
<t
t-if="group_order.cutoff_day and group_order.cutoff_date"
>
<span
class="info-value"
>
<t
t-esc="day_names[int(group_order.cutoff_day) % 7]"
/>
<span
class="info-date"
>(<t
t-esc="group_order.cutoff_date.strftime('%d/%m/%Y')"
/>)</span>
</span>
</t>
<t t-else="1">
<span
t-att-class="'text-muted small'"
>Not configured</span>
</t>
</div>
</div>
<div class="col-md-4">
<div class="info-item">
<t
t-if="group_order.pickup_day and group_order.pickup_date"
>
<label
t-att-class="'info-label'"
>Store Pickup Day</label>
<span
class="info-value"
t-attf-data-pickup-date="{{ group_order.pickup_date }}"
t-attf-data-delivery-date="{{ group_order.delivery_date }}"
>
<t
t-esc="day_names[int(group_order.pickup_day) % 7]"
/>
<span
class="info-date"
>(<t
t-esc="group_order.pickup_date.strftime('%d/%m/%Y')"
/>)</span>
</span>
</t>
</div>
</div>
<div class="col-md-4">
<div class="info-item">
<t
t-if="group_order.delivery_date and group_order.home_delivery"
>
<label
t-att-class="'info-label'"
>Home Delivery Day</label>
<span
class="info-value"
>
<t
t-esc="day_names[group_order.delivery_date.weekday()]"
/>
<span
class="info-date"
>(<t
t-esc="group_order.delivery_date.strftime('%d/%m/%Y')"
/>)</span>
</span>
</t>
</div>
</div>
</div>
<hr class="my-2" />
<div class="row">
<div
class="col-md-6 text-muted small help-text-sm"
>
<i
class="fa fa-info-circle"
aria-hidden="true"
t-translation="off"
/>
<span
>Save your order as a draft before confirming to make final changes if needed.</span>
</div>
<div class="col-md-6 text-end">
<button
class="btn btn-outline-primary save-order-btn-styled"
id="save-order-btn"
t-attf-data-order-id="{{ group_order.id }}"
aria-label="Save order as draft"
>
<i
class="fa fa-save save-icon-size"
aria-hidden="true"
t-translation="off"
/>
<span>Save as Draft</span>
</button>
</div>
</div>
</div>
</div>
<!-- Summary Section -->
<h4
class="summary-heading mb-3"
>Order Summary</h4>
<!-- Editable area: Above summary -->
<div
class="oe_structure oe_empty mb-3"
data-name="Before Summary"
/>
<div id="checkout-summary" class="mb-5">
<t
t-call="website_sale_aplicoop.eskaera_checkout_summary"
>
<t t-set="labels" t-value="{}" />
</t>
</div>
<!-- Editable area: Below summary -->
<div
class="oe_structure oe_empty mb-4"
data-name="After Summary"
/>
<!-- Home Delivery Checkbox -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="home-delivery-checkbox" name="home_delivery" />
<label class="form-check-label fw-bold" for="home-delivery-checkbox">Home Delivery</label>
</div>
<div
id="delivery-info-alert"
class="alert alert-info mt-3 d-none"
>
<p class="mb-2">
<i
class="fa fa-truck"
aria-hidden="true"
t-translation="off"
/>
<t
t-if="group_order.delivery_date and group_order.home_delivery"
>
<strong
>Delivery Information:</strong> Your order will be delivered at
<t
t-esc="day_names[(int(group_order.pickup_day) + 1) % 7]"
/>
<t
t-esc="group_order.delivery_date.strftime('%d/%m/%Y')"
/>
<t
t-if="group_order.delivery_notice"
>
<br />
<t
t-esc="group_order.delivery_notice"
/>
</t>
</t>
</p>
</div>
</div>
</div>
<!-- Warning Alert -->
<div
class="alert alert-warning alert-dismissible fade show"
role="alert"
>
<div>
<i
class="fa fa-exclamation-triangle"
aria-hidden="true"
t-translation="off"
/>
<span class="fw-bold">
<t
t-if="request.env.context.get('lang') == 'eu_ES' or request.env.context.get('lang') == 'eu'"
>Garrantzitsua</t>
<t
t-elif="request.env.context.get('lang') == 'es_ES' or request.env.context.get('lang') == 'es'"
>Importante</t>
<t t-else="">Important</t>
</span>:
</div>
<p>
<t
t-if="request.env.context.get('lang') == 'eu_ES' or request.env.context.get('lang') == 'eu'"
>Behin eskaera hau berretsi ondoren, ezin izango duzu aldatu. Mesedez, arretaz berrikusi berretsi aurretik.</t>
<t
t-elif="request.env.context.get('lang') == 'es_ES' or request.env.context.get('lang') == 'es'"
>Una vez confirmes este pedido, no podrás modificarlo. Por favor, revisa cuidadosamente antes de confirmar.</t>
<t
t-else=""
>Once you confirm this order, you will not be able to modify it. Please review carefully before confirming.</t>
</p>
<button
type="button"
class="btn-close"
data-bs-dismiss="alert"
aria-label="Close"
/>
</div>
<!-- Action Buttons -->
<div class="checkout-actions d-grid gap-3" id="checkout-form-labels">
<button class="btn btn-success btn-lg" id="confirm-order-btn" t-attf-data-order-id="{{ group_order.id }}" data-confirmed-label="Order confirmed" data-pickup-label="Pickup Day" aria-label="Confirm and send order" data-bs-title="Confirm Order" data-bs-toggle="tooltip">
<i class="fa fa-check-circle" aria-hidden="true" t-translation="off" />
<span>Confirm Order</span>
</button>
<a t-attf-href="/eskaera/{{ group_order.id }}" class="btn btn-outline-secondary btn-lg" aria-label="Back to cart page" data-bs-title="Back to Cart" data-bs-toggle="tooltip">
<i class="fa fa-arrow-left" aria-hidden="true" t-translation="off" />
<span>Back to Cart</span>
</a>
</div>
</div>
</div>
</div>
<!-- Initialize translated labels for JavaScript (same as in eskaera) -->
<script type="text/javascript">
(function() {
'use strict';
// Initialize groupOrderShop.labels from server-rendered labels
if (!window.groupOrderShop) {
window.groupOrderShop = {};
}
window.groupOrderShop.labels = <t
t-raw="labels_json"
/>;
console.log('[LABELS] Initialized from server:', window.groupOrderShop.labels);
})();
</script>
<!-- Scripts (in dependency order) -->
<!-- Load i18n_manager first - fetches translations from server -->
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/i18n_manager.js" />
<!-- Keep legacy helpers for backwards compatibility -->
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/i18n_helpers.js" />
<!-- Main shop functionality (depends on i18nManager) -->
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/website_sale.js" />
<!-- UI enhancements -->
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/checkout_labels.js" />
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/home_delivery.js" />
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/checkout_summary.js" />
<script type="text/javascript">
// Auto-load cart from localStorage when accessing checkout directly
(function() {
'use strict';
// Get order ID from button
var confirmBtn = document.getElementById('confirm-order-btn');
if (!confirmBtn) return;
var orderId = confirmBtn.getAttribute('data-order-id');
var cartKey = 'eskaera_' + orderId + '_cart';
// Check if there's a saved cart and load it
var savedCart = localStorage.getItem(cartKey);
if (savedCart) {
try {
var cart = JSON.parse(savedCart);
console.log('[CHECKOUT AUTO-LOAD] Cart found in localStorage:', cart);
// Simulate cart loading by triggering a custom event
// The checkout_labels.js will listen for cart data
var event = new CustomEvent('cartLoaded', { detail: { cart: cart } });
document.dispatchEvent(event);
} catch (e) {
console.error('[CHECKOUT AUTO-LOAD] Error parsing cart:', e);
}
} else {
console.log('[CHECKOUT AUTO-LOAD] No cart found in localStorage');
}
})();
</script>
</div>
</t>
</template>
<!-- Recursive macro to render category hierarchy for select dropdown -->
<template id="category_hierarchy_options" name="Category Hierarchy Options">
<!--
Macro para renderizar recursivamente la jerarquía de categorías.
Todas las categorías son seleccionables, indentadas por profundidad.
Parámetros:
- categories: lista de categorías a renderizar
- depth: nivel de profundidad actual (para padding/indentación)
-->
<t t-foreach="categories" t-as="cat">
<!-- Calcular padding basado en profundidad: 20px por nivel -->
<t t-set="padding_px" t-value="depth * 20" />
<!-- Crear prefijo visual con flechas según profundidad -->
<t t-set="prefix">
<t t-foreach="range(depth)" t-as="i"></t>
</t>
<!-- Renderizar como opción indentada y seleccionable -->
<option t-att-value="str(cat['id'])" t-attf-style="padding-left: {{ padding_px }}px;">
<t t-esc="prefix" /><t t-esc="cat['name']" />
</option>
<!-- Renderizar hijos recursivamente si existen -->
<t t-if="cat['children']">
<t t-call="website_sale_aplicoop.category_hierarchy_options">
<t t-set="categories" t-value="cat['children']" />
<t t-set="depth" t-value="depth + 1" />
</t>
</t>
</t>
</template>
<!-- Template: Eskaera Shop Products (for lazy loading) -->
<template id="eskaera_shop_products" name="Eskaera Shop Products">
<t t-foreach="products" t-as="product">
<div
class="product-card-wrapper product-card"
t-attf-data-product-name="{{ product.name }}"
t-attf-data-category-id="{{ product.categ_id.id if product.categ_id else '' }}"
t-attf-data-product-tags="{{ ','.join(str(t.id) for t in product.product_tag_ids) if product.product_tag_ids else '' }}"
>
<div class="card h-100">
<t t-if="product.image_128">
<img
t-attf-src="data:image/png;base64,{{ product.image_128.decode() }}"
class="card-img-top product-img-cover"
t-attf-alt="{{ product.name }}"
/>
</t>
<t t-else="">
<div
class="card-img-top bg-light d-flex align-items-center justify-content-center product-img-placeholder"
>
<i
class="fa fa-image fa-3x text-muted"
/>
</div>
</t>
<div
class="card-body d-flex flex-column"
>
<h6
class="card-title"
t-esc="product.name"
/>
<t
t-if="product.product_tag_ids"
>
<div
class="product-tags mb-2"
>
<t
t-foreach="filtered_product_tags.get(product.id, {}).get('published_tags', product.product_tag_ids)"
t-as="tag"
>
<t
t-if="tag.color"
>
<span
class="badge badge-km"
t-attf-style="background-color: {{ tag.color }} !important; border-color: {{ tag.color }} !important; color: #ffffff !important;"
t-esc="tag.name"
/>
</t>
<t t-else="">
<span
class="badge badge-km tag-use-theme-color"
t-esc="tag.name"
/>
</t>
</t>
</div>
</t>
<t
t-if="product_supplier_info.get(product.id)"
>
<p
class="product-supplier mb-2"
>
<small><t
t-esc="product_supplier_info[product.id]"
/></small>
</p>
</t>
<t
t-if="product.country_id or product.state_id"
>
<p
class="product-origin mb-2"
>
<small>
<i
class="fa fa-map-marker"
aria-hidden="true"
/>
<t
t-if="product.state_id"
>
<t
t-out="product.state_id.name"
/><t
t-if="product.country_id"
>, </t>
</t>
<t
t-if="product.country_id"
>
<t
t-out="product.country_id.name"
/>
</t>
</small>
</p>
</t>
<t
t-set="price_info"
t-value="product_price_info.get(product.id, {})"
/>
<t
t-set="display_price"
t-value="product_display_info.get(product.id, {}).get('display_price', 0.0)"
/>
<t
t-set="base_price"
t-value="price_info.get('list_price', product.list_price)"
/>
<h6
class="card-text product-price-display"
>
<span
class="product-price-main"
>
<t
t-esc="'%.2f' % display_price"
/>
</span>
<t
t-if="price_info.get('has_discounted_price', False)"
>
<small
class="text-muted text-decoration-line-through ms-1"
>
<t
t-esc="'%.2f' % base_price"
/>
</small>
</t>
</h6>
<t
t-if="product.base_unit_price and product.base_unit_name"
>
<p
class="product-unit-price text-muted"
style="font-size: 0.85rem; margin-top: 0.25rem; margin-bottom: 0;"
>
<t
t-esc="'%.2f' % product.base_unit_price"
/> € / <t
t-esc="product.base_unit_name"
/>
</p>
</t>
</div>
<t
t-set="safe_uom_category"
t-value="product_display_info.get(product.id, {}).get('safe_uom_category', '')"
/>
<t
t-set="order_id_safe"
t-value="group_order.id if group_order else ''"
/>
<form
class="add-to-cart-form"
t-attf-data-order-id="{{ order_id_safe }}"
t-attf-data-product-id="{{ product.id }}"
t-attf-data-product-name="{{ product.name }}"
t-attf-data-product-price="{{ display_price }}"
t-attf-data-uom-category="{{ safe_uom_category }}"
>
<div class="qty-control">
<label
t-attf-for="qty_{{ product.id }}"
class="sr-only"
>Quantity of <t
t-esc="product.name"
/></label>
<button
class="qty-decrease"
type="button"
t-attf-data-product-id="{{ product.id }}"
aria-label="Decrease quantity"
>
<i
class="fa fa-minus"
/>
</button>
<input
type="number"
t-attf-id="qty_{{ product.id }}"
class="product-qty"
name="quantity"
value="1"
min="1"
step="1"
/>
<button
class="qty-increase"
type="button"
t-attf-data-product-id="{{ product.id }}"
aria-label="Increase quantity"
>
<i
class="fa fa-plus"
/>
</button>
<button
class="add-to-cart-btn"
type="button"
t-attf-aria-label="Add {{ product.name }} to cart"
t-attf-title="Add {{ product.name }} to cart"
>
<i
class="fa fa-shopping-cart"
aria-hidden="true"
/>
</button>
</div>
</form>
</div>
</div>
</t>
</template>
</data>
</odoo>