Separar produtos por cor na vitrine

É comum lojas de moda unificarem produtos por tamanho e também por cor, o que pode reduzir visualmente as        opções nas categorias. Para melhorar a usabilidade e incentivar a compra rápida, podemos desmembrar por cor na vitrine via Twig, exibindo por exemplo “Camiseta Básica Branca”, “Camiseta Básica Preta”, etc., ainda que seja o mesmo produto com variações.

Objetivo

  • Criar no Admin a opção para ativar/desativar o recurso.
  • Adicionar a lógica no snipplets/product_grid.tpl para separar o produto por cor.

Configuração no Admin (config/settings.txt)

Em config/settings.txt, na seção “Listado de productos”, adicione:

title
		title = Separación de productos por color
checkbox
		name = separation_products_by_color
		description = Separa los productos en la ventana de visualización de categorías por color.

Traduções (config/translations.txt)

Adicione as chaves i18n para título e descrição:

es "Separación de productos por color"
pt "Separação de produtos por cor"
en "Separation of products by color"
es_mx "Separación de productos por color"

es "Separa los productos en la ventana de visualización de categorías por color."
pt "Separa os produtos na visualização de categorias por cor."
en "Separates products in the category view by color."
es_mx "Separa los productos en la ventana de visualización de categorías por color."

Lógica na vitrine (snipplets/product_grid.tpl)

Substitua o loop padrão do grid por um modo avançado que separa por cor quando o recurso estiver ativo.

