[IMP] website_sale_aplicoop: compact mobile layout for the Eskaera shop
Reorder cart/tags/category/search/products into one flex row with Bootstrap order utilities, so mobile shows cart, tags, category, search, then products, while desktop keeps its current layout. Product cards become a two-column grid of compact tiles from the smallest phones, with a 4:3 photo instead of a tall rectangle. Origin, supplier and tags move behind a per-product "i" toggle instead of always reserving their own row, which also drops the now-unused any_product_has_tags plumbing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
ef1283be7c
commit
6a59d9f6ad
6 changed files with 217 additions and 152 deletions
|
|
@ -274,56 +274,15 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 eskaera-filters" id="realtimeSearch-filters">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-7">
|
||||
<div class="search-input-wrapper">
|
||||
<label class="visually-hidden" for="realtime-search-input">Search products</label>
|
||||
<input type="search" id="realtime-search-input" class="form-control eskaera-search-input" placeholder="Search products..." autocomplete="off" aria-describedby="realtime-search-status" />
|
||||
<button type="button" id="clear-search-btn" class="search-clear-btn" aria-label="Clear search" title="Clear search">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<p id="realtime-search-status" class="visually-hidden" aria-live="polite" aria-atomic="true" />
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<select name="category" id="realtime-category-select" class="form-select">
|
||||
<option value="">Browse Product Categories</option>
|
||||
<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">
|
||||
<!-- Toggle buttons: aria-pressed carries the filter
|
||||
state, which realtime_search.js keeps in sync
|
||||
with the .is-selected / .is-dimmed classes.
|
||||
The record colour rides in a custom property so
|
||||
the stylesheet keeps owning the badge. -->
|
||||
<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="--ac-tag-color: {{ tag['color'] }};" data-toggle="tag-filter" aria-pressed="false">
|
||||
<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" aria-pressed="false">
|
||||
<span t-esc="tag['name']" /> (<span class="tag-count" t-esc="tag['count']" />)
|
||||
</button>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div class="row g-2">
|
||||
<div class="col-lg-3 mb-4 mb-lg-0">
|
||||
<!-- One flex row for cart/tags/category/search/products: Bootstrap's
|
||||
order utilities need every reordered item to be a sibling of the
|
||||
same flex container, so the four blocks that used to live in two
|
||||
separate rows (filters above, cart+products below) are now columns
|
||||
here. Unprefixed order-* puts the cart first on mobile (right below
|
||||
the header, above the filters); order-lg-* restores the desktop
|
||||
layout: search+category as one row, then tags, then cart+products. -->
|
||||
<div class="row g-2 eskaera-filters" id="realtimeSearch-filters">
|
||||
<div class="col-12 col-lg-3 order-1 order-lg-4 mb-4 mb-lg-0">
|
||||
<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>
|
||||
|
|
@ -362,7 +321,49 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<t t-if="available_tags">
|
||||
<div class="col-12 order-2 order-lg-3">
|
||||
<div id="tag-filter-container" class="tag-filter-badges">
|
||||
<t t-foreach="available_tags" t-as="tag">
|
||||
<!-- Toggle buttons: aria-pressed carries the filter
|
||||
state, which realtime_search.js keeps in sync
|
||||
with the .is-selected / .is-dimmed classes.
|
||||
The record colour rides in a custom property so
|
||||
the stylesheet keeps owning the badge. -->
|
||||
<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="--ac-tag-color: {{ tag['color'] }};" data-toggle="tag-filter" aria-pressed="false">
|
||||
<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" aria-pressed="false">
|
||||
<span t-esc="tag['name']" /> (<span class="tag-count" t-esc="tag['count']" />)
|
||||
</button>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<div class="col-12 col-lg-5 order-3 order-lg-2">
|
||||
<select name="category" id="realtime-category-select" class="form-select">
|
||||
<option value="">Browse Product Categories</option>
|
||||
<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 class="col-12 col-lg-7 order-4 order-lg-1">
|
||||
<div class="search-input-wrapper">
|
||||
<label class="visually-hidden" for="realtime-search-input">Search products</label>
|
||||
<input type="search" id="realtime-search-input" class="form-control eskaera-search-input" placeholder="Search products..." autocomplete="off" aria-describedby="realtime-search-status" />
|
||||
<button type="button" id="clear-search-btn" class="search-clear-btn" aria-label="Clear search" title="Clear search">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<p id="realtime-search-status" class="visually-hidden" aria-live="polite" aria-atomic="true" />
|
||||
</div>
|
||||
<div class="col-12 col-lg-9 order-5 order-lg-5">
|
||||
<div class="oe_structure oe_empty" data-name="Before Products Filter" />
|
||||
<t t-if="products">
|
||||
<div class="products-grid" id="products-grid">
|
||||
|
|
@ -763,16 +764,31 @@
|
|||
</template>
|
||||
<template id="eskaera_shop_products" name="Eskaera Shop Products">
|
||||
<t t-foreach="products" t-as="product">
|
||||
<t t-set="has_tags" t-value="bool(product.product_tag_ids)" />
|
||||
<t t-set="supplier_text" t-value="product_supplier_info.get(product.id)" />
|
||||
<t t-set="has_origin" t-value="bool(product.origin_text)" />
|
||||
<t t-set="has_info" t-value="has_tags or bool(supplier_text) or has_origin" />
|
||||
<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" aria-hidden="true" />
|
||||
</div>
|
||||
</t>
|
||||
<div class="product-media">
|
||||
<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" aria-hidden="true" />
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="has_info">
|
||||
<!-- Origin/supplier/tags used to sit unconditionally in the card
|
||||
body; on the compact "cromo" tile there is no room to show
|
||||
them inline, so they move behind this toggle (see
|
||||
.product-info-panel below). -->
|
||||
<button type="button" class="product-info-toggle" aria-expanded="false" t-attf-aria-controls="product-info-{{ product.id }}" aria-label="Show origin, supplier and tags" title="Origin, supplier and tags">
|
||||
<i class="fa fa-info" aria-hidden="true" t-translation="off" />
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
<t t-if="product.dynamic_ribbon_id">
|
||||
<t t-set="ribbon" t-value="product.dynamic_ribbon_id" />
|
||||
<span t-attf-class="o_ribbon {{ ribbon._get_position_class() }} z-1" t-attf-style="color: {{ ribbon.text_color }}; background-color: {{ ribbon.bg_color }};" t-esc="ribbon.name" />
|
||||
|
|
@ -783,53 +799,36 @@
|
|||
</t>
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h6 class="card-title" t-esc="product.name" />
|
||||
<!-- Tags/supplier/origin always occupy their slot (empty and
|
||||
aria-hidden when the product has no value) so that the price
|
||||
row below lines up across every card in the grid row,
|
||||
regardless of which optional fields a given product has. The
|
||||
tags row itself is only reserved when at least one product in
|
||||
this batch actually has a tag (any_product_has_tags, computed
|
||||
controller-side) — otherwise it would add a permanent empty
|
||||
gap to every card for a feature nobody in the list uses. -->
|
||||
<t t-if="any_product_has_tags">
|
||||
<t t-if="product.product_tag_ids">
|
||||
<div class="product-tags">
|
||||
<t t-foreach="filtered_product_tags.get(product.id, {}).get('published_tags', product.product_tag_ids)" t-as="tag">
|
||||
<t t-if="tag.color">
|
||||
<!-- Per-record colour: passed as a custom property so the
|
||||
stylesheet keeps owning the badge, no !important needed. -->
|
||||
<span class="badge badge-km" t-attf-style="--ac-tag-color: {{ tag.color }};" t-esc="tag.name" />
|
||||
<t t-if="has_info">
|
||||
<div t-attf-id="product-info-{{ product.id }}" class="product-info-panel" hidden="hidden">
|
||||
<t t-if="has_tags">
|
||||
<div class="product-tags">
|
||||
<t t-foreach="filtered_product_tags.get(product.id, {}).get('published_tags', product.product_tag_ids)" t-as="tag">
|
||||
<t t-if="tag.color">
|
||||
<!-- Per-record colour: passed as a custom property so the
|
||||
stylesheet keeps owning the badge, no !important needed. -->
|
||||
<span class="badge badge-km" t-attf-style="--ac-tag-color: {{ tag.color }};" t-esc="tag.name" />
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span class="badge badge-km tag-use-theme-color" t-esc="tag.name" />
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span class="badge badge-km tag-use-theme-color" t-esc="tag.name" />
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="product-tags" aria-hidden="true" />
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="product_supplier_info.get(product.id)">
|
||||
<p class="product-supplier">
|
||||
<small>
|
||||
<t t-esc="product_supplier_info[product.id]" />
|
||||
</small>
|
||||
</p>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<p class="product-supplier" aria-hidden="true" />
|
||||
</t>
|
||||
<t t-if="product.origin_text">
|
||||
<p class="product-origin">
|
||||
<small>
|
||||
<i class="fa fa-map-marker" aria-hidden="true" />
|
||||
<t t-out="product.origin_text" />
|
||||
</small>
|
||||
</p>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<p class="product-origin" aria-hidden="true" />
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="supplier_text">
|
||||
<p class="product-supplier">
|
||||
<small t-esc="supplier_text" />
|
||||
</p>
|
||||
</t>
|
||||
<t t-if="has_origin">
|
||||
<p class="product-origin">
|
||||
<small>
|
||||
<i class="fa fa-map-marker" aria-hidden="true" />
|
||||
<t t-out="product.origin_text" />
|
||||
</small>
|
||||
</p>
|
||||
</t>
|
||||
</div>
|
||||
</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)" />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue