[IMP] website_sale_aplicoop: skip the product-card tags row when unused

The tags row was reserved (empty, aria-hidden) on every product card so
prices stay aligned across a grid row regardless of which cards have tags.
But when no product in the current batch has a published tag, that row was
still a fixed, permanent gap on every card for a feature nobody was using.

any_product_has_tags is now computed once per batch (controller-side, per
CLAUDE.md's no-logic-in-QWeb rule) from the already-filtered published_tags,
and the template skips the row entirely when it's False across all three
render paths (initial page, load-more, infinite-scroll AJAX).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
GitHub Copilot 2026-08-16 12:50:32 +02:00
parent f3e39550c2
commit a67181ab42
2 changed files with 39 additions and 16 deletions

View file

@ -847,6 +847,12 @@ class AplicoopWebsiteSale(WebsiteSale):
product_display_info, product_display_info,
filtered_products_dict, filtered_products_dict,
) = self._prepare_products_maps(products, pricelist) ) = self._prepare_products_maps(products, pricelist)
# Whether the tags row needs to be reserved on every card: if nothing in
# this batch has a published tag, the row is skipped entirely instead of
# leaving an empty aria-hidden gap on every product (see product-card.css).
any_product_has_tags = any(
v["published_tags"] for v in filtered_products_dict.values()
)
# Inject draft sale demand context so ribbons/is_out_of_stock use forecasted net qty # Inject draft sale demand context so ribbons/is_out_of_stock use forecasted net qty
products, product_max_qty = self._prepare_draft_stock_data(products) products, product_max_qty = self._prepare_draft_stock_data(products)
@ -876,6 +882,7 @@ class AplicoopWebsiteSale(WebsiteSale):
"group_order": group_order, "group_order": group_order,
"products": products, "products": products,
"filtered_product_tags": filtered_products_dict, "filtered_product_tags": filtered_products_dict,
"any_product_has_tags": any_product_has_tags,
"cart": cart, "cart": cart,
"available_categories": available_categories, "available_categories": available_categories,
"category_hierarchy": category_hierarchy, "category_hierarchy": category_hierarchy,
@ -986,6 +993,10 @@ class AplicoopWebsiteSale(WebsiteSale):
"product": product, "product": product,
"published_tags": published_tags, "published_tags": published_tags,
} }
# See eskaera_shop for why this decides whether to reserve the tags row.
any_product_has_tags = any(
v["published_tags"] for v in filtered_products_dict.values()
)
product_display_info = {} product_display_info = {}
for product in products_page: for product in products_page:
@ -1004,6 +1015,7 @@ class AplicoopWebsiteSale(WebsiteSale):
"group_order": group_order, "group_order": group_order,
"products": products_page, "products": products_page,
"filtered_product_tags": filtered_products_dict, "filtered_product_tags": filtered_products_dict,
"any_product_has_tags": any_product_has_tags,
"product_supplier_info": product_supplier_info, "product_supplier_info": product_supplier_info,
"product_price_info": product_price_info, "product_price_info": product_price_info,
"product_display_info": product_display_info, "product_display_info": product_display_info,
@ -1112,6 +1124,10 @@ class AplicoopWebsiteSale(WebsiteSale):
} }
for product in products_page for product in products_page
} }
# See eskaera_shop for why this decides whether to reserve the tags row.
any_product_has_tags = any(
v["published_tags"] for v in filtered_products_dict.values()
)
# Inject draft demand context for ribbons/stock flags # Inject draft demand context for ribbons/stock flags
products_page, product_max_qty = self._prepare_draft_stock_data(products_page) products_page, product_max_qty = self._prepare_draft_stock_data(products_page)
@ -1126,6 +1142,7 @@ class AplicoopWebsiteSale(WebsiteSale):
"group_order": group_order, "group_order": group_order,
"products": products_page, "products": products_page,
"filtered_product_tags": filtered_products_dict, "filtered_product_tags": filtered_products_dict,
"any_product_has_tags": any_product_has_tags,
"product_supplier_info": product_supplier_info, "product_supplier_info": product_supplier_info,
"product_price_info": product_price_info, "product_price_info": product_price_info,
"product_display_info": product_display_info, "product_display_info": product_display_info,

View file

@ -661,7 +661,12 @@
<!-- Tags/supplier/origin always occupy their slot (empty and <!-- Tags/supplier/origin always occupy their slot (empty and
aria-hidden when the product has no value) so that the price aria-hidden when the product has no value) so that the price
row below lines up across every card in the grid row, row below lines up across every card in the grid row,
regardless of which optional fields a given product has. --> 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"> <t t-if="product.product_tag_ids">
<div class="product-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-foreach="filtered_product_tags.get(product.id, {}).get('published_tags', product.product_tag_ids)" t-as="tag">
@ -679,6 +684,7 @@
<t t-else=""> <t t-else="">
<div class="product-tags" aria-hidden="true" /> <div class="product-tags" aria-hidden="true" />
</t> </t>
</t>
<t t-if="product_supplier_info.get(product.id)"> <t t-if="product_supplier_info.get(product.id)">
<p class="product-supplier"> <p class="product-supplier">
<small> <small>