Antes
{% if products and pages.is_last %}
    <div class="last-page" style="display:none;"></div>
{% endif %}
{% for product in products %}
    {% include 'snipplets/grid/item.tpl' %}
{% endfor %}
Depois
{# Exibe marcador de última página quando existem produtos e a paginação está na última página #}
{% if products and pages.is_last %}
    <div class="last-page" style="display:none;"></div>
{% endif %}

{# Loop principal: percorre os produtos da página atual #}
{% for product in products %}

    {# Flag de controle: se o modo avançado renderizar itens, evitamos duplicar no fallback #}
    {% set show_advanced_mode = false %}

    {# Modo avançado: separar por cor usando variants_object (somente se existir variação de "Cor") #}
    {% if settings.separation_products_by_color and product.variations %}
        {# 1) Checa se há uma variação cujo nome é Cor/Color e guarda seu índice (0,1,2) #}
        {% set color_index = null %}
        {% for variation in product.variations %}
            {% if variation.name | lower in ['color', 'cor'] and color_index is null %}
                {% set color_index = loop.index0 %}
            {% endif %}
        {% endfor %}

        {# 2) Se encontrou variação de cor, percorre variants_object e rende um item por cor única #}
        {#    Obs.: o "product.variants_object > 1" funciona como uma checagem rápida para evitar cenários sem múltiplas variantes #}
        {% if color_index is not null and product.variants_object > 1 %}
            {% set rendered_colors = [] %}
            {# Percorre todas as variantes (cada combinação de opções) #}
            {% for variante in product.variants_object %}
                {# Renderiza somente variantes disponíveis e visíveis #}
                {% if variante.available and variante.is_visible %}
                    {# Extrai o nome da cor desta variante usando o índice da variação de cor (option0/1/2) #}
                    {% set color_name = attribute(variante, 'option' ~ color_index) | default('') %}
                    {# Garante cor válida e evita duplicar a mesma cor (dedupe) #}
                    {% if color_name and (color_name not in rendered_colors) %}
                        {% set variant_color_name = color_name %}
                        {# Inclui o cartão de produto passando a variante específica e o nome da cor #}
                        {% include 'snipplets/grid/item.tpl' with { special_variant: variante, variant_color_name: variant_color_name } %}
                        {# Marca a cor como já renderizada para não repetir #}
                        {% set rendered_colors = rendered_colors | merge([color_name]) %}
                        {# Sinaliza que o modo avançado foi utilizado (impede fallback abaixo) #}
                        {% set show_advanced_mode = true %}
                    {% endif %}
                {% endif %}
            {% endfor %}
        {% endif %}
    {% endif %}
    
    {# Fallback: renderiza o item padrão somente se o modo avançado NÃO foi acionado #}
    {% if not show_advanced_mode %}
        {% include 'snipplets/grid/item.tpl' %}
    {% endif %}
{% endfor %}

Dica de debug (json_encode)

Para inspecionar objetos no Twig, use o filtro json_encode dentro de uma tag segura:

<textarea>{{ product.variations | json_encode }}</textarea>
Exemplo de saída
[
  {
    "id": 0,
    "name": "Cor",
    "options": [
      { "id": "Preto", "name": "Preto", "custom_data": "#000000" },
      { "id": "Azul", "name": "Azul", "custom_data": "#0000DC" }
    ]
  },
  {
    "id": 1,
    "name": "Tamanho",
    "options": [
      { "id": "PP", "name": "PP", "custom_data": null },
      { "id": "P", "name": "P", "custom_data": null },
      { "id": "M", "name": "M", "custom_data": null },
      { "id": "G", "name": "G", "custom_data": null },
      { "id": "GG", "name": "GG", "custom_data": null }
    ]
  }
]


Ajustes no item da vitrine (snipplets/grid/item.tpl)

No início do arquivo, defina variáveis opcionais com default(null) para não alterar o comportamento padrão:

{% set variant_color_name = variant_color_name | default(null) %}
{% set special_variant = special_variant | default(null) %}
Selecionando a variante na URL

Baseie-se na variável existente product_url_with_selected_variant e, caso uma special_variant tenha sido enviada, force o parâmetro variant:

{% set product_url_with_selected_variant = has_filters ?  ( product.url | add_param('variant', product.selected_or_first_available_variant.id)) : product.url  %}

{% if special_variant %}
    {% set product_url_with_selected_variant = (product.url | add_param('variant', special_variant.id)) %}
{% endif %}
Imagem da variante selecionada

Recupere a imagem principal e, se houver special_variant, substitua pelos dados da imagem vinculada a essa variante. Desative a imagem secundária para não confundir o usuário.

{% set item_img_width = product.featured_image.dimensions['width'] %}
{% set item_img_height = product.featured_image.dimensions['height'] %}
{% set item_img_srcset = product.featured_image %}
{% set item_img_alt = product.featured_image.alt %}

{% if special_variant %}
    {% for image in product.other_images %}
        {% if image.id == special_variant.image %}
            {% set item_img_width = image.dimensions['width'] %}
            {% set item_img_height = image.dimensions['height'] %}
            {% set item_img_srcset = image.image %}
            {% set item_img_alt = image.alt %}
            {% set show_secondary_image = false %}
        {% endif %}
    {% endfor %}
{% endif %}

{% set item_img_spacing = item_img_height / item_img_width * 100 %}
Render do bloco da imagem
<div class="js-product-item-image-container-private {% if show_secondary_image %}js-product-item-private-with-secondary-images{% endif %} product-item-image-container item-image{% if columns == 1 %} item-image-big{% endif %}">
  <div style="padding-bottom: {{ item_img_spacing }}%;" class="js-item-image-padding position-relative d-block" data-store="product-item-image-{{ product.id }}{{variant_color_name}}">
    <a href="{{ product_url_with_selected_variant }}" title="{{ product.name }}" aria-label="{{ product.name }}">
      <img alt="{{ item_img_alt }}" src="{{ 'images/empty-placeholder.png' | static_url }}" srcset="{{ item_img_srcset | product_image_url('small')}} 240w, {{ item_img_srcset | product_image_url('medium')}} 320w, {{ item_img_srcset | product_image_url('large')}} 480w, {{ item_img_srcset | product_image_url('huge')}} 640w, {{ item_img_srcset | product_image_url('original')}} 1024w" class="{{ image_classes }} product-item-image-featured item-image-featured" width="{{ item_img_width }}" height="{{ item_img_height }}" data-expand="{{ data_expand}}" sizes="(max-width: 768px) {{ mobile_image_viewport_space }}vw, (min-width: 769px) {{ desktop_image_viewport_space }}vw" />
      {% if show_secondary_image %}
        <img alt="{{ item_img_alt }}" data-sizes="auto" src="{{ 'images/empty-placeholder.png' | static_url }}" data-srcset="{{ product.other_images | first | product_image_url('small')}} 240w, {{ product.other_images | first | product_image_url('medium')}} 320w, {{ product.other_images | first | product_image_url('large')}} 480w, {{ product.other_images | first | product_image_url('huge')}} 640w, {{ product.other_images | first | product_image_url('original')}} 1024w" class="{{ image_classes}} js-product-item-secondary-image-private product-item-image-secondary item-image-secondary" sizes="(min-width: 768px) {{ desktop_image_viewport_space }}vw, {{ mobile_image_viewport_space }}vw" />
      {% endif %}
      <div class="placeholder-fade"></div>
    </a>
  </div>
</div>


Nome do produto com a cor

Após imprimir {{ "{{ product.name }}" }}, acrescente o sufixo com o nome da cor quando existir:

{{ product.name }}
{% if variant_color_name %}
- {{ variant_color_name }}
{% endif %}

Escondendo o seletor de cores

Se estiver exibindo um item de cor específica, esconda o componente de variações de cor:

Antes
{% if settings.product_color_variants and not reduced_item %}
    {% include 'snipplets/grid/item-colors.tpl' %}
{% endif %}
Depois
{% if settings.product_color_variants and not reduced_item and not special_variant %}
    {% include 'snipplets/grid/item-colors.tpl' %}
{% endif %}