templates/home/listing.html.twig line 1

Open in your IDE?
  1. {% extends 'base_home.html.twig' %}
  2. {% block body %}
  3.     <!-- Start Banner Area -->
  4.     <section class="banner-area organic-breadcrumb">
  5.         <div class="container">
  6.             <div class="breadcrumb-banner d-flex flex-wrap align-items-center justify-content-end">
  7.                 <div class="col-first">
  8.                     <h1>Page de listage de produit</h1>
  9.                     <nav class="d-flex align-items-center">
  10.                         <a href="{{ path('ui_home') }}">Accueil<span class="lnr lnr-arrow-right"></span>
  11.                         </a>
  12.                         <a href="javascript:void(0);">Liste des produits</a>
  13.                     </nav>
  14.                 </div>
  15.             </div>
  16.         </div>
  17.     </section>
  18.     <!-- End Banner Area -->
  19.     <div class="container">
  20.         <div class="row">
  21.             <div class="col-xl-3 col-lg-4 col-md-5">
  22.                 <!-- Bouton toggle sidebar mobile -->
  23.                 <button class="btn btn-outline-primary w-100 d-md-none mb-3 listing-sidebar-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#listingSidebarCollapse" aria-expanded="false" aria-controls="listingSidebarCollapse">
  24.                     <i class="lnr lnr-menu me-2"></i>Filtres et catégories
  25.                     <i class="lnr lnr-chevron-down ms-2 toggle-icon"></i>
  26.                 </button>
  27.                 <!-- Sidebar avec collapse -->
  28.                 <div class="collapse d-md-block" id="listingSidebarCollapse">
  29.                     <div class="sidebar-categories">
  30.                         <div class="head">Catégories</div>
  31.                         <ul class="main-categories">
  32.                             <li class="main-nav-list">
  33.                                 <a href="{{ path('ui_listing') }}" class="category-link {% if not currentCategory %}active{% endif %}">
  34.                                     <span class="lnr lnr-tag"></span>Toutes les catégories
  35.                                     <span class="number">({{ totalProducts }})</span>
  36.                                 </a>
  37.                             </li>
  38.                             {% for category in categories %}
  39.                                 <li class="main-nav-list">
  40.                                     <a href="{{ path('ui_listing', {'category': category.slug}) }}" class="category-link {% if currentCategory == category.slug %}active{% endif %}" data-category="{{ category.slug }}">
  41.                                         <span class="lnr lnr-tag"></span>
  42.                                         {{ category.name }}
  43.                                         <span class="number">({{ category.products|length }})</span>
  44.                                     </a>
  45.                                 </li>
  46.                             {% else %}
  47.                                 <li class="main-nav-list">
  48.                                     <span>Aucune catégorie</span>
  49.                                 </li>
  50.                             {% endfor %}
  51.                         </ul>
  52.                     </div>
  53.                     <div class="sidebar-filter mt-50" id="brandsFilter">
  54.                         <div class="top-filter-head">Filtres</div>
  55.                         <div class="common-filter">
  56.                             <div class="head">Marques</div>
  57.                             <ul class="brand-list" id="brandList">
  58.                                 <li class="filter-list">
  59.                                     <input class="pixel-radio" type="radio" id="all-brands" name="brand" checked>
  60.                                     <label for="all-brands">Toutes les marques</label>
  61.                                 </li>
  62.                                 {% for brand in brands %}
  63.                                     <li class="filter-list">
  64.                                         <input class="pixel-radio" type="radio" id="brand-{{ brand.id }}" name="brand" value="{{ brand.slug }}">
  65.                                         <label for="brand-{{ brand.id }}">{{ brand.name }}<span>({{ brand.getActiveProductsCount() }})</span>
  66.                                         </label>
  67.                                     </li>
  68.                                 {% endfor %}
  69.                             </ul>
  70.                         </div>
  71.                         <!-- Filtres avancés -->
  72.                         <div class="common-filter">
  73.                             <div class="head">Boutiques</div>
  74.                             <div id="shopsFilter">
  75.                                 <form action="#">
  76.                                     <ul
  77.                                         class="shop-list" id="shopList"><!-- Les boutiques seront chargées dynamiquement -->
  78.                                     </ul>
  79.                                 </form>
  80.                             </div>
  81.                         </div>
  82.                         <div class="common-filter">
  83.                             <div class="head">Condition ({{ conditions|length }})</div>
  84.                             <ul class="condition-list" id="conditionList">
  85.                                 <li class="filter-list">
  86.                                     <input class="pixel-radio" type="radio" id="all-conditions" name="condition" checked>
  87.                                     <label for="all-conditions">Toutes les conditions</label>
  88.                                 </li>
  89.                                 {% for condition in conditions %}
  90.                                     <li class="filter-list">
  91.                                         <input class="pixel-radio" type="radio" id="condition-{{ condition.id }}" name="condition" value="{{ condition.slug }}">
  92.                                         <label for="condition-{{ condition.id }}">{{ condition.name }}<span>({{ condition.getActiveProductsCount() }})</span>
  93.                                         </label>
  94.                                     </li>
  95.                                 {% else %}
  96.                                     <li class="filter-list">
  97.                                         <span class="text-muted">Aucune condition disponible</span>
  98.                                     </li>
  99.                                 {% endfor %}
  100.                             </ul>
  101.                         </div>
  102.                         <div class="common-filter">
  103.                             <div class="head">Produits vedettes</div>
  104.                             <form action="#">
  105.                                 <ul>
  106.                                     <li class="filter-list">
  107.                                         <input class="pixel-radio" type="radio" id="featured-all" name="featured" checked>
  108.                                         <label for="featured-all">Tous les produits</label>
  109.                                     </li>
  110.                                     <li class="filter-list">
  111.                                         <input class="pixel-radio" type="radio" id="featured-only" name="featured" value="true">
  112.                                         <label for="featured-only">Produits vedettes uniquement</label>
  113.                                     </li>
  114.                                 </ul>
  115.                             </form>
  116.                         </div>
  117.                         <div class="common-filter">
  118.                             <div class="head">Type de produit</div>
  119.                             <form action="#">
  120.                                 <ul>
  121.                                     <li class="filter-list">
  122.                                         <input class="pixel-radio" type="radio" id="digital-all" name="digital" checked>
  123.                                         <label for="digital-all">Tous les types</label>
  124.                                     </li>
  125.                                     <li class="filter-list">
  126.                                         <input class="pixel-radio" type="radio" id="digital-physical" name="digital" value="false">
  127.                                         <label for="digital-physical">Produits physiques</label>
  128.                                     </li>
  129.                                     <li class="filter-list">
  130.                                         <input class="pixel-radio" type="radio" id="digital-digital" name="digital" value="true">
  131.                                         <label for="digital-digital">Produits numériques</label>
  132.                                     </li>
  133.                                 </ul>
  134.                             </form>
  135.                         </div>
  136.                         <div class="common-filter">
  137.                             <div class="head">Disponibilité</div>
  138.                             <form action="#">
  139.                                 <ul>
  140.                                     <li class="filter-list">
  141.                                         <input class="pixel-radio" type="radio" id="availability-all" name="availability" checked>
  142.                                         <label for="availability-all">Tous</label>
  143.                                     </li>
  144.                                     <li class="filter-list">
  145.                                         <input class="pixel-radio" type="radio" id="availability-in-stock" name="availability" value="in_stock">
  146.                                         <label for="availability-in-stock">En stock</label>
  147.                                     </li>
  148.                                     <li class="filter-list">
  149.                                         <input class="pixel-radio" type="radio" id="availability-low-stock" name="availability" value="low_stock">
  150.                                         <label for="availability-low-stock">Stock faible</label>
  151.                                     </li>
  152.                                     <li class="filter-list">
  153.                                         <input class="pixel-radio" type="radio" id="availability-out-of-stock" name="availability" value="out_of_stock">
  154.                                         <label for="availability-out-of-stock">Rupture de stock</label>
  155.                                     </li>
  156.                                 </ul>
  157.                             </form>
  158.                         </div>
  159.                         <div class="common-filter">
  160.                             <div class="head">Note minimale</div>
  161.                             <form action="#">
  162.                                 <ul>
  163.                                     <li class="filter-list">
  164.                                         <input class="pixel-radio" type="radio" id="rating-all" name="rating" checked>
  165.                                         <label for="rating-all">Toutes les notes</label>
  166.                                     </li>
  167.                                     <li class="filter-list">
  168.                                         <input class="pixel-radio" type="radio" id="rating-4" name="rating" value="4">
  169.                                         <label for="rating-4">4 étoiles et plus</label>
  170.                                     </li>
  171.                                     <li class="filter-list">
  172.                                         <input class="pixel-radio" type="radio" id="rating-3" name="rating" value="3">
  173.                                         <label for="rating-3">3 étoiles et plus</label>
  174.                                     </li>
  175.                                     <li class="filter-list">
  176.                                         <input class="pixel-radio" type="radio" id="rating-2" name="rating" value="2">
  177.                                         <label for="rating-2">2 étoiles et plus</label>
  178.                                     </li>
  179.                                 </ul>
  180.                             </form>
  181.                         </div>
  182.                         <div class="common-filter">
  183.                             <div class="head">Poids</div>
  184.                             <div class="weight-range-area">
  185.                                 <div class="weight-inputs d-flex">
  186.                                     <div class="weight-input">
  187.                                         <label>Min (kg):</label>
  188.                                         <input type="number" id="weightMin" placeholder="0" step="0.1" min="0">
  189.                                     </div>
  190.                                     <div class="weight-input">
  191.                                         <label>Max (kg):</label>
  192.                                         <input type="number" id="weightMax" placeholder="100" step="0.1" min="0">
  193.                                     </div>
  194.                                 </div>
  195.                             </div>
  196.                         </div>
  197.                         <div class="common-filter">
  198.                             <div class="head">Couleur</div>
  199.                             <div id="colorsFilter">
  200.                                 <form action="#">
  201.                                     <ul
  202.                                         class="color-list" id="colorList"><!-- Les couleurs seront chargées dynamiquement -->
  203.                                     </ul>
  204.                                 </form>
  205.                             </div>
  206.                         </div>
  207.                         <div class="common-filter">
  208.                             <div class="head">Taille</div>
  209.                             <div id="sizesFilter">
  210.                                 <form action="#">
  211.                                     <ul
  212.                                         class="size-list" id="sizeList"><!-- Les tailles seront chargées dynamiquement -->
  213.                                     </ul>
  214.                                 </form>
  215.                             </div>
  216.                         </div>
  217.                         <div class="common-filter">
  218.                             <div class="head">Matériau</div>
  219.                             <div id="materialsFilter">
  220.                                 <form action="#">
  221.                                     <ul
  222.                                         class="material-list" id="materialList"><!-- Les matériaux seront chargés dynamiquement -->
  223.                                     </ul>
  224.                                 </form>
  225.                             </div>
  226.                         </div>
  227.                         <div class="common-filter">
  228.                             <div class="head">Prix</div>
  229.                             <div class="price-range-area">
  230.                                 <div id="price-range"></div>
  231.                                 <div class="value-wrapper d-flex">
  232.                                     <div class="price">Prix:</div>
  233.                                     <span>$</span>
  234.                                     <div id="lower-value"></div>
  235.                                     <div class="to">to</div>
  236.                                     <span>$</span>
  237.                                     <div id="upper-value"></div>
  238.                                 </div>
  239.                             </div>
  240.                         </div>
  241.                     </div>
  242.                 </div>
  243.                 <!-- Fin du collapse sidebar -->
  244.             </div>
  245.             <div class="col-xl-9 col-lg-8 col-md-7">
  246.             <!-- Barre de tri et bouton de filtres -->
  247.             <div class="row mb-4">
  248.                 <div
  249.                     class="col-md-6 mb-3 mb-md-0">
  250.                     <!-- Bouton pour ouvrir le modal de filtres -->
  251.                     <button type="button" class="btn btn-lg d-flex align-items-center" data-bs-toggle="modal" data-bs-target="#filtersModal" style="background: transparent; border: none; padding: 10px 16px; font-weight: 600; transition: all 0.3s ease;">
  252.                         <div class="filter-icon-circle d-flex align-items-center justify-content-center" style="width: 42px; height: 42px; background: linear-gradient(135deg, #ffa200 0%, #ff8c00 100%); border-radius: 50%; border: 2px solid #ffa200; flex-shrink: 0; margin-right: 10px; box-shadow: 0 2px 8px rgba(255, 162, 0, 0.3);">
  253.                             <i class="lnr lnr-filter" style="font-size: 1.3rem; color: white; font-weight: bold;"></i>
  254.                         </div>
  255.                         <span style="font-size: 1.1rem; font-weight: 600; color: #333;">Filtres</span>
  256.                         <span class="badge bg-warning text-dark ms-2" id="activeFiltersCount" style="display: none; font-size: 0.85rem; padding: 4px 10px; border-radius: 12px; font-weight: 600;">0</span>
  257.                     </button>
  258.                 </div>
  259.                 <div class="col-md-6">
  260.                     <select class="form-select" id="sortSelect" onchange="applySorting()">
  261.                         <option value="newest" {% if currentSort == 'newest' %} selected {% endif %}>Plus récents</option>
  262.                         <option value="price_asc" {% if currentSort == 'price_asc' %} selected {% endif %}>Prix croissant</option>
  263.                         <option value="price_desc" {% if currentSort == 'price_desc' %} selected {% endif %}>Prix décroissant</option>
  264.                         <option value="name_asc" {% if currentSort == 'name_asc' %} selected {% endif %}>Nom A-Z</option>
  265.                         <option value="name_desc" {% if currentSort == 'name_desc' %} selected {% endif %}>Nom Z-A</option>
  266.                         <option value="popular" {% if currentSort == 'popular' %} selected {% endif %}>Plus populaires</option>
  267.                     </select>
  268.                 </div>
  269.             </div>
  270.             <!-- Start Best Seller -->
  271.             <section class="lattest-product-area pb-40 category-list">
  272.                 <div class="row" id="productsContainer">
  273.                     {% for product in products %}
  274.                         <div class="col-lg-4 col-md-6">
  275.                             <div class="single-product">
  276.                                 <div class="product-image-container mb-2" onmouseenter="showImageNav({{ product.id }})" onmouseleave="hideImageNav({{ product.id }})">
  277.                                     <a href="{{ path('ui_product_show', { slug: product.slug }) }}">
  278.                                         {% set allImages = product.images|default([]) %}
  279.                                         {% if product.variants is defined %}
  280.                                             {% for variant in product.variants %}
  281.                                                 {% if variant.isActive and variant.images is defined %}
  282.                                                     {% for variantImg in variant.images %}
  283.                                                         {% set allImages = allImages|merge([variantImg]) %}
  284.                                                     {% endfor %}
  285.                                                 {% endif %}
  286.                                             {% endfor %}
  287.                                         {% endif %}
  288.                                         {% if allImages|length > 0 %}
  289.                                             <img class="img-fluid main-product-img" id="main-img-{{ product.id }}" src="{{ asset(allImages[0]) }}" alt="{{ product.name }}">
  290.                                         {% else %}
  291.                                             <img class="img-fluid main-product-img" id="main-img-{{ product.id }}" src="{{ asset('ui/img/product/p1.jpg') }}" alt="{{ product.name }}">
  292.                                         {% endif %}
  293.                                     </a>
  294.                                     <!-- Boutons de navigation (visibles au survol) -->
  295.                                     {% set allImages = product.images|default([]) %}
  296.                                     {% if product.variants is defined %}
  297.                                         {% for variant in product.variants %}
  298.                                             {% if variant.isActive and variant.images is defined %}
  299.                                                 {% for variantImg in variant.images %}
  300.                                                     {% set allImages = allImages|merge([variantImg]) %}
  301.                                                 {% endfor %}
  302.                                             {% endif %}
  303.                                         {% endfor %}
  304.                                     {% endif %}
  305.                                     {% if allImages|length > 1 %}
  306.                                         <button class="img-nav-btn img-nav-left" id="img-left-{{ product.id }}" onclick="prevImage({{ product.id }})" style="display: none;">
  307.                                             <span class="lnr lnr-chevron-left"></span>
  308.                                         </button>
  309.                                         <button class="img-nav-btn img-nav-right" id="img-right-{{ product.id }}" onclick="nextImage({{ product.id }})" style="display: none;">
  310.                                             <span class="lnr lnr-chevron-right"></span>
  311.                                         </button>
  312.                                         <!-- Indicateurs de position -->
  313.                                         <div class="img-indicators" id="img-indicators-{{ product.id }}" style="display: none;">
  314.                                             {% for img in allImages %}
  315.                                                 <span class="indicator" id="indicator-{{ product.id }}-{{ loop.index0 }}" onclick="showImage({{ product.id }}, {{ loop.index0 }})"></span>
  316.                                             {% endfor %}
  317.                                         </div>
  318.                                     {% endif %}
  319.                                 </div>
  320.                                 <div class="product-details">
  321.                                     <a href="{{ path('ui_product_show', { slug: product.slug }) }}">
  322.                                         <h6>{{ product.name }}</h6>
  323.                                     </a>
  324.                                     <!-- Affichage de la boutique -->
  325.                                     <div class="shop-info">
  326.                                         <small class="text-muted">
  327.                                             <i class="lnr lnr-store"></i>
  328.                                             Vendu par :
  329.                                             {% if product.shop is defined and product.shop %}
  330.                                                 <a href="{{ path('ui_shop_show', {'slug': product.shop.slug}) }}" class="shop-link">
  331.                                                     {{ product.shop.name }}
  332.                                                 </a>
  333.                                             {% else %}
  334.                                                 <span class="text-muted">Boutique inconnue</span>
  335.                                             {% endif %}
  336.                                         </small>
  337.                                     </div>
  338.                                     <div class="price">
  339.                                         <h6>{{ product.price|number_format(2, '.', ' ') }}
  340.                                             HTG</h6>
  341.                                         {% if product.compareAtPrice %}
  342.                                             <h6 class="l-through">{{ product.compareAtPrice|number_format(2, '.', ' ') }}
  343.                                                 HTG</h6>
  344.                                         {% endif %}
  345.                                     </div>
  346.                                     <div class="prd-bottom">
  347.                                         <a href="javascript:void(0)" class="social-info add-to-cart" data-product-id="{{ product.id }}" data-qty="1">
  348.                                             <span class="ti-bag"></span>
  349.                                             <p class="hover-text">+ panier</p>
  350.                                         </a>
  351.                                         <a href="#" class="social-info wishlist-btn" data-product-id="{{ product.id }}" {% if app.user %} onclick="toggleWishlist({{ product.id }}, this); return false;" {% else %} onclick="alert('Vous devez être connecté pour ajouter aux favoris'); return false;" {% endif %}>
  352.                                             <span class="lnr lnr-heart"></span>
  353.                                             <p class="hover-text">Favoris</p>
  354.                                         </a>
  355.                                         <a href="#" class="social-info comparison-btn" data-product-id="{{ product.id }}" {% if app.user %} onclick="toggleComparison({{ product.id }}, this); return false;" {% else %} onclick="alert('Vous devez être connecté pour comparer des produits'); return false;" {% endif %}>
  356.                                             <span class="lnr lnr-sync"></span>
  357.                                             <p class="hover-text">Comparer</p>
  358.                                         </a>
  359.                                         <a href="{{ path('ui_product_show', { slug: product.slug }) }}" class="social-info">
  360.                                             <span class="lnr lnr-move"></span>
  361.                                             <p class="hover-text">Voir plus</p>
  362.                                         </a>
  363.                                     </div>
  364.                                 </div>
  365.                             </div>
  366.                         </div>
  367.                     {% else %}
  368.                         <div class="col-12">
  369.                             <p>Aucun produit disponible.</p>
  370.                         </div>
  371.                     {% endfor %}
  372.                 </div>
  373.                 <!-- Bouton Charger plus de produits -->
  374.                 {% if totalPages > currentPage %}
  375.                     <div class="text-center mt-4 mb-4" id="loadMoreContainer">
  376.                         <button type="button" class="btn btn-primary btn-lg" id="loadMoreBtn" onclick="loadMoreProducts()">
  377.                             <i class="lnr lnr-plus-circle me-2"></i>Charger plus de produits
  378.                         </button>
  379.                     </div>
  380.                 {% endif %}
  381.                 <!-- Message de fin -->
  382.                 <div id="noMoreProducts" class="text-center py-4" style="display: none;">
  383.                     <p class="text-muted">
  384.                         <i class="lnr lnr-checkmark-circle"></i>
  385.                         Tous les produits ont été chargés</p>
  386.                 </div>
  387.             </section>
  388.             <!-- End Best Seller -->
  389.         </div>
  390.     </div>
  391. </div>{% endblock %}{% block title %}Liste des produits | MaketOu{% endblock %}{% block stylesheets %}
  392. <style>
  393.     /* Styles pour la navigation des images */
  394.     .product-image-container {
  395.         position: relative;
  396.         overflow: hidden;
  397.         border-radius: 8px;
  398.     }
  399.     .img-nav-btn {
  400.         position: absolute;
  401.         top: 50%;
  402.         transform: translateY(-50%);
  403.         background: rgba(0, 0, 0, 0.6);
  404.         color: white;
  405.         border: none;
  406.         width: 35px;
  407.         height: 35px;
  408.         border-radius: 50%;
  409.         display: flex;
  410.         align-items: center;
  411.         justify-content: center;
  412.         cursor: pointer;
  413.         transition: all 0.3s ease;
  414.         z-index: 10;
  415.     }
  416.     .img-nav-btn:hover {
  417.         background: #095ad3;
  418.         transform: translateY(-50%) scale(1.1);
  419.     }
  420.     .img-nav-left {
  421.         left: 10px;
  422.     }
  423.     .img-nav-right {
  424.         right: 10px;
  425.     }
  426.     .img-indicators {
  427.         position: absolute;
  428.         bottom: 10px;
  429.         left: 50%;
  430.         transform: translateX(-50%);
  431.         display: flex;
  432.         gap: 5px;
  433.         z-index: 10;
  434.     }
  435.     .indicator {
  436.         width: 8px;
  437.         height: 8px;
  438.         border-radius: 50%;
  439.         background: rgba(255, 255, 255, 0.5);
  440.         cursor: pointer;
  441.         margin-bottom: 10px;
  442.         transition: all 0.3s ease;
  443.         border: 1px solid rgba(255, 255, 255, 0.3);
  444.     }
  445.     .indicator.active {
  446.         background: #ffa200;
  447.         border-color: #ffa200;
  448.         transform: scale(1.2);
  449.     }
  450.     .indicator:hover {
  451.         background: rgba(255, 255, 255, 0.8);
  452.     }
  453.     /* Styles pour l'affichage de la boutique */
  454.     .shop-info {
  455.         margin: 8px 0;
  456.         padding: 5px 0;
  457.         border-bottom: 1px solid #f0f0f0;
  458.     }
  459.     .shop-link {
  460.         color: #007bff;
  461.         text-decoration: none;
  462.         font-weight: 500;
  463.     }
  464.     .shop-link:hover {
  465.         color: #ffa200;
  466.         text-decoration: underline;
  467.     }
  468.     /* Animation pour le survol */
  469.     .product-image-container:hover .main-product-img {
  470.         transform: scale(1.05);
  471.         transition: transform 0.3s ease;
  472.     }
  473.     .main-product-img {
  474.         transition: transform 0.3s ease;
  475.         width: 100%;
  476.         height: 100%;
  477.         object-fit: cover; /* Recadre l'image pour remplir le conteneur */
  478.         object-position: center; /* Centre l'image */
  479.         display: block;
  480.     }
  481.     /* Styles pour les images des produits associés (Deals de la semaine) */
  482.     .related-product-img-container {
  483.         position: relative;
  484.         overflow: hidden;
  485.         border-radius: 8px;
  486.         width: 100%;
  487.         height: 200px; /* Hauteur fixe pour les produits associés */
  488.         background: #f8f9fa; /* Fond au cas où l'image ne charge pas */
  489.     }
  490.     .related-product-img {
  491.         width: 100%;
  492.         height: 100%;
  493.         object-fit: cover; /* Recadre l'image pour remplir le conteneur */
  494.         object-position: center; /* Centre l'image */
  495.         display: block;
  496.         transition: transform 0.3s ease;
  497.     }
  498.     .related-product-img-container:hover .related-product-img {
  499.         transform: scale(1.05);
  500.     }
  501.     /* Styles pour l'image de catégorie dans les deals */
  502.     .category-banner-container {
  503.         display: block;
  504.         position: relative;
  505.         overflow: hidden;
  506.         border-radius: 8px;
  507.         width: 100%;
  508.         height: 400px; /* Hauteur fixe pour la bannière de catégorie */
  509.         background: #f8f9fa; /* Fond au cas où l'image ne charge pas */
  510.     }
  511.     .category-banner-img {
  512.         width: 100%;
  513.         height: 100%;
  514.         object-fit: cover; /* Recadre l'image pour remplir le conteneur */
  515.         object-position: center; /* Centre l'image */
  516.         display: block;
  517.         transition: transform 0.3s ease;
  518.     }
  519.     .category-banner-container:hover .category-banner-img {
  520.         transform: scale(1.05);
  521.     }
  522.     /* Styles pour les images de la modal Quick Product View */
  523.     .quick-view-carousel .item {
  524.         width: 100%;
  525.         height: 400px; /* Hauteur fixe pour les images du carousel */
  526.         background-size: cover;
  527.         background-position: center;
  528.         background-repeat: no-repeat;
  529.         border-radius: 8px;
  530.     }
  531.     /* Contraindre le conteneur d'image pour maintenir les proportions */
  532.     .product-image-container {
  533.         position: relative;
  534.         overflow: hidden;
  535.         border-radius: 8px;
  536.         width: 200px;
  537.         aspect-ratio: 1 / 1; /* Conteneur carré */
  538.         background: #f8f9fa; /* Fond au cas où l'image ne charge pas */
  539.     }
  540.     /* Styles pour le bouton de filtre amélioré */
  541.     .filter-icon-circle {
  542.         transition: all 0.3s ease;
  543.     }
  544.     button[data-bs-target="#filtersModal"]:hover .filter-icon-circle {
  545.         background: linear-gradient(135deg, #ff8c00 0%, #ff7700 100%) !important;
  546.         transform: rotate(15deg) scale(1.1);
  547.         box-shadow: 0 4px 15px rgba(255, 162, 0, 0.5);
  548.     }
  549.     button[data-bs-target="#filtersModal"]:hover {
  550.         transform: translateY(-2px);
  551.     }
  552.     button[data-bs-target="#filtersModal"]:active {
  553.         transform: translateY(0);
  554.     }
  555.     button[data-bs-target="#filtersModal"]:hover span {
  556.         color: #ffa200 !important;
  557.     }
  558.     /* Styles pour le modal de filtres */
  559.     #filtersModal .modal-body {
  560.         padding: 1.5rem;
  561.     }
  562.     #filtersModal .modal-body::-webkit-scrollbar {
  563.         width: 8px;
  564.     }
  565.     #filtersModal .modal-body::-webkit-scrollbar-track {
  566.         background: #f1f1f1;
  567.         border-radius: 4px;
  568.     }
  569.     #filtersModal .modal-body::-webkit-scrollbar-thumb {
  570.         background: #ffa200;
  571.         border-radius: 4px;
  572.     }
  573.     #filtersModal .modal-body::-webkit-scrollbar-thumb:hover {
  574.         background: #e69100;
  575.     }
  576.     #filtersModal .modal-footer {
  577.         box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
  578.     }
  579.     /* Styles pour les filtres avancés */
  580.     .weight-range-area {
  581.         padding: 15px 0;
  582.     }
  583.     .weight-inputs {
  584.         gap: 15px;
  585.         align-items: center;
  586.     }
  587.     .weight-input {
  588.         flex: 1;
  589.     }
  590.     .weight-input label {
  591.         display: block;
  592.         margin-bottom: 5px;
  593.         font-weight: 500;
  594.         color: #333;
  595.     }
  596.     .weight-input input {
  597.         width: 100%;
  598.         padding: 8px 12px;
  599.         border: 1px solid #ddd;
  600.         border-radius: 4px;
  601.         font-size: 14px;
  602.     }
  603.     .weight-input input:focus {
  604.         outline: none;
  605.         border-color: #ffa200;
  606.         box-shadow: 0 0 0 2px rgba(255, 162, 0, 0.2);
  607.     }
  608.     /* Styles pour les filtres dynamiques */
  609.     .shop-list,
  610.     .color-list,
  611.     .size-list,
  612.     .material-list,
  613.     .condition-list {
  614.         max-height: 200px;
  615.         overflow-y: auto;
  616.         padding-right: 5px;
  617.     }
  618.     .shop-list::-webkit-scrollbar,
  619.     .color-list::-webkit-scrollbar,
  620.     .size-list::-webkit-scrollbar,
  621.     .material-list::-webkit-scrollbar,
  622.     .condition-list::-webkit-scrollbar {
  623.         width: 6px;
  624.     }
  625.     .shop-list::-webkit-scrollbar-track,
  626.     .color-list::-webkit-scrollbar-track,
  627.     .size-list::-webkit-scrollbar-track,
  628.     .material-list::-webkit-scrollbar-track,
  629.     .condition-list::-webkit-scrollbar-track {
  630.         background: #f1f1f1;
  631.         border-radius: 3px;
  632.     }
  633.     .shop-list::-webkit-scrollbar-thumb,
  634.     .color-list::-webkit-scrollbar-thumb,
  635.     .size-list::-webkit-scrollbar-thumb,
  636.     .material-list::-webkit-scrollbar-thumb,
  637.     .condition-list::-webkit-scrollbar-thumb {
  638.         background: #ffa200;
  639.         border-radius: 3px;
  640.     }
  641.     .shop-list::-webkit-scrollbar-thumb:hover,
  642.     .color-list::-webkit-scrollbar-thumb:hover,
  643.     .size-list::-webkit-scrollbar-thumb:hover,
  644.     .material-list::-webkit-scrollbar-thumb:hover,
  645.     .condition-list::-webkit-scrollbar-thumb:hover {
  646.         background: #e69100;
  647.     }
  648.     /* Animation pour les filtres qui apparaissent */
  649.     .common-filter {
  650.         transition: all 0.3s ease;
  651.     }
  652.     .common-filter[style*="display: none"] {
  653.         opacity: 0;
  654.         transform: translateY(-10px);
  655.     }
  656.     .common-filter[style*="display: block"] {
  657.         opacity: 1;
  658.         transform: translateY(0);
  659.     }
  660.     /* Styles pour les indicateurs de chargement */
  661.     .loading-indicator {
  662.         display: inline-block;
  663.         width: 20px;
  664.         height: 20px;
  665.         border: 3px solid #f3f3f3;
  666.         border-top: 3px solid #ffa200;
  667.         border-radius: 50%;
  668.         animation: spin 1s linear infinite;
  669.     }
  670.     @keyframes spin {
  671.         0% {
  672.             transform: rotate(0deg);
  673.         }
  674.         100% {
  675.             transform: rotate(360deg);
  676.         }
  677.     }
  678.     /* Styles pour le loader de chargement infini */
  679.     .infinite-loader {
  680.         display: flex;
  681.         flex-direction: column;
  682.         align-items: center;
  683.         justify-content: center;
  684.         padding: 20px;
  685.     }
  686.     .loader-spinner {
  687.         width: 50px;
  688.         height: 50px;
  689.         border: 4px solid #f3f3f3;
  690.         border-top: 4px solid #ffa200;
  691.         border-radius: 50%;
  692.         animation: spin 1s linear infinite;
  693.     }
  694.     #infiniteScrollLoader {
  695.         min-height: 100px;
  696.     }
  697.     #noMoreProducts {
  698.         min-height: 60px;
  699.         color: #6c757d;
  700.     }
  701.     #noMoreProducts i {
  702.         font-size: 24px;
  703.         color: #28a745;
  704.         margin-right: 8px;
  705.     }
  706.     /* Responsive pour les filtres */
  707.     @media(max-width: 768px) {
  708.         .weight-inputs {
  709.             flex-direction: column;
  710.             gap: 10px;
  711.         }
  712.         .weight-input {
  713.             width: 100%;
  714.         }
  715.         .common-filter {
  716.             margin-bottom: 20px;
  717.         }
  718.         .shop-list,
  719.         .color-list,
  720.         .size-list,
  721.         .material-list,
  722.         .condition-list {
  723.             max-height: 150px;
  724.         }
  725.     }
  726.     /* Bouton toggle sidebar mobile */
  727.     .listing-sidebar-toggle {
  728.         border-radius: 0.5rem;
  729.         padding: 0.75rem;
  730.         font-weight: 500;
  731.         display: flex;
  732.         align-items: center;
  733.         justify-content: center;
  734.     }
  735.     .listing-sidebar-toggle .toggle-icon {
  736.         transition: transform 0.3s ease;
  737.         display: inline-block;
  738.     }
  739.     /* État fermé (par défaut) */
  740.     .listing-sidebar-toggle[aria-expanded="false"] .toggle-icon,
  741.     .listing-sidebar-toggle.collapsed .toggle-icon {
  742.         transform: rotate(0deg);
  743.     }
  744.     /* État ouvert */
  745.     .listing-sidebar-toggle[aria-expanded="true"] .toggle-icon,
  746.     .listing-sidebar-toggle:not(.collapsed) .toggle-icon {
  747.         transform: rotate(180deg);
  748.     }
  749.     /* Sidebar mobile */
  750.     @media(max-width: 767.98px) {
  751.         #listingSidebarCollapse {
  752.             margin-bottom: 1rem;
  753.         }
  754.     }
  755. </style>{% endblock %}{% block javascripts %}
  756. <script>
  757. // === Script isolé : chargement de plus de produits (comme les boutiques) ===
  758. var currentPage = {{ currentPage }};
  759. var totalPages = {{ totalPages }};
  760. var isLoading = false;
  761. function loadMoreProducts() {
  762.     if (isLoading || currentPage >= totalPages) return;
  763.     isLoading = true;
  764.     var btn = document.getElementById('loadMoreBtn');
  765.     var productsContainer = document.getElementById('productsContainer');
  766.     if (!btn || !productsContainer) {
  767.         isLoading = false;
  768.         return;
  769.     }
  770.     var originalText = btn.innerHTML;
  771.     btn.disabled = true;
  772.     btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Chargement...';
  773.     currentPage++;
  774.     var url = new URL(window.location.href);
  775.     url.searchParams.set('page', currentPage);
  776.     fetch(url.toString(), {
  777.         headers: { 'X-Requested-With': 'XMLHttpRequest' }
  778.     })
  779.     .then(function(response) { return response.json(); })
  780.     .then(function(data) {
  781.         if (data.success && data.products) {
  782.             var tempDiv = document.createElement('div');
  783.             tempDiv.innerHTML = data.products;
  784.             var newProducts = tempDiv.querySelectorAll('.col-lg-4, .col-lg-3, [class*="col-lg"]');
  785.             newProducts.forEach(function(product) {
  786.                 productsContainer.appendChild(product);
  787.             });
  788.             currentPage = data.pagination.currentPage;
  789.             totalPages = data.pagination.totalPages;
  790.             // Initialiser les images et events pour les nouveaux produits
  791.             if (typeof initializeProductImages === 'function') initializeProductImages(tempDiv);
  792.             if (typeof initializeProductEventListeners === 'function') initializeProductEventListeners(tempDiv);
  793.             if (currentPage >= totalPages) {
  794.                 document.getElementById('loadMoreContainer').style.display = 'none';
  795.             } else {
  796.                 btn.disabled = false;
  797.                 btn.innerHTML = originalText;
  798.             }
  799.         } else {
  800.             document.getElementById('loadMoreContainer').style.display = 'none';
  801.         }
  802.         isLoading = false;
  803.     })
  804.     .catch(function(error) {
  805.         btn.disabled = false;
  806.         btn.innerHTML = originalText;
  807.         isLoading = false;
  808.     });
  809. }
  810. </script>
  811. <script>
  812.     // Variables globales pour la navigation des images
  813. const productImages = {};
  814. const currentImageIndex = {};
  815. // Variables pour le chargement infini (utilise les var globales définies au-dessus)
  816. let hasMoreProducts = totalPages > currentPage;
  817. let currentFilters = {
  818. category: '{{ currentCategory ?? '' }}',
  819. brand: '{{ currentBrand ?? '' }}',
  820. sort: '{{ currentSort ?? 'newest' }}',
  821. priceMin: '{{ priceMin ?? '' }}',
  822. priceMax: '{{ priceMax ?? '' }}'
  823. };
  824. // Modal style eBay pour remplacer les alertes - Définir AVANT son utilisation
  825. function showEbayModal(message, type = 'info') {
  826. const modalId = 'ebayAlertModal';
  827. let existingModal = document.getElementById(modalId);
  828. if (existingModal) {
  829. existingModal.remove();
  830. }
  831. const modal = document.createElement('div');
  832. modal.id = modalId;
  833. modal.className = 'modal fade';
  834. modal.setAttribute('tabindex', '-1');
  835. modal.setAttribute('aria-labelledby', 'ebayAlertModalLabel');
  836. modal.setAttribute('aria-hidden', 'true');
  837. // Définir les couleurs et icônes selon le type
  838. let iconClass = 'ti-info-alt';
  839. let iconColor = '#0064D2';
  840. let title = 'Information';
  841. switch (type) {
  842. case 'success': iconClass = 'ti-check-box';
  843. iconColor = '#0A7C42';
  844. title = 'Succès';
  845. break;
  846. case 'error':
  847. case 'danger': iconClass = 'ti-alert';
  848. iconColor = '#D32F2F';
  849. title = 'Erreur';
  850. break;
  851. case 'warning': iconClass = 'ti-alert-triangle';
  852. iconColor = '#F57C00';
  853. title = 'Attention';
  854. break;
  855. default: iconClass = 'ti-info-alt';
  856. iconColor = '#0064D2';
  857. title = 'Information';
  858. }
  859. modal.innerHTML = `
  860. <div class="modal-dialog modal-dialog-centered">
  861. <div class="modal-content" style="border-radius: 8px; border: none; box-shadow: 0 4px 20px rgba(0,0,0,0.15);">
  862. <div class="modal-header" style="border-bottom: 1px solid #e0e0e0; padding: 20px 24px;">
  863. <h5 class="modal-title" id="ebayAlertModalLabel" style="font-weight: 600; font-size: 18px; color: #333;">
  864. <i class="${iconClass}" style="color: ${iconColor}; margin-right: 8px;"></i>${title}
  865. </h5>
  866. <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" style="margin: 0;"></button>
  867. </div>
  868. <div class="modal-body" style="padding: 24px; text-align: center;">
  869. <p style="margin: 0; font-size: 16px; color: #333; line-height: 1.5;">${message}</p>
  870. </div>
  871. <div class="modal-footer" style="border-top: 1px solid #e0e0e0; padding: 16px 24px; justify-content: center;">
  872. <button type="button" class="btn btn-primary" data-bs-dismiss="modal" style="min-width: 100px; background-color: ${iconColor}; border-color: ${iconColor}; border-radius: 4px; font-weight: 500;">
  873. OK
  874. </button>
  875. </div>
  876. </div>
  877. </div>
  878. `;
  879. document.body.appendChild(modal);
  880. const bsModal = new bootstrap.Modal(modal);
  881. bsModal.show();
  882. // Supprimer le modal du DOM après fermeture
  883. modal.addEventListener('hidden.bs.modal', function () {
  884. modal.remove();
  885. });
  886. }
  887. // Rendre la fonction globale
  888. window.showEbayModal = showEbayModal;
  889. document.addEventListener('DOMContentLoaded', function () { // Initialisation des données d'images pour chaque produit
  890. {% for product in products %}
  891. {% set allImages = product.images|default([]) %}
  892. {% if product.variants is defined %}
  893.     {% for variant in product.variants %}
  894.         {% if variant.isActive and variant.images is defined %}
  895.             {% for variantImg in variant.images %}
  896.                 {% set allImages = allImages|merge([variantImg]) %}
  897.             {% endfor %}
  898.         {% endif %}
  899.     {% endfor %}
  900. {% endif %}
  901. {% if allImages|length > 0 %}productImages[{{ product.id }}] = [{% for img in allImages %}'{{ asset(img) }}'{% if not loop.last %},{% endif %}{% endfor %}];
  902. currentImageIndex[{{ product.id }}] = 0;
  903. console.log('Initialized product {{ product.id }} with images:', productImages[{{ product.id }}]);{% endif %}{% endfor %}
  904. // Gestion du panier
  905. const cartButtons = document.querySelectorAll('.add-to-cart');
  906. cartButtons.forEach(button => {
  907. button.addEventListener('click', function () {
  908. const productId = this.getAttribute('data-product-id');
  909. const qty = this.getAttribute('data-qty');
  910. fetch('{{ path("ui_cart_add") }}', {
  911. method: 'POST',
  912. headers: {
  913. 'Content-Type': 'application/x-www-form-urlencoded'
  914. },
  915. body: 'productId=' + productId + '&qty=' + qty
  916. }).then(response => response.json()).then(data => {
  917. if (data.ok) {
  918. showEbayModal('Produit ajouté au panier !', 'success');
  919. // Mettre à jour le compteur du panier si présent
  920. const cartBadge = document.querySelector('.cart-badge');
  921. if (cartBadge) {
  922. cartBadge.textContent = data.totalQty;
  923. }
  924. } else {
  925. showEbayModal(data.message || 'Erreur lors de l\'ajout au panier', 'error');
  926. }
  927. }).catch(error => {
  928. console.error('Erreur:', error);
  929. showEbayModal('Erreur lors de l\'ajout au panier', 'error');
  930. });
  931. });
  932. });
  933. });
  934. // Fonctions pour la navigation des images
  935. window.showImageNav = function(productId) {
  936. const leftBtn = document.getElementById('img-left-' + productId);
  937. const rightBtn = document.getElementById('img-right-' + productId);
  938. const indicators = document.getElementById('img-indicators-' + productId);
  939. if (leftBtn) {
  940. leftBtn.style.display = 'flex';
  941. }
  942. if (rightBtn) {
  943. rightBtn.style.display = 'flex';
  944. }
  945. if (indicators) {
  946. indicators.style.display = 'flex';
  947. }
  948. };
  949. window.hideImageNav = function(productId) {
  950. const leftBtn = document.getElementById('img-left-' + productId);
  951. const rightBtn = document.getElementById('img-right-' + productId);
  952. const indicators = document.getElementById('img-indicators-' + productId);
  953. if (leftBtn) {
  954. leftBtn.style.display = 'none';
  955. }
  956. if (rightBtn) {
  957. rightBtn.style.display = 'none';
  958. }
  959. if (indicators) {
  960. indicators.style.display = 'none';
  961. }
  962. };
  963. window.showImage = function(productId, imageIndex) {
  964. if (! productImages[productId] || ! productImages[productId][imageIndex]) {
  965. console.log('Image not found for product', productId, 'index', imageIndex);
  966. return;
  967. }
  968. const img = document.getElementById('main-img-' + productId);
  969. if (img) {
  970. console.log('Changing image to:', productImages[productId][imageIndex]);
  971. // Force le rechargement de l'image
  972. img.style.opacity = '0.5';
  973. setTimeout(() => {
  974. img.src = productImages[productId][imageIndex];
  975. img.style.opacity = '1';
  976. currentImageIndex[productId] = imageIndex;
  977. updateIndicators(productId);
  978. }, 100);
  979. } else {
  980. console.log('Image element not found:', 'main-img-' + productId);
  981. }
  982. };
  983. window.nextImage = function(productId) {
  984. if (! productImages[productId]) {
  985. console.log('No images for product', productId);
  986. return;
  987. }
  988. const nextIndex = (currentImageIndex[productId] + 1) % productImages[productId].length;
  989. showImage(productId, nextIndex);
  990. };
  991. window.prevImage = function(productId) {
  992. if (! productImages[productId]) {
  993. console.log('No images for product', productId);
  994. return;
  995. }
  996. const prevIndex = currentImageIndex[productId] === 0 ? productImages[productId].length - 1 : currentImageIndex[productId] - 1;
  997. showImage(productId, prevIndex);
  998. };
  999. function updateIndicators(productId) {
  1000. const indicators = document.querySelectorAll('#img-indicators-' + productId + ' .indicator');
  1001. indicators.forEach((indicator, index) => {
  1002. indicator.classList.toggle('active', index === currentImageIndex[productId]);
  1003. });
  1004. }
  1005. // Fonction pour tracker la vue d'un produit
  1006. function trackProductView(productId) {
  1007. fetch (`/api/track-product-view/${productId}`, {
  1008. method: 'POST',
  1009. headers: {
  1010. 'Content-Type': 'application/json',
  1011. 'X-Requested-With': 'XMLHttpRequest'
  1012. }
  1013. }).then(response => response.json()).then(data => {
  1014. if (data.success) {
  1015. console.log (`Vue enregistrée pour le produit ${productId}:`, data.viewCount);
  1016. }
  1017. }).catch(error => {
  1018. console.error('Erreur lors du tracking:', error);
  1019. });
  1020. }
  1021. // Fonction pour tracker la vue d'une boutique
  1022. function trackShopView(shopId) {
  1023. fetch (`/api/track-shop-view/${shopId}`, {
  1024. method: 'POST',
  1025. headers: {
  1026. 'Content-Type': 'application/json',
  1027. 'X-Requested-With': 'XMLHttpRequest'
  1028. }
  1029. }).then(response => response.json()).then(data => {
  1030. if (data.success) {
  1031. console.log (`Vue enregistrée pour la boutique ${shopId}:`, data.viewCount);
  1032. }
  1033. }).catch(error => {
  1034. console.error('Erreur lors du tracking:', error);
  1035. });
  1036. }
  1037. // Ajouter le tracking aux événements existants
  1038. document.addEventListener('DOMContentLoaded', function () { // Tracking au survol des produits (avec délai)
  1039. const productCards = document.querySelectorAll('.single_product_item');
  1040. productCards.forEach(card => {
  1041. const productId = card.dataset.productId;
  1042. const shopId = card.dataset.shopId;
  1043. if (productId) {
  1044. let hoverTimeout;
  1045. card.addEventListener('mouseenter', () => { // Tracker la vue du produit après 2 secondes de survol
  1046. hoverTimeout = setTimeout(() => {
  1047. trackProductView(productId);
  1048. }, 2000);
  1049. });
  1050. card.addEventListener('mouseleave', () => {
  1051. clearTimeout(hoverTimeout);
  1052. });
  1053. // Tracking au clic sur le lien du produit
  1054. const productLink = card.querySelector('.product_img a');
  1055. if (productLink) {
  1056. productLink.addEventListener('click', () => {
  1057. trackProductView(productId);
  1058. });
  1059. }
  1060. }
  1061. // Tracking au clic sur le lien de la boutique
  1062. if (shopId) {
  1063. const shopLink = card.querySelector('.shop-link');
  1064. if (shopLink) {
  1065. shopLink.addEventListener('click', () => {
  1066. trackShopView(shopId);
  1067. });
  1068. }
  1069. }
  1070. });
  1071. });
  1072. </script>
  1073. <script>
  1074.     // Système de filtrage AJAX avancé
  1075. // currentFilters est déjà déclaré plus haut, on met juste à jour les valeurs
  1076. if (typeof currentFilters !== 'undefined') { // Mettre à jour les valeurs existantes
  1077. currentFilters.category = '{{ currentCategory }}';
  1078. currentFilters.brand = '{{ currentBrand }}';
  1079. currentFilters.condition = '{{ currentCondition }}';
  1080. currentFilters.sort = '{{ currentSort }}';
  1081. currentFilters.priceMin = '{{ priceMin }}';
  1082. currentFilters.priceMax = '{{ priceMax }}';
  1083. currentFilters.page = {{ currentPage }};
  1084. currentFilters.q = currentFilters.q || '{{ app.request.query.get('q', '') }}';
  1085. // Ajouter les nouveaux filtres depuis l'URL s'ils n'existent pas
  1086. if (! currentFilters.shop) 
  1087. currentFilters.shop = '{{ app.request.query.get('shop', '') }}';
  1088. if (! currentFilters.featured) 
  1089. currentFilters.featured = '{{ app.request.query.get('featured', '') }}';
  1090. if (! currentFilters.digital) 
  1091. currentFilters.digital = '{{ app.request.query.get('digital', '') }}';
  1092. if (! currentFilters.stockStatus) 
  1093. currentFilters.stockStatus = '{{ app.request.query.get('stock_status', '') }}';
  1094. if (! currentFilters.ratingMin) 
  1095. currentFilters.ratingMin = '{{ app.request.query.get('rating_min', '') }}';
  1096. if (! currentFilters.rating) 
  1097. currentFilters.rating = '{{ app.request.query.get('rating_min', '') }}';
  1098. if (! currentFilters.weightMin) 
  1099. currentFilters.weightMin = '{{ app.request.query.get('weight_min', '') }}';
  1100. if (! currentFilters.weightMax) 
  1101. currentFilters.weightMax = '{{ app.request.query.get('weight_max', '') }}';
  1102. if (! currentFilters.color) 
  1103. currentFilters.color = '{{ app.request.query.get('color', '') }}';
  1104. if (! currentFilters.size) 
  1105. currentFilters.size = '{{ app.request.query.get('size', '') }}';
  1106. if (! currentFilters.material) 
  1107. currentFilters.material = '{{ app.request.query.get('material', '') }}';
  1108. if (! currentFilters.condition) 
  1109. currentFilters.condition = '{{ app.request.query.get('condition', '') }}';
  1110. if (! currentFilters.availability) 
  1111. currentFilters.availability = '{{ app.request.query.get('availability', '') }}';
  1112. } else { // Si currentFilters n'existe pas, le créer avec les valeurs de l'URL
  1113. let currentFilters = {
  1114. category: '{{ currentCategory ?? '' }}',
  1115. brand: '{{ currentBrand ?? '' }}',
  1116. condition: '{{ currentCondition ?? '' }}',
  1117. sort: '{{ currentSort ?? 'newest' }}',
  1118. priceMin: '{{ app.request.query.get('price_min', '') }}',
  1119. priceMax: '{{ app.request.query.get('price_max', '') }}',
  1120. page: {{ currentPage }},
  1121. q: '{{ app.request.query.get('q', '') }}',
  1122. // Nouveaux filtres depuis l'URL
  1123. shop: '{{ app.request.query.get('shop', '') }}',
  1124. featured: '{{ app.request.query.get('featured', '') }}',
  1125. digital: '{{ app.request.query.get('digital', '') }}',
  1126. stockStatus: '{{ app.request.query.get('stock_status', '') }}',
  1127. ratingMin: '{{ app.request.query.get('rating_min', '') }}',
  1128. rating: '{{ app.request.query.get('rating_min', '') }}',
  1129. weightMin: '{{ app.request.query.get('weight_min', '') }}',
  1130. weightMax: '{{ app.request.query.get('weight_max', '') }}',
  1131. color: '{{ app.request.query.get('color', '') }}',
  1132. size: '{{ app.request.query.get('size', '') }}',
  1133. material: '{{ app.request.query.get('material', '') }}',
  1134. condition: '{{ app.request.query.get('condition', '') }}',
  1135. availability: '{{ app.request.query.get('availability', '') }}'
  1136. };
  1137. }
  1138. // Fonction pour appliquer les filtres
  1139. function applyFilters() {
  1140. const url = new URL('{{ path('ui_api_products_filter') }}', window.location.origin);
  1141. // Ajouter les paramètres
  1142. Object.keys(currentFilters).forEach(key => {
  1143. if (currentFilters[key] && currentFilters[key] !== '') { // Mapper les clés pour l'API
  1144. let apiKey = key;
  1145. if (key === 'priceMin') {
  1146. apiKey = 'price_min';
  1147. } else if (key === 'priceMax') {
  1148. apiKey = 'price_max';
  1149. } else if (key === 'ratingMin') {
  1150. apiKey = 'rating_min';
  1151. } else if (key === 'weightMin') {
  1152. apiKey = 'weight_min';
  1153. } else if (key === 'weightMax') {
  1154. apiKey = 'weight_max';
  1155. } else if (key === 'stockStatus') {
  1156. apiKey = 'stock_status';
  1157. }
  1158. url.searchParams.set(apiKey, currentFilters[key]);
  1159. }
  1160. });
  1161. // Afficher le loader
  1162. document.getElementById('productsContainer').innerHTML = '<div class="col-12 text-center"><i class="fa fa-spinner fa-spin"></i> Chargement...</div>';
  1163. // Faire la requête AJAX
  1164. fetch(url).then(response => response.json()).then(data => {
  1165. if (data.success) { // Mettre à jour les produits
  1166. document.getElementById('productsContainer').innerHTML = data.products;
  1167. // Mettre à jour la pagination
  1168. currentPage = data.pagination.currentPage;
  1169. totalPages = data.pagination.totalPages;
  1170. hasMoreProducts = currentPage < totalPages;
  1171. // Préserver l'état des filtres statiques avant la mise à jour
  1172. const preservedFilters = {
  1173. featured: currentFilters.featured,
  1174. digital: currentFilters.digital,
  1175. availability: currentFilters.availability,
  1176. rating: currentFilters.rating || currentFilters.ratingMin
  1177. };
  1178. // Mettre à jour les marques disponibles (toujours afficher toutes)
  1179. updateAvailableBrands(data.availableBrands || []);
  1180. // Mettre à jour les conditions disponibles (toujours afficher toutes)
  1181. updateAvailableConditions(data.availableConditions || []);
  1182. // Mettre à jour les boutiques disponibles (toujours afficher toutes)
  1183. updateAvailableShops(data.availableShops || []);
  1184. // Mettre à jour les attributs disponibles (toujours afficher tous)
  1185. updateAvailableAttributes(data.availableAttributes || {});
  1186. // Restaurer l'état des filtres statiques après la mise à jour
  1187. restoreStaticFiltersState(preservedFilters);
  1188. // Réinitialiser les filtres depuis currentFilters pour s'assurer qu'ils sont tous cochés
  1189. if (typeof initializeFiltersFromURL === 'function') {
  1190. initializeFiltersFromURL();
  1191. }
  1192. // Mettre à jour l'URL
  1193. updateURL();
  1194. // Mettre à jour l'affichage du bouton
  1195. updateLoadMoreButton();
  1196. // Réinitialiser les event listeners pour les nouveaux produits
  1197. const tempDiv = document.createElement('div');
  1198. tempDiv.innerHTML = data.products;
  1199. initializeProductEventListeners(tempDiv);
  1200. initializeProductImages(tempDiv);
  1201. // Mettre à jour les filtres actifs
  1202. if (typeof updateActiveFilters === 'function') {
  1203. updateActiveFilters();
  1204. }
  1205. }
  1206. }).catch(error => {
  1207. console.error('Erreur lors du filtrage:', error);
  1208. document.getElementById('productsContainer').innerHTML = '<div class="col-12 text-center text-danger">Erreur lors du chargement des produits</div>';
  1209. });
  1210. }
  1211. // Exposer la fonction globalement
  1212. window.applyFilters = applyFilters;
  1213. // Fonction pour appliquer le tri
  1214. function applySorting() {
  1215. currentFilters.sort = document.getElementById('sortSelect').value;
  1216. currentFilters.page = 1;
  1217. applyFilters();
  1218. }
  1219. // Fonction pour appliquer le filtre de prix
  1220. function applyPriceFilter() {
  1221. currentFilters.priceMin = document.getElementById('priceMin').value;
  1222. currentFilters.priceMax = document.getElementById('priceMax').value;
  1223. currentFilters.page = 1;
  1224. applyFilters();
  1225. }
  1226. // Fonction pour appliquer le filtre de poids
  1227. function applyWeightFilter() {
  1228. currentFilters.weightMin = document.getElementById('weightMin').value;
  1229. currentFilters.weightMax = document.getElementById('weightMax').value;
  1230. currentFilters.page = 1;
  1231. applyFilters();
  1232. }
  1233. // Fonction pour changer de page
  1234. function changePage(page) {
  1235. currentFilters.page = page;
  1236. applyFilters();
  1237. }
  1238. // Fonction pour mettre à jour les marques disponibles
  1239. function updateAvailableBrands(brands) {
  1240. const brandsFilter = document.getElementById('brandsFilter');
  1241. const brandList = document.getElementById('brandList');
  1242. const currentBrandValue = currentFilters.brand || '';
  1243. // Toujours afficher le filtre des marques
  1244. brandsFilter.style.display = 'block';
  1245. // Si pas de marques disponibles, garder les marques existantes ou afficher un message
  1246. if (brands.length === 0) { // Ne pas réinitialiser si la liste existe déjà et a du contenu
  1247. if (brandList && brandList.children.length > 0) { // Juste mettre à jour les états cochés
  1248. const existingRadios = brandList.querySelectorAll('input[name="brand"]');
  1249. existingRadios.forEach(radio => {
  1250. if (currentBrandValue && radio.value === currentBrandValue) {
  1251. radio.checked = true;
  1252. } else if (! currentBrandValue && radio.id === 'all-brands') {
  1253. radio.checked = true;
  1254. } else {
  1255. radio.checked = false;
  1256. }
  1257. });
  1258. return; // Garder les marques existantes
  1259. }
  1260. // Sinon, afficher un message
  1261. brandList.innerHTML = '<li class="filter-list"><span class="text-muted">Aucune marque disponible</span></li>';
  1262. return;
  1263. }
  1264. let html = '';
  1265. const isAllSelected = ! currentBrandValue || currentBrandValue === '';
  1266. html += `<li class="filter-list"><input class="pixel-radio" type="radio" id="all-brands" name="brand" ${
  1267. isAllSelected ? 'checked' : ''
  1268. }><label for="all-brands">Toutes les marques</label></li>`;
  1269. brands.forEach(brand => {
  1270. const isSelected = currentBrandValue === brand.slug;
  1271. html += `<li class="filter-list">
  1272.                 <input class="pixel-radio" type="radio" id="brand-${
  1273. brand.id
  1274. }" name="brand" value="${
  1275. brand.slug
  1276. }" ${
  1277. isSelected ? 'checked' : ''
  1278. }>
  1279.                 <label for="brand-${
  1280. brand.id
  1281. }">${
  1282. brand.name
  1283. }<span>(${
  1284. brand.productCount
  1285. })</span></label>
  1286.             </li>`;
  1287. });
  1288. brandList.innerHTML = html;
  1289. // Ajouter les event listeners
  1290. brandList.querySelectorAll('input[name="brand"]').forEach(radio => {
  1291. radio.addEventListener('change', function () {
  1292. if (this.id === 'all-brands') {
  1293. currentFilters.brand = '';
  1294. } else {
  1295. currentFilters.brand = this.value;
  1296. } currentFilters.page = 1;
  1297. applyFilters();
  1298. });
  1299. });
  1300. }
  1301. // Fonction pour mettre à jour les conditions disponibles
  1302. function updateAvailableConditions(conditions) {
  1303. const conditionList = document.getElementById('conditionList');
  1304. const currentConditionValue = currentFilters.condition || '';
  1305. // Toujours garder le contenu statique et juste mettre à jour les états cochés
  1306. if (conditionList && conditionList.children.length > 0) {
  1307. const existingRadios = conditionList.querySelectorAll('input[name="condition"]');
  1308. existingRadios.forEach(radio => {
  1309. if (currentConditionValue && radio.value === currentConditionValue) {
  1310. radio.checked = true;
  1311. } else if (! currentConditionValue && radio.id === 'all-conditions') {
  1312. radio.checked = true;
  1313. } else {
  1314. radio.checked = false;
  1315. }
  1316. });
  1317. return; // Garder les conditions existantes
  1318. }
  1319. // Si pas de contenu statique, générer dynamiquement
  1320. if (conditions.length === 0) {
  1321. conditionList.innerHTML = '<li class="filter-list"><span class="text-muted">Aucune condition disponible</span></li>';
  1322. return;
  1323. }
  1324. let html = '';
  1325. const isAllSelected = ! currentConditionValue || currentConditionValue === '';
  1326. html += `<li class="filter-list"><input class="pixel-radio" type="radio" id="all-conditions" name="condition" ${
  1327. isAllSelected ? 'checked' : ''
  1328. }><label for="all-conditions">Toutes les conditions</label></li>`;
  1329. conditions.forEach(condition => {
  1330. const isSelected = currentConditionValue === condition.slug;
  1331. html += `<li class="filter-list">
  1332.                 <input class="pixel-radio" type="radio" id="condition-${
  1333. condition.id
  1334. }" name="condition" value="${
  1335. condition.slug
  1336. }" ${
  1337. isSelected ? 'checked' : ''
  1338. }>
  1339.                 <label for="condition-${
  1340. condition.id
  1341. }">${
  1342. condition.name
  1343. }<span>(${
  1344. condition.productCount
  1345. })</span></label>
  1346.             </li>`;
  1347. });
  1348. conditionList.innerHTML = html;
  1349. // Ajouter les event listeners
  1350. conditionList.querySelectorAll('input[name="condition"]').forEach(radio => {
  1351. radio.addEventListener('change', function () {
  1352. if (this.id === 'all-conditions') {
  1353. currentFilters.condition = '';
  1354. } else {
  1355. currentFilters.condition = this.value;
  1356. } currentFilters.page = 1;
  1357. applyFilters();
  1358. });
  1359. });
  1360. }
  1361. // Fonction pour mettre à jour les boutiques disponibles
  1362. function updateAvailableShops(shops) {
  1363. const shopsFilter = document.getElementById('shopsFilter');
  1364. const shopList = document.getElementById('shopList');
  1365. const currentShopValue = currentFilters.shop || '';
  1366. // Toujours afficher le filtre des boutiques
  1367. shopsFilter.style.display = 'block';
  1368. // Si pas de boutiques disponibles, afficher un message ou garder les boutiques existantes
  1369. if (shops.length === 0) { // Ne pas réinitialiser si la liste existe déjà et a du contenu
  1370. if (shopList && shopList.children.length > 0) {
  1371. return; // Garder les boutiques existantes
  1372. }
  1373. // Sinon, afficher un message
  1374. shopList.innerHTML = '<li class="filter-list"><span class="text-muted">Aucune boutique disponible</span></li>';
  1375. return;
  1376. }
  1377. let html = '';
  1378. const isAllSelected = ! currentShopValue || currentShopValue === '';
  1379. html += `<li class="filter-list"><input class="pixel-radio" type="radio" id="all-shops" name="shop" ${
  1380. isAllSelected ? 'checked' : ''
  1381. }><label for="all-shops">Toutes les boutiques</label></li>`;
  1382. shops.forEach(shop => {
  1383. const isSelected = currentShopValue === shop.slug;
  1384. html += `<li class="filter-list">
  1385.                 <input class="pixel-radio" type="radio" id="shop-${
  1386. shop.id
  1387. }" name="shop" value="${
  1388. shop.slug
  1389. }" ${
  1390. isSelected ? 'checked' : ''
  1391. }>
  1392.                 <label for="shop-${
  1393. shop.id
  1394. }">${
  1395. shop.name
  1396. }<span>(${
  1397. shop.productCount
  1398. })</span></label>
  1399.             </li>`;
  1400. });
  1401. shopList.innerHTML = html;
  1402. // Mettre à jour aussi le modal
  1403. const modalShopList = document.getElementById('modalShopList');
  1404. if (modalShopList) {
  1405. modalShopList.innerHTML = html.replace(/id="all-shops"/g, 'id="modal-all-shops"').replace(/id="shop-/g, 'id="modal-shop-').replace(/name="shop"/g, 'name="modal-shop"').replace(/for="all-shops"/g, 'for="modal-all-shops"').replace(/for="shop-/g, 'for="modal-shop-');
  1406. }
  1407. // Ajouter les event listeners (seulement pour la sidebar, le modal gère ses propres listeners)
  1408. shopList.querySelectorAll('input[name="shop"]').forEach(radio => {
  1409. radio.addEventListener('change', function () {
  1410. if (this.id === 'all-shops') {
  1411. currentFilters.shop = '';
  1412. } else {
  1413. currentFilters.shop = this.value;
  1414. } currentFilters.page = 1;
  1415. applyFilters();
  1416. });
  1417. });
  1418. }
  1419. // Fonction pour mettre à jour les attributs disponibles
  1420. function updateAvailableAttributes(attributes) {
  1421. // Toujours afficher tous les filtres d'attributs, même s'ils ne sont pas pertinents
  1422. // Couleurs
  1423. updateAttributeFilter('colors', attributes.colors, 'colorList', 'colorsFilter');
  1424. // Tailles
  1425. updateAttributeFilter('sizes', attributes.sizes, 'sizeList', 'sizesFilter');
  1426. // Matériaux
  1427. updateAttributeFilter('materials', attributes.materials, 'materialList', 'materialsFilter');
  1428. // Conditions
  1429. updateAttributeFilter('conditions', attributes.conditions, 'conditionList', 'conditionsFilter');
  1430. }
  1431. // Fonction générique pour mettre à jour les filtres d'attributs
  1432. function updateAttributeFilter(attributeType, attributes, listId, filterId) {
  1433. const filter = document.getElementById(filterId);
  1434. const list = document.getElementById(listId);
  1435. const currentValue = currentFilters[attributeType] || '';
  1436. // Toujours afficher les filtres d'attributs
  1437. filter.style.display = 'block';
  1438. // Si pas d'attributs disponibles, afficher un message ou garder les attributs existants
  1439. if (! attributes || attributes.length === 0) { // Ne pas réinitialiser si la liste existe déjà et a du contenu
  1440. if (list && list.children.length > 0) {
  1441. return; // Garder les attributs existants
  1442. }
  1443. // Sinon, afficher un message
  1444. list.innerHTML = '<li class="filter-list"><span class="text-muted">Aucun attribut disponible</span></li>';
  1445. return;
  1446. }
  1447. let html = '';
  1448. const isAllSelected = ! currentValue || currentValue === '';
  1449. html += `<li class="filter-list"><input class="pixel-radio" type="radio" id="all-${attributeType}" name="${attributeType}" ${
  1450. isAllSelected ? 'checked' : ''
  1451. }><label for="all-${attributeType}">Tous</label></li>`;
  1452. attributes.forEach(attr => {
  1453. const isSelected = currentValue === attr.value;
  1454. html += `<li class="filter-list">
  1455.                 <input class="pixel-radio" type="radio" id="${attributeType}-${
  1456. attr.value
  1457. }" name="${attributeType}" value="${
  1458. attr.value
  1459. }" ${
  1460. isSelected ? 'checked' : ''
  1461. }>
  1462.                 <label for="${attributeType}-${
  1463. attr.value
  1464. }">${
  1465. attr.value
  1466. }<span>(${
  1467. attr.count
  1468. })</span></label>
  1469.             </li>`;
  1470. });
  1471. list.innerHTML = html;
  1472. // Mettre à jour aussi le modal (si l'élément existe)
  1473. const modalListId = 'modal' + listId.charAt(0).toUpperCase() + listId.slice(1); // modalColorList, modalSizeList, etc.
  1474. const modalList = document.getElementById(modalListId);
  1475. if (modalList) { // Adapter le HTML pour le modal (changer les IDs et names)
  1476. let modalHtml = html.replace(new RegExp (`id="all-${attributeType}"`, 'g'), `id="modal-all-${attributeType}"`).replace(new RegExp (`id="${attributeType}-`, 'g'), `id="modal-${attributeType}-`).replace(new RegExp (`name="${attributeType}"`, 'g'), `name="modal-${attributeType}"`).replace(new RegExp (`for="all-${attributeType}"`, 'g'), `for="modal-all-${attributeType}"`).replace(new RegExp (`for="${attributeType}-`, 'g'), `for="modal-${attributeType}-`);
  1477. modalList.innerHTML = modalHtml;
  1478. }
  1479. // Ajouter les event listeners (seulement pour la sidebar, le modal gère ses propres listeners)
  1480. list.querySelectorAll (`input[name="${attributeType}"]`).forEach(radio => {
  1481. radio.addEventListener('change', function () {
  1482. if (this.id === `all-${attributeType}`) {
  1483. currentFilters[attributeType] = '';
  1484. } else {
  1485. currentFilters[attributeType] = this.value;
  1486. } currentFilters.page = 1;
  1487. applyFilters();
  1488. });
  1489. });
  1490. } else {
  1491. filter.style.display = 'none';
  1492. }
  1493. }
  1494. // Fonction pour mettre à jour l'URL
  1495. function updateURL () {
  1496. const url = new URL(window.location);
  1497. // Supprimer les anciens paramètres
  1498. const paramsToRemove = [
  1499. 'category',
  1500. 'brand',
  1501. 'sort',
  1502. 'price_min',
  1503. 'price_max',
  1504. 'page',
  1505. 'shop',
  1506. 'featured',
  1507. 'digital',
  1508. 'stock_status',
  1509. 'rating_min',
  1510. 'weight_min',
  1511. 'weight_max',
  1512. 'color',
  1513. 'size',
  1514. 'material',
  1515. 'condition',
  1516. 'availability'
  1517. ];
  1518. paramsToRemove.forEach(param => url.searchParams.delete(param));
  1519. // Ajouter les nouveaux paramètres
  1520. Object.keys(currentFilters).forEach(key => {
  1521. if (currentFilters[key]) {
  1522. url.searchParams.set(key, currentFilters[key]);
  1523. }
  1524. });
  1525. // Mettre à jour l'URL sans recharger la page
  1526. window.history.pushState({}, '', url);
  1527. }
  1528. // Fonction pour restaurer l'état des filtres statiques
  1529. function restoreStaticFiltersState (preservedFilters) { // Restaurer featured
  1530. if (preservedFilters.featured) {
  1531. const featuredInput = document.querySelector (`input[name="featured"][value="${
  1532. preservedFilters.featured
  1533. }"]`);
  1534. if (featuredInput) {
  1535. featuredInput.checked = true;
  1536. } else {
  1537. const featuredAll = document.getElementById('featured-all');
  1538. if (featuredAll) 
  1539. featuredAll.checked = true;
  1540. }
  1541. } else {
  1542. const featuredAll = document.getElementById('featured-all');
  1543. if (featuredAll) 
  1544. featuredAll.checked = true;
  1545. }
  1546. // Restaurer digital
  1547. if (preservedFilters.digital) {
  1548. const digitalInput = document.querySelector (`input[name="digital"][value="${
  1549. preservedFilters.digital
  1550. }"]`);
  1551. if (digitalInput) {
  1552. digitalInput.checked = true;
  1553. } else {
  1554. const digitalAll = document.getElementById('digital-all');
  1555. if (digitalAll) 
  1556. digitalAll.checked = true;
  1557. }
  1558. } else {
  1559. const digitalAll = document.getElementById('digital-all');
  1560. if (digitalAll) 
  1561. digitalAll.checked = true;
  1562. }
  1563. // Restaurer availability
  1564. if (preservedFilters.availability) {
  1565. const availabilityInput = document.querySelector (`input[name="availability"][value="${
  1566. preservedFilters.availability
  1567. }"]`);
  1568. if (availabilityInput) {
  1569. availabilityInput.checked = true;
  1570. } else {
  1571. const availabilityAll = document.getElementById('availability-all');
  1572. if (availabilityAll) 
  1573. availabilityAll.checked = true;
  1574. }
  1575. } else {
  1576. const availabilityAll = document.getElementById('availability-all');
  1577. if (availabilityAll) 
  1578. availabilityAll.checked = true;
  1579. }
  1580. // Restaurer rating (mapping vers ratingMin)
  1581. if (preservedFilters.rating) {
  1582. const ratingInput = document.querySelector (`input[name="rating"][value="${
  1583. preservedFilters.rating
  1584. }"]`);
  1585. if (ratingInput) {
  1586. ratingInput.checked = true;
  1587. } else {
  1588. const ratingAll = document.getElementById('rating-all');
  1589. if (ratingAll) 
  1590. ratingAll.checked = true;
  1591. }
  1592. } else {
  1593. const ratingAll = document.getElementById('rating-all');
  1594. if (ratingAll) 
  1595. ratingAll.checked = true;
  1596. }
  1597. }
  1598. // Fonction pour initialiser tous les filtres avec les valeurs de l'URL
  1599. function initializeFiltersFromURL () { // Initialiser les filtres statiques
  1600. const staticFilters = {
  1601. featured: currentFilters.featured || '',
  1602. digital: currentFilters.digital || '',
  1603. availability: currentFilters.availability || '',
  1604. rating: currentFilters.rating || currentFilters.ratingMin || ''
  1605. };
  1606. // Cocher les filtres statiques
  1607. Object.keys(staticFilters).forEach(filterName => {
  1608. const value = staticFilters[filterName];
  1609. if (value) {
  1610. const input = document.querySelector(`input[name="${filterName}"][value="${value}"]`);
  1611. if (input) {
  1612. input.checked = true;
  1613. } else { // Cocher "all" si la valeur n'existe pas
  1614. const allInput = document.getElementById (`${filterName}-all`);
  1615. if (allInput) 
  1616. allInput.checked = true;
  1617. }
  1618. } else {
  1619. const allInput = document.getElementById (`${filterName}-all`);
  1620. if (allInput) 
  1621. allInput.checked = true;
  1622. }
  1623. });
  1624. // Initialiser les filtres de poids
  1625. if (currentFilters.weightMin) {
  1626. const weightMinInput = document.getElementById('weightMin');
  1627. if (weightMinInput) 
  1628. weightMinInput.value = currentFilters.weightMin;
  1629. }
  1630. if (currentFilters.weightMax) {
  1631. const weightMaxInput = document.getElementById('weightMax');
  1632. if (weightMaxInput) 
  1633. weightMaxInput.value = currentFilters.weightMax;
  1634. }
  1635. // Initialiser les filtres de marque (si déjà chargés)
  1636. if (currentFilters.brand) {
  1637. const brandInput = document.querySelector (`input[name="brand"][value="${
  1638. currentFilters.brand
  1639. }"]`);
  1640. if (brandInput) {
  1641. brandInput.checked = true;
  1642. } else {
  1643. const allBrands = document.getElementById('all-brands');
  1644. if (allBrands) 
  1645. allBrands.checked = true;
  1646. }
  1647. }
  1648. // Initialiser les filtres de boutique (si déjà chargés)
  1649. if (currentFilters.shop) {
  1650. const shopInput = document.querySelector (`input[name="shop"][value="${
  1651. currentFilters.shop
  1652. }"]`);
  1653. if (shopInput) {
  1654. shopInput.checked = true;
  1655. } else {
  1656. const allShops = document.getElementById('all-shops');
  1657. if (allShops) 
  1658. allShops.checked = true;
  1659. }
  1660. }
  1661. // Initialiser les filtres d'attributs (si déjà chargés)
  1662. ['color', 'size', 'material', 'condition'].forEach(attrType => {
  1663. if (currentFilters[attrType]) {
  1664. const attrInput = document.querySelector(`input[name="${attrType}"][value="${
  1665. currentFilters[attrType]
  1666. }"]`);
  1667. if (attrInput) {
  1668. attrInput.checked = true;
  1669. } else {
  1670. const allAttr = document.getElementById (`all-${attrType}`);
  1671. if (allAttr) 
  1672. allAttr.checked = true;
  1673. }
  1674. }
  1675. });
  1676. }
  1677. // Initialiser les filtres
  1678. document.addEventListener('DOMContentLoaded', function () { // Initialiser les filtres depuis l'URL
  1679. initializeFiltersFromURL();
  1680. // Event listeners pour les filtres statiques
  1681. const staticFilters = ['featured', 'digital', 'availability'];
  1682. staticFilters.forEach(filterName => {
  1683. const inputs = document.querySelectorAll (`input[name="${filterName}"]`);
  1684. inputs.forEach(input => {
  1685. input.addEventListener('change', function () {
  1686. if (this.id.includes('-all')) {
  1687. currentFilters[filterName] = '';
  1688. } else {
  1689. currentFilters[filterName] = this.value;
  1690. }
  1691. currentFilters.page = 1;
  1692. applyFilters();
  1693. });
  1694. });
  1695. });
  1696. // Event listener spécial pour rating (mapping vers ratingMin)
  1697. const ratingInputs = document.querySelectorAll('input[name="rating"]');
  1698. ratingInputs.forEach(input => {
  1699. input.addEventListener('change', function () {
  1700. if (this.id === 'rating-all') {
  1701. currentFilters.ratingMin = '';
  1702. currentFilters.rating = '';
  1703. } else {
  1704. currentFilters.ratingMin = this.value;
  1705. currentFilters.rating = this.value;
  1706. }
  1707. currentFilters.page = 1;
  1708. applyFilters();
  1709. });
  1710. });
  1711. // Event listeners pour les filtres de poids
  1712. const weightInputs = ['weightMin', 'weightMax'];
  1713. weightInputs.forEach(inputId => {
  1714. const input = document.getElementById(inputId);
  1715. if (input) {
  1716. input.addEventListener('change', applyWeightFilter);
  1717. input.addEventListener('input', debounce(applyWeightFilter, 500));
  1718. }
  1719. });
  1720. // Event listeners pour les filtres de prix
  1721. const priceInputs = ['priceMin', 'priceMax'];
  1722. priceInputs.forEach(inputId => {
  1723. const input = document.getElementById(inputId);
  1724. if (input) {
  1725. input.addEventListener('change', applyPriceFilter);
  1726. input.addEventListener('input', debounce(applyPriceFilter, 500));
  1727. }
  1728. });
  1729. // Ajouter les event listeners pour les marques existantes
  1730. const brandList = document.getElementById('brandList');
  1731. if (brandList) {
  1732. brandList.querySelectorAll('input[name="brand"]').forEach(radio => {
  1733. radio.addEventListener('change', function () {
  1734. if (this.id === 'all-brands') {
  1735. currentFilters.brand = '';
  1736. } else {
  1737. currentFilters.brand = this.value;
  1738. }
  1739. currentFilters.page = 1;
  1740. applyFilters();
  1741. });
  1742. });
  1743. }
  1744. // Appliquer les filtres initiaux si une catégorie est sélectionnée
  1745. if (currentFilters.category) {
  1746. applyFilters();
  1747. } else { // Initialiser l'affichage du bouton au chargement de la page
  1748. updateLoadMoreButton();
  1749. }
  1750. });
  1751. // Fonction de debounce pour éviter trop de requêtes
  1752. function debounce (func, wait) {
  1753. let timeout;
  1754. return function executedFunction(...args) {
  1755. const later = () => {
  1756. clearTimeout(timeout);
  1757. func(...args);
  1758. };
  1759. clearTimeout(timeout);
  1760. timeout = setTimeout(later, wait);
  1761. };
  1762. }
  1763. // Les fonctions toggleComparison, toggleWishlist, et showNotification sont définies globalement dans base_home.html.twig
  1764. // Pas besoin de les redéfinir ici
  1765. // Fonctions pour le dropshipping
  1766. function generateDropshipLink (productId) {
  1767. showEbayPromptModal('URL originale (optionnel):', '').then(originalUrl => {
  1768. if (originalUrl === null) 
  1769. return;
  1770.  // Utilisateur a annulé
  1771. const formData = new FormData();
  1772. formData.append('originalUrl', originalUrl || '');
  1773. fetch (`/api/dropship/generate/${productId}`, {
  1774. method: 'POST',
  1775. body: formData
  1776. }).then(response => response.json()).then(data => {
  1777. if (data.success) { // Afficher le lien généré dans une modal
  1778. showDropshipLinkModal(data.data);
  1779. } else {
  1780. showNotification(data.message || 'Erreur lors de la génération du lien', 'error');
  1781. }
  1782. }).catch(error => {
  1783. console.error('Erreur:', error);
  1784. showNotification('Erreur lors de la génération du lien', 'error');
  1785. });
  1786. });
  1787. }
  1788. function showDropshipLinkModal (linkData) {
  1789. const modal = document.createElement('div');
  1790. modal.className = 'modal fade show';
  1791. modal.style.display = 'block';
  1792. modal.innerHTML = `
  1793.         <div class="modal-dialog">
  1794.             <div class="modal-content">
  1795.                 <div class="modal-header">
  1796.                     <h5 class="modal-title">
  1797.                         <i class="fa fa-share-alt"></i> Lien de dropshipping généré
  1798.                     </h5>
  1799.                     <button type="button" class="btn-close" onclick="closeModal(this)"></button>
  1800.                 </div>
  1801.                 <div class="modal-body">
  1802.                     <div class="mb-3">
  1803.                         <label class="form-label">Lien de dropshipping :</label>
  1804.                         <div class="input-group">
  1805.                             <input type="text" class="form-control" value="${
  1806. linkData.dropshipUrl
  1807. }" readonly id="dropshipUrl">
  1808.                             <button class="btn btn-outline-secondary" onclick="copyToClipboard('${
  1809. linkData.dropshipUrl
  1810. }')">
  1811.                                 <i class="fa fa-copy"></i>
  1812.                             </button>
  1813.                         </div>
  1814.                     </div>
  1815.                     <div class="row">
  1816.                         <div class="col-md-6">
  1817.                             <div class="card">
  1818.                                 <div class="card-body text-center">
  1819.                                     <h6 class="card-title">Taux de commission</h6>
  1820.                                     <h4 class="text-primary">${
  1821. linkData.commissionRate
  1822. }%</h4>
  1823.                                 </div>
  1824.                             </div>
  1825.                         </div>
  1826.                         <div class="col-md-6">
  1827.                             <div class="card">
  1828.                                 <div class="card-body text-center">
  1829.                                     <h6 class="card-title">Commission par vente</h6>
  1830.                                     <h4 class="text-success">${
  1831. linkData.commissionAmount
  1832. } HTG</h4>
  1833.                                 </div>
  1834.                             </div>
  1835.                         </div>
  1836.                     </div>
  1837.                     <div class="alert alert-info">
  1838.                         <i class="fa fa-info-circle"></i>
  1839.                         <strong>Note :</strong> Ce lien expire le ${
  1840. new Date(linkData.expiresAt).toLocaleDateString('fr-FR')
  1841. }.
  1842.                     </div>
  1843.                 </div>
  1844.                 <div class="modal-footer">
  1845.                     <button type="button" class="btn btn-secondary" onclick="closeModal(this)">Fermer</button>
  1846.                     <a href="/dropship/dashboard" class="btn btn-primary">
  1847.                         <i class="fa fa-tachometer"></i> Voir le dashboard
  1848.                     </a>
  1849.                 </div>
  1850.             </div>
  1851.         </div>
  1852.     `;
  1853. document.body.appendChild(modal);
  1854. // Ajouter le backdrop
  1855. const backdrop = document.createElement('div');
  1856. backdrop.className = 'modal-backdrop fade show';
  1857. document.body.appendChild(backdrop);
  1858. }
  1859. function closeModal (button) {
  1860. const modal = button.closest('.modal');
  1861. const backdrop = document.querySelector('.modal-backdrop');
  1862. if (modal) {
  1863. modal.remove();
  1864. }
  1865. if (backdrop) {
  1866. backdrop.remove();
  1867. }
  1868. }
  1869. function copyToClipboard (text) {
  1870. navigator.clipboard.writeText(text).then(function () {
  1871. showNotification('Lien copié dans le presse-papiers !', 'success');
  1872. }, function (err) {
  1873. console.error('Erreur lors de la copie:', err);
  1874. showNotification('Erreur lors de la copie du lien', 'error');
  1875. });
  1876. }
  1877. // Fonction pour initialiser le chargement infini
  1878. function initInfiniteScroll () {
  1879. const loader = document.getElementById('infiniteScrollLoader');
  1880. const noMoreProducts = document.getElementById('noMoreProducts');
  1881. // Afficher le loader si il y a plus de produits
  1882. if (hasMoreProducts && loader) {
  1883. loader.style.display = 'block';
  1884. } else if (!hasMoreProducts && noMoreProducts) {
  1885. noMoreProducts.style.display = 'block';
  1886. }
  1887. // Observer pour détecter quand l'utilisateur arrive en bas
  1888. const observer = new IntersectionObserver((entries) => {
  1889. entries.forEach(entry => {
  1890. if (entry.isIntersecting && !isLoading && hasMoreProducts) {
  1891. loadMoreProducts();
  1892. }
  1893. });
  1894. }, {
  1895. root: null,
  1896. rootMargin: '200px', // Démarrer le chargement 200px avant d'arriver en bas
  1897. threshold: 0.1
  1898. });
  1899. // Observer le loader
  1900. if (loader) {
  1901. observer.observe(loader);
  1902. }
  1903. }
  1904. // Fonction pour mettre à jour l'affichage du bouton
  1905. function updateLoadMoreButton () {
  1906. const loadMoreContainer = document.getElementById('loadMoreContainer');
  1907. const noMoreProducts = document.getElementById('noMoreProducts');
  1908. if (hasMoreProducts) {
  1909. if (loadMoreContainer) {
  1910. loadMoreContainer.style.display = 'block';
  1911. }
  1912. if (noMoreProducts) {
  1913. noMoreProducts.style.display = 'none';
  1914. }
  1915. } else {
  1916. if (loadMoreContainer) {
  1917. loadMoreContainer.style.display = 'none';
  1918. }
  1919. if (noMoreProducts) {
  1920. noMoreProducts.style.display = 'block';
  1921. }
  1922. }
  1923. }
  1924. // Fonction pour initialiser les images des nouveaux produits
  1925. function initializeProductImages (container) {
  1926. const productCards = container.querySelectorAll('.single-product');
  1927. productCards.forEach(card => {
  1928. const productId = card.querySelector('.add-to-cart') ?. getAttribute('data-product-id');
  1929. if (productId) { // Récupérer les images depuis les attributs data ou depuis le DOM
  1930. const imgElement = card.querySelector('.main-product-img');
  1931. if (imgElement && imgElement.dataset.images) {
  1932. const images = JSON.parse(imgElement.dataset.images);
  1933. productImages[productId] = images;
  1934. currentImageIndex[productId] = 0;
  1935. }
  1936. }
  1937. });
  1938. }
  1939. // Fonction pour initialiser les event listeners des nouveaux produits
  1940. function initializeProductEventListeners (container) { // Boutons d'ajout au panier
  1941. const cartButtons = container.querySelectorAll('.add-to-cart');
  1942. cartButtons.forEach(button => {
  1943. button.addEventListener('click', function () {
  1944. const productId = this.getAttribute('data-product-id');
  1945. const qty = this.getAttribute('data-qty');
  1946. fetch('{{ path("ui_cart_add") }}', {
  1947. method: 'POST',
  1948. headers: {
  1949. 'Content-Type': 'application/x-www-form-urlencoded'
  1950. },
  1951. body: 'productId=' + productId + '&qty=' + qty
  1952. }).then(response => response.json()).then(data => {
  1953. if (data.ok) {
  1954. showEbayModal('Produit ajouté au panier !', 'success');
  1955. const cartBadge = document.querySelector('.cart-badge');
  1956. if (cartBadge) {
  1957. cartBadge.textContent = data.totalQty;
  1958. }
  1959. } else {
  1960. showEbayModal(data.message || 'Erreur lors de l\'ajout au panier', 'error');
  1961. }
  1962. }).catch(error => {
  1963. console.error('Erreur:', error);
  1964. showEbayModal('Erreur lors de l\'ajout au panier', 'error');
  1965. });
  1966. });
  1967. });
  1968. }
  1969. // La fonction showEbayModal est déjà définie plus haut
  1970. // Modal style eBay pour remplacer prompt()
  1971. function showEbayPromptModal (message, defaultValue = '') {
  1972. return new Promise((resolve) => {
  1973. const modalId = 'ebayPromptModal';
  1974. let existingModal = document.getElementById(modalId);
  1975. if (existingModal) {
  1976. existingModal.remove();
  1977. }
  1978. const modal = document.createElement('div');
  1979. modal.id = modalId;
  1980. modal.className = 'modal fade';
  1981. modal.setAttribute('tabindex', '-1');
  1982. modal.setAttribute('aria-labelledby', 'ebayPromptModalLabel');
  1983. modal.setAttribute('aria-hidden', 'true');
  1984. modal.innerHTML = `
  1985. <div class="modal-dialog modal-dialog-centered">
  1986. <div class="modal-content" style="border-radius: 8px; border: none; box-shadow: 0 4px 20px rgba(0,0,0,0.15);">
  1987. <div class="modal-header" style="border-bottom: 1px solid #e0e0e0; padding: 20px 24px;">
  1988. <h5 class="modal-title" id="ebayPromptModalLabel" style="font-weight: 600; font-size: 18px; color: #333;">
  1989. <i class="ti-pencil-alt" style="color: #0064D2; margin-right: 8px;"></i>Saisie
  1990. </h5>
  1991. <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" style="margin: 0;"></button>
  1992. </div>
  1993. <div class="modal-body" style="padding: 24px;">
  1994. <p style="margin-bottom: 16px; font-size: 16px; color: #333;">${message}</p>
  1995. <input type="text" class="form-control" id="ebayPromptInput" value="${defaultValue}" style="border-radius: 4px; border: 1px solid #ccc; padding: 10px; font-size: 14px;" autofocus>
  1996. </div>
  1997. <div class="modal-footer" style="border-top: 1px solid #e0e0e0; padding: 16px 24px; justify-content: flex-end;">
  1998. <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" style="min-width: 80px; border-radius: 4px; font-weight: 500; margin-right: 8px;">
  1999. Annuler
  2000. </button>
  2001. <button type="button" class="btn btn-primary" id="ebayPromptConfirm" style="min-width: 80px; background-color: #0064D2; border-color: #0064D2; border-radius: 4px; font-weight: 500;">
  2002. OK
  2003. </button>
  2004. </div>
  2005. </div>
  2006. </div>
  2007. `;
  2008. document.body.appendChild(modal);
  2009. const bsModal = new bootstrap.Modal(modal);
  2010. const input = modal.querySelector('#ebayPromptInput');
  2011. const confirmBtn = modal.querySelector('#ebayPromptConfirm');
  2012. // Gérer la confirmation
  2013. confirmBtn.addEventListener('click', function () {
  2014. const value = input.value;
  2015. bsModal.hide();
  2016. resolve(value);
  2017. });
  2018. // Gérer l'annulation
  2019. modal.addEventListener('hidden.bs.modal', function () {
  2020. if (input.value === undefined || input.value === '') {
  2021. resolve(null);
  2022. }
  2023. modal.remove();
  2024. });
  2025. // Gérer la touche Entrée
  2026. input.addEventListener('keypress', function (e) {
  2027. if (e.key === 'Enter') {
  2028. confirmBtn.click();
  2029. }
  2030. });
  2031. bsModal.show();
  2032. input.focus();
  2033. input.select();
  2034. });
  2035. }
  2036. // Remplacer window.alert et window.prompt
  2037. window.alert = showEbayModal;
  2038. window.prompt = showEbayPromptModal;
  2039. </script>
  2040. <script>
  2041.     // Gestion du toggle de la sidebar sur mobile
  2042. document.addEventListener('DOMContentLoaded', function () {
  2043. const sidebarToggle = document.querySelector('.listing-sidebar-toggle');
  2044. const sidebarCollapse = document.getElementById('listingSidebarCollapse');
  2045. if (sidebarToggle && sidebarCollapse) { // Bootstrap 5 utilise des événements natifs
  2046. sidebarCollapse.addEventListener('show.bs.collapse', function () {
  2047. sidebarToggle.classList.remove('collapsed');
  2048. sidebarToggle.setAttribute('aria-expanded', 'true');
  2049. });
  2050. sidebarCollapse.addEventListener('hide.bs.collapse', function () {
  2051. sidebarToggle.classList.add('collapsed');
  2052. sidebarToggle.setAttribute('aria-expanded', 'false');
  2053. });
  2054. sidebarCollapse.addEventListener('shown.bs.collapse', function () {
  2055. sidebarToggle.classList.remove('collapsed');
  2056. });
  2057. sidebarCollapse.addEventListener('hidden.bs.collapse', function () {
  2058. sidebarToggle.classList.add('collapsed');
  2059. });
  2060. // Vérifier l'état initial
  2061. if (sidebarCollapse.classList.contains('show')) {
  2062. sidebarToggle.classList.remove('collapsed');
  2063. sidebarToggle.setAttribute('aria-expanded', 'true');
  2064. } else {
  2065. sidebarToggle.classList.add('collapsed');
  2066. sidebarToggle.setAttribute('aria-expanded', 'false');
  2067. }
  2068. }
  2069. });
  2070. // Synchroniser la recherche avec la barre de recherche du header
  2071. const headerSearchInput = document.getElementById('search_input');
  2072. if (headerSearchInput && typeof currentFilters !== 'undefined') { // Mettre à jour la valeur de la barre de recherche du header
  2073. headerSearchInput.value = currentFilters.q || '';
  2074. // Afficher le bouton clear si nécessaire
  2075. const clearSearchBtn = document.getElementById('clear_search_btn');
  2076. if (clearSearchBtn) {
  2077. clearSearchBtn.style.display = currentFilters.q ? 'block' : 'none';
  2078. }
  2079. }
  2080. // Réinitialiser tous les filtres
  2081. const resetFiltersBtn = document.getElementById('resetFiltersBtn');
  2082. if (resetFiltersBtn) {
  2083. resetFiltersBtn.addEventListener('click', function () {
  2084. resetAllFilters();
  2085. });
  2086. }
  2087. // Fonction pour réinitialiser tous les filtres
  2088. function resetAllFilters () {
  2089. currentFilters = {
  2090. category: '',
  2091. brand: '',
  2092. sort: 'newest',
  2093. priceMin: '',
  2094. priceMax: '',
  2095. page: 1,
  2096. q: '',
  2097. shop: '',
  2098. featured: '',
  2099. digital: '',
  2100. stockStatus: '',
  2101. ratingMin: '',
  2102. rating: '',
  2103. weightMin: '',
  2104. weightMax: '',
  2105. color: '',
  2106. size: '',
  2107. material: '',
  2108. condition: '',
  2109. availability: ''
  2110. };
  2111. // Réinitialiser la barre de recherche du header
  2112. const headerSearchInput = document.getElementById('search_input');
  2113. if (headerSearchInput) {
  2114. headerSearchInput.value = '';
  2115. }
  2116. const clearSearchBtn = document.getElementById('clear_search_btn');
  2117. if (clearSearchBtn) {
  2118. clearSearchBtn.style.display = 'none';
  2119. }
  2120. // Réinitialiser les inputs
  2121. document.getElementById('sortSelect').value = 'newest';
  2122. // Réinitialiser les radios
  2123. document.querySelectorAll('input[type="radio"]').forEach(radio => {
  2124. if (radio.id.includes('all-') || radio.id.includes('featured-all') || radio.id.includes('digital-all') || radio.id.includes('availability-all') || radio.id.includes('rating-all')) {
  2125. radio.checked = true;
  2126. } else {
  2127. radio.checked = false;
  2128. }
  2129. });
  2130. // Réinitialiser les inputs de prix et poids
  2131. const weightMinInput = document.getElementById('weightMin');
  2132. const weightMaxInput = document.getElementById('weightMax');
  2133. if (weightMinInput) 
  2134. weightMinInput.value = '';
  2135. if (weightMaxInput) 
  2136. weightMaxInput.value = '';
  2137. applyFilters();
  2138. updateActiveFilters();
  2139. }
  2140. // Fonction pour mettre à jour l'affichage des filtres actifs
  2141. function updateActiveFilters () {
  2142. const activeFiltersDiv = document.getElementById('activeFilters');
  2143. const activeFiltersList = document.getElementById('activeFiltersList');
  2144. const filters = [];
  2145. if (currentFilters.q) {
  2146. filters.push (`<span class="badge bg-primary">Recherche: "${
  2147. currentFilters.q
  2148. }"</span>`);
  2149. }
  2150. if (currentFilters.category) {
  2151. filters.push(`<span class="badge bg-secondary">Catégorie</span>`);
  2152. }
  2153. if (currentFilters.brand) {
  2154. filters.push(`<span class="badge bg-secondary">Marque</span>`);
  2155. }
  2156. if (currentFilters.priceMin || currentFilters.priceMax) {
  2157. filters.push(`<span class="badge bg-info">Prix</span>`);
  2158. }
  2159. if (currentFilters.featured === 'true') {
  2160. filters.push(`<span class="badge bg-warning">Produits vedettes</span>`);
  2161. }
  2162. if (currentFilters.digital === 'true') {
  2163. filters.push(`<span class="badge bg-success">Numériques</span>`);
  2164. }
  2165. if (currentFilters.digital === 'false') {
  2166. filters.push(`<span class="badge bg-success">Physiques</span>`);
  2167. }
  2168. if (currentFilters.availability) {
  2169. filters.push(`<span class="badge bg-info">Disponibilité</span>`);
  2170. }
  2171. if (currentFilters.ratingMin || currentFilters.rating) {
  2172. const ratingValue = currentFilters.ratingMin || currentFilters.rating;
  2173. filters.push (`<span class="badge bg-warning">Note ≥ ${ratingValue}</span>`);
  2174. }
  2175. if (currentFilters.weightMin || currentFilters.weightMax) {
  2176. filters.push(`<span class="badge bg-secondary">Poids</span>`);
  2177. }
  2178. if (filters.length > 0) {
  2179. activeFiltersList.innerHTML = filters.join(' ');
  2180. activeFiltersDiv.style.display = 'block';
  2181. } else {
  2182. activeFiltersDiv.style.display = 'none';
  2183. }
  2184. }
  2185. // Mettre à jour les filtres actifs au chargement et après chaque filtrage
  2186. document.addEventListener('DOMContentLoaded', function () {
  2187. updateActiveFilters();
  2188. });
  2189. // Mettre à jour les filtres actifs après chaque application de filtres
  2190. const originalApplyFilters = applyFilters;
  2191. applyFilters = function () {
  2192. originalApplyFilters();
  2193. setTimeout(updateActiveFilters, 100);
  2194. // Mettre à jour le compteur de filtres actifs
  2195. updateActiveFiltersCount();
  2196. };
  2197. // S'assurer que applyFilters est accessible globalement
  2198. window.applyFilters = applyFilters;
  2199. // Fonction pour mettre à jour le compteur de filtres actifs
  2200. function updateActiveFiltersCount () {
  2201. const count = Object.keys(currentFilters).filter(key => {
  2202. const value = currentFilters[key];
  2203. return value && value !== '' && key !== 'page' && key !== 'sort';
  2204. }).length;
  2205. const badge = document.getElementById('activeFiltersCount');
  2206. if (badge) {
  2207. if (count > 0) {
  2208. badge.textContent = count;
  2209. badge.style.display = 'inline-block';
  2210. } else {
  2211. badge.style.display = 'none';
  2212. }
  2213. }
  2214. }
  2215. // Initialiser le compteur au chargement
  2216. document.addEventListener('DOMContentLoaded', function () {
  2217. updateActiveFiltersCount();
  2218. });
  2219. </script>
  2220. <!-- Modal de filtres -->
  2221. <div class="modal fade" id="filtersModal" tabindex="-1" aria-labelledby="filtersModalLabel" aria-hidden="true">
  2222.     <div class="modal-dialog modal-lg">
  2223.         <div class="modal-content" style="display: flex; flex-direction: column; max-height: 90vh;">
  2224.             <div class="modal-header" style="flex-shrink: 0;">
  2225.                 <h5 class="modal-title" id="filtersModalLabel">
  2226.                     <i class="lnr lnr-filter me-2"></i>Filtres de recherche
  2227.                 </h5>
  2228.                 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
  2229.             </div>
  2230.             <div class="modal-body" style="overflow-y: auto; flex: 1; min-height: 0;">
  2231.                 <div
  2232.                     class="row">
  2233.                     <!-- Colonne gauche -->
  2234.                     <div
  2235.                         class="col-md-6">
  2236.                         <!-- Marques -->
  2237.                         <div class="common-filter mb-4">
  2238.                             <div class="head">Marques</div>
  2239.                             <ul class="brand-list" id="modalBrandList">
  2240.                                 <li class="filter-list">
  2241.                                     <input class="pixel-radio" type="radio" id="modal-all-brands" name="modal-brand" checked>
  2242.                                     <label for="modal-all-brands">Toutes les marques</label>
  2243.                                 </li>
  2244.                                 {% for brand in brands %}
  2245.                                     <li class="filter-list">
  2246.                                         <input class="pixel-radio" type="radio" id="modal-brand-{{ brand.id }}" name="modal-brand" value="{{ brand.slug }}">
  2247.                                         <label for="modal-brand-{{ brand.id }}">{{ brand.name }}<span>({{ brand.getActiveProductsCount() }})</span>
  2248.                                         </label>
  2249.                                     </li>
  2250.                                 {% endfor %}
  2251.                             </ul>
  2252.                         </div>
  2253.                         <!-- Conditions -->
  2254.                         <div class="common-filter mb-4">
  2255.                             <div class="head">Condition</div>
  2256.                             <ul class="condition-list" id="modalConditionList">
  2257.                                 <li class="filter-list">
  2258.                                     <input class="pixel-radio" type="radio" id="modal-all-conditions" name="modal-condition" checked>
  2259.                                     <label for="modal-all-conditions">Toutes les conditions</label>
  2260.                                 </li>
  2261.                                 {% for condition in conditions %}
  2262.                                     <li class="filter-list">
  2263.                                         <input class="pixel-radio" type="radio" id="modal-condition-{{ condition.id }}" name="modal-condition" value="{{ condition.slug }}">
  2264.                                         <label for="modal-condition-{{ condition.id }}">{{ condition.name }}<span>({{ condition.getActiveProductsCount() }})</span>
  2265.                                         </label>
  2266.                                     </li>
  2267.                                 {% endfor %}
  2268.                             </ul>
  2269.                         </div>
  2270.                         <!-- Boutiques -->
  2271.                         <div class="common-filter mb-4">
  2272.                             <div class="head">Boutiques</div>
  2273.                             <div id="modalShopsFilter">
  2274.                                 <ul
  2275.                                     class="shop-list" id="modalShopList"><!-- Les boutiques seront chargées dynamiquement -->
  2276.                                 </ul>
  2277.                             </div>
  2278.                         </div>
  2279.                         <!-- Produits vedettes -->
  2280.                         <div class="common-filter mb-4">
  2281.                             <div class="head">Produits vedettes</div>
  2282.                             <ul>
  2283.                                 <li class="filter-list">
  2284.                                     <input class="pixel-radio" type="radio" id="modal-featured-all" name="modal-featured" checked>
  2285.                                     <label for="modal-featured-all">Tous les produits</label>
  2286.                                 </li>
  2287.                                 <li class="filter-list">
  2288.                                     <input class="pixel-radio" type="radio" id="modal-featured-only" name="modal-featured" value="true">
  2289.                                     <label for="modal-featured-only">Produits vedettes uniquement</label>
  2290.                                 </li>
  2291.                             </ul>
  2292.                         </div>
  2293.                         <!-- Type de produit -->
  2294.                         <div class="common-filter mb-4">
  2295.                             <div class="head">Type de produit</div>
  2296.                             <ul>
  2297.                                 <li class="filter-list">
  2298.                                     <input class="pixel-radio" type="radio" id="modal-digital-all" name="modal-digital" checked>
  2299.                                     <label for="modal-digital-all">Tous les types</label>
  2300.                                 </li>
  2301.                                 <li class="filter-list">
  2302.                                     <input class="pixel-radio" type="radio" id="modal-digital-physical" name="modal-digital" value="false">
  2303.                                     <label for="modal-digital-physical">Produits physiques</label>
  2304.                                 </li>
  2305.                                 <li class="filter-list">
  2306.                                     <input class="pixel-radio" type="radio" id="modal-digital-digital" name="modal-digital" value="true">
  2307.                                     <label for="modal-digital-digital">Produits numériques</label>
  2308.                                 </li>
  2309.                             </ul>
  2310.                         </div>
  2311.                     </div>
  2312.                     <!-- Colonne droite -->
  2313.                     <div
  2314.                         class="col-md-6">
  2315.                         <!-- Disponibilité -->
  2316.                         <div class="common-filter mb-4">
  2317.                             <div class="head">Disponibilité</div>
  2318.                             <ul>
  2319.                                 <li class="filter-list">
  2320.                                     <input class="pixel-radio" type="radio" id="modal-availability-all" name="modal-availability" checked>
  2321.                                     <label for="modal-availability-all">Tous</label>
  2322.                                 </li>
  2323.                                 <li class="filter-list">
  2324.                                     <input class="pixel-radio" type="radio" id="modal-availability-in-stock" name="modal-availability" value="in_stock">
  2325.                                     <label for="modal-availability-in-stock">En stock</label>
  2326.                                 </li>
  2327.                                 <li class="filter-list">
  2328.                                     <input class="pixel-radio" type="radio" id="modal-availability-low-stock" name="modal-availability" value="low_stock">
  2329.                                     <label for="modal-availability-low-stock">Stock faible</label>
  2330.                                 </li>
  2331.                                 <li class="filter-list">
  2332.                                     <input class="pixel-radio" type="radio" id="modal-availability-out-of-stock" name="modal-availability" value="out_of_stock">
  2333.                                     <label for="modal-availability-out-of-stock">Rupture de stock</label>
  2334.                                 </li>
  2335.                             </ul>
  2336.                         </div>
  2337.                         <!-- Note minimale -->
  2338.                         <div class="common-filter mb-4">
  2339.                             <div class="head">Note minimale</div>
  2340.                             <ul>
  2341.                                 <li class="filter-list">
  2342.                                     <input class="pixel-radio" type="radio" id="modal-rating-all" name="modal-rating" checked>
  2343.                                     <label for="modal-rating-all">Toutes les notes</label>
  2344.                                 </li>
  2345.                                 <li class="filter-list">
  2346.                                     <input class="pixel-radio" type="radio" id="modal-rating-4" name="modal-rating" value="4">
  2347.                                     <label for="modal-rating-4">4 étoiles et plus</label>
  2348.                                 </li>
  2349.                                 <li class="filter-list">
  2350.                                     <input class="pixel-radio" type="radio" id="modal-rating-3" name="modal-rating" value="3">
  2351.                                     <label for="modal-rating-3">3 étoiles et plus</label>
  2352.                                 </li>
  2353.                                 <li class="filter-list">
  2354.                                     <input class="pixel-radio" type="radio" id="modal-rating-2" name="modal-rating" value="2">
  2355.                                     <label for="modal-rating-2">2 étoiles et plus</label>
  2356.                                 </li>
  2357.                             </ul>
  2358.                         </div>
  2359.                         <!-- Prix -->
  2360.                         <div class="common-filter mb-4">
  2361.                             <div class="head">Prix (HTG)</div>
  2362.                             <div class="price-range-area">
  2363.                                 <div class="d-flex gap-3">
  2364.                                     <div class="flex-grow-1">
  2365.                                         <label class="form-label">Min</label>
  2366.                                         <input type="number" class="form-control" id="modal-priceMin" placeholder="0" min="0" step="0.01">
  2367.                                     </div>
  2368.                                     <div class="flex-grow-1">
  2369.                                         <label class="form-label">Max</label>
  2370.                                         <input type="number" class="form-control" id="modal-priceMax" placeholder="999999" min="0" step="0.01">
  2371.                                     </div>
  2372.                                 </div>
  2373.                             </div>
  2374.                         </div>
  2375.                         <!-- Poids -->
  2376.                         <div class="common-filter mb-4">
  2377.                             <div class="head">Poids (kg)</div>
  2378.                             <div class="weight-range-area">
  2379.                                 <div class="d-flex gap-3">
  2380.                                     <div class="flex-grow-1">
  2381.                                         <label class="form-label">Min</label>
  2382.                                         <input type="number" class="form-control" id="modal-weightMin" placeholder="0" step="0.1" min="0">
  2383.                                     </div>
  2384.                                     <div class="flex-grow-1">
  2385.                                         <label class="form-label">Max</label>
  2386.                                         <input type="number" class="form-control" id="modal-weightMax" placeholder="100" step="0.1" min="0">
  2387.                                     </div>
  2388.                                 </div>
  2389.                             </div>
  2390.                         </div>
  2391.                         <!-- Couleur -->
  2392.                         <div class="common-filter mb-4">
  2393.                             <div class="head">Couleur</div>
  2394.                             <div id="modalColorsFilter">
  2395.                                 <ul
  2396.                                     class="color-list" id="modalColorList"><!-- Les couleurs seront chargées dynamiquement -->
  2397.                                 </ul>
  2398.                             </div>
  2399.                         </div>
  2400.                         <!-- Taille -->
  2401.                         <div class="common-filter mb-4">
  2402.                             <div class="head">Taille</div>
  2403.                             <div id="modalSizesFilter">
  2404.                                 <ul
  2405.                                     class="size-list" id="modalSizeList"><!-- Les tailles seront chargées dynamiquement -->
  2406.                                 </ul>
  2407.                             </div>
  2408.                         </div>
  2409.                         <!-- Matériau -->
  2410.                         <div class="common-filter mb-4">
  2411.                             <div class="head">Matériau</div>
  2412.                             <div id="modalMaterialsFilter">
  2413.                                 <ul
  2414.                                     class="material-list" id="modalMaterialList"><!-- Les matériaux seront chargés dynamiquement -->
  2415.                                 </ul>
  2416.                             </div>
  2417.                         </div>
  2418.                     </div>
  2419.                 </div>
  2420.             </div>
  2421.             <div class="modal-footer" style="flex-shrink: 0; border-top: 1px solid #dee2e6; padding: 1rem 1.5rem; background: #f8f9fa;">
  2422.                 <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
  2423.                     <i class="lnr lnr-cross-circle me-1"></i>Annuler
  2424.                 </button>
  2425.                 <button type="button" class="btn btn-outline-danger" id="modalResetFiltersBtn">
  2426.                     <i class="lnr lnr-refresh me-1"></i>Réinitialiser
  2427.                 </button>
  2428.                 <button type="button" class="btn btn-primary" id="modalApplyFiltersBtn">
  2429.                     <i class="lnr lnr-checkmark-circle me-1"></i>Appliquer les filtres
  2430.                 </button>
  2431.             </div>
  2432.         </div>
  2433.     </div>
  2434. </div>
  2435. <script>
  2436.     // Synchroniser les filtres du modal avec currentFilters au chargement
  2437. document.addEventListener('DOMContentLoaded', function () {
  2438. console.log('[Modal] Initializing filter modal...');
  2439. // Fonction pour synchroniser les valeurs du modal avec currentFilters
  2440. function syncModalFilters() {
  2441. console.log('[Modal] Syncing filters with currentFilters:', currentFilters);
  2442. // Réinitialiser tous les radios "Tous" d'abord
  2443. document.querySelectorAll('#filtersModal input[type="radio"][id*="-all"]').forEach(radio => {
  2444. radio.checked = true;
  2445. });
  2446. // Marques
  2447. if (currentFilters.brand) {
  2448. const brandRadio = document.querySelector (`#modalBrandList input[value="${
  2449. currentFilters.brand
  2450. }"]`);
  2451. if (brandRadio) {
  2452. brandRadio.checked = true;
  2453. console.log('[Modal] Brand synced:', currentFilters.brand);
  2454. }
  2455. } else {
  2456. const allBrands = document.getElementById('modal-all-brands');
  2457. if (allBrands) 
  2458. allBrands.checked = true;
  2459. }
  2460. // Conditions
  2461. if (currentFilters.condition) {
  2462. const conditionRadio = document.querySelector (`#modalConditionList input[value="${
  2463. currentFilters.condition
  2464. }"]`);
  2465. if (conditionRadio) {
  2466. conditionRadio.checked = true;
  2467. console.log('[Modal] Condition synced:', currentFilters.condition);
  2468. }
  2469. } else {
  2470. const allConditions = document.getElementById('modal-all-conditions');
  2471. if (allConditions) 
  2472. allConditions.checked = true;
  2473. }
  2474. // Featured
  2475. if (currentFilters.featured === 'true') {
  2476. const featuredRadio = document.getElementById('modal-featured-only');
  2477. if (featuredRadio) {
  2478. featuredRadio.checked = true;
  2479. console.log('[Modal] Featured synced');
  2480. }
  2481. } else {
  2482. const allFeatured = document.getElementById('modal-featured-all');
  2483. if (allFeatured) 
  2484. allFeatured.checked = true;
  2485. }
  2486. // Digital
  2487. if (currentFilters.digital === 'true') {
  2488. const digitalRadio = document.getElementById('modal-digital-digital');
  2489. if (digitalRadio) 
  2490. digitalRadio.checked = true;
  2491. } else if (currentFilters.digital === 'false') {
  2492. const physicalRadio = document.getElementById('modal-digital-physical');
  2493. if (physicalRadio) 
  2494. physicalRadio.checked = true;
  2495. } else {
  2496. const allDigital = document.getElementById('modal-digital-all');
  2497. if (allDigital) 
  2498. allDigital.checked = true;
  2499. }
  2500. // Availability
  2501. if (currentFilters.availability) {
  2502. const availabilityId = `modal-availability-${
  2503. currentFilters.availability.replace('_', '-')
  2504. }`;
  2505. const availabilityRadio = document.getElementById(availabilityId);
  2506. if (availabilityRadio) {
  2507. availabilityRadio.checked = true;
  2508. console.log('[Modal] Availability synced:', currentFilters.availability);
  2509. }
  2510. } else {
  2511. const allAvailability = document.getElementById('modal-availability-all');
  2512. if (allAvailability) 
  2513. allAvailability.checked = true;
  2514. }
  2515. // Rating
  2516. if (currentFilters.ratingMin || currentFilters.rating) {
  2517. const ratingValue = currentFilters.ratingMin || currentFilters.rating;
  2518. const ratingRadio = document.getElementById (`modal-rating-${ratingValue}`);
  2519. if (ratingRadio) {
  2520. ratingRadio.checked = true;
  2521. console.log('[Modal] Rating synced:', ratingValue);
  2522. }
  2523. } else {
  2524. const allRating = document.getElementById('modal-rating-all');
  2525. if (allRating) 
  2526. allRating.checked = true;
  2527. }
  2528. // Prix
  2529. const priceMinInput = document.getElementById('modal-priceMin');
  2530. const priceMaxInput = document.getElementById('modal-priceMax');
  2531. if (priceMinInput) 
  2532. priceMinInput.value = currentFilters.priceMin || '';
  2533. if (priceMaxInput) 
  2534. priceMaxInput.value = currentFilters.priceMax || '';
  2535. // Poids
  2536. const weightMinInput = document.getElementById('modal-weightMin');
  2537. const weightMaxInput = document.getElementById('modal-weightMax');
  2538. if (weightMinInput) 
  2539. weightMinInput.value = currentFilters.weightMin || '';
  2540. if (weightMaxInput) 
  2541. weightMaxInput.value = currentFilters.weightMax || '';
  2542. console.log('[Modal] Filters synced successfully');
  2543. }
  2544. // Fonction pour charger les filtres dynamiques dans le modal
  2545. function loadDynamicFiltersToModal() {
  2546. console.log('[Modal] Loading dynamic filters...');
  2547. // Boutiques
  2548. const sidebarShopList = document.getElementById('shopList');
  2549. const modalShopList = document.getElementById('modalShopList');
  2550. if (modalShopList) { // Si la sidebar a déjà les boutiques, les copier
  2551. if (sidebarShopList && sidebarShopList.innerHTML.trim() && ! sidebarShopList.innerHTML.includes('Aucune boutique') && ! sidebarShopList.innerHTML.includes('seront chargées')) {
  2552. let shopHtml = sidebarShopList.innerHTML.replace(/id="all-shops"/g, 'id="modal-all-shops"').replace(/id="shop-/g, 'id="modal-shop-').replace(/name="shop"/g, 'name="modal-shop"').replace(/for="all-shops"/g, 'for="modal-all-shops"').replace(/for="shop-/g, 'for="modal-shop-');
  2553. modalShopList.innerHTML = shopHtml;
  2554. // Synchroniser la sélection
  2555. if (currentFilters.shop) {
  2556. const shopRadio = document.querySelector (`#modalShopList input[value="${
  2557. currentFilters.shop
  2558. }"]`);
  2559. if (shopRadio) 
  2560. shopRadio.checked = true;
  2561. } else {
  2562. const allShops = document.getElementById('modal-all-shops');
  2563. if (allShops) 
  2564. allShops.checked = true;
  2565. }
  2566. console.log('[Modal] Shops loaded from sidebar');
  2567. } else { // Si pas encore chargés, afficher un message ou laisser vide
  2568. if (! modalShopList.innerHTML.trim() || modalShopList.innerHTML.includes('seront chargées')) {
  2569. modalShopList.innerHTML = '<li class="filter-list"><span class="text-muted">Les boutiques seront chargées lors de l\'application des filtres</span></li>';
  2570. }
  2571. }
  2572. }
  2573. // Couleurs
  2574. const sidebarColorList = document.getElementById('colorList');
  2575. const modalColorList = document.getElementById('modalColorList');
  2576. if (sidebarColorList && modalColorList) {
  2577. if (sidebarColorList.innerHTML.trim() && ! sidebarColorList.innerHTML.includes('Aucun')) {
  2578. let colorHtml = sidebarColorList.innerHTML.replace(/id="all-color"/g, 'id="modal-all-color"').replace(/id="color-/g, 'id="modal-color-').replace(/name="color"/g, 'name="modal-color"').replace(/for="all-color"/g, 'for="modal-all-color"').replace(/for="color-/g, 'for="modal-color-');
  2579. modalColorList.innerHTML = colorHtml;
  2580. // Synchroniser la sélection
  2581. if (currentFilters.color) {
  2582. const colorRadio = document.querySelector (`#modalColorList input[value="${
  2583. currentFilters.color
  2584. }"]`);
  2585. if (colorRadio) 
  2586. colorRadio.checked = true;
  2587. } else {
  2588. const allColor = document.getElementById('modal-all-color');
  2589. if (allColor) 
  2590. allColor.checked = true;
  2591. }
  2592. console.log('[Modal] Colors loaded');
  2593. }
  2594. }
  2595. // Tailles
  2596. const sidebarSizeList = document.getElementById('sizeList');
  2597. const modalSizeList = document.getElementById('modalSizeList');
  2598. if (sidebarSizeList && modalSizeList) {
  2599. if (sidebarSizeList.innerHTML.trim() && ! sidebarSizeList.innerHTML.includes('Aucun')) {
  2600. let sizeHtml = sidebarSizeList.innerHTML.replace(/id="all-size"/g, 'id="modal-all-size"').replace(/id="size-/g, 'id="modal-size-').replace(/name="size"/g, 'name="modal-size"').replace(/for="all-size"/g, 'for="modal-all-size"').replace(/for="size-/g, 'for="modal-size-');
  2601. modalSizeList.innerHTML = sizeHtml;
  2602. // Synchroniser la sélection
  2603. if (currentFilters.size) {
  2604. const sizeRadio = document.querySelector (`#modalSizeList input[value="${
  2605. currentFilters.size
  2606. }"]`);
  2607. if (sizeRadio) 
  2608. sizeRadio.checked = true;
  2609. } else {
  2610. const allSize = document.getElementById('modal-all-size');
  2611. if (allSize) 
  2612. allSize.checked = true;
  2613. }
  2614. console.log('[Modal] Sizes loaded');
  2615. }
  2616. }
  2617. // Matériaux
  2618. const sidebarMaterialList = document.getElementById('materialList');
  2619. const modalMaterialList = document.getElementById('modalMaterialList');
  2620. if (sidebarMaterialList && modalMaterialList) {
  2621. if (sidebarMaterialList.innerHTML.trim() && ! sidebarMaterialList.innerHTML.includes('Aucun')) {
  2622. let materialHtml = sidebarMaterialList.innerHTML.replace(/id="all-material"/g, 'id="modal-all-material"').replace(/id="material-/g, 'id="modal-material-').replace(/name="material"/g, 'name="modal-material"').replace(/for="all-material"/g, 'for="modal-all-material"').replace(/for="material-/g, 'for="modal-material-');
  2623. modalMaterialList.innerHTML = materialHtml;
  2624. // Synchroniser la sélection
  2625. if (currentFilters.material) {
  2626. const materialRadio = document.querySelector (`#modalMaterialList input[value="${
  2627. currentFilters.material
  2628. }"]`);
  2629. if (materialRadio) 
  2630. materialRadio.checked = true;
  2631. } else {
  2632. const allMaterial = document.getElementById('modal-all-material');
  2633. if (allMaterial) 
  2634. allMaterial.checked = true;
  2635. }
  2636. console.log('[Modal] Materials loaded');
  2637. }
  2638. }
  2639. }
  2640. // Synchroniser à l'ouverture du modal
  2641. const filtersModal = document.getElementById('filtersModal');
  2642. if (filtersModal) {
  2643. filtersModal.addEventListener('show.bs.modal', function () {
  2644. console.log('[Modal] Modal opening, syncing filters...');
  2645. // Charger les filtres dynamiques d'abord si pas encore chargés
  2646. loadDynamicFiltersToModal();
  2647. // Puis synchroniser les valeurs
  2648. setTimeout(() => {
  2649. syncModalFilters();
  2650. }, 100);
  2651. });
  2652. }
  2653. // Charger les filtres dynamiques au chargement initial si la sidebar les a déjà
  2654. loadDynamicFiltersToModal();
  2655. // Bouton Appliquer les filtres
  2656. const applyBtn = document.getElementById('modalApplyFiltersBtn');
  2657. if (applyBtn) {
  2658. applyBtn.addEventListener('click', function (e) {
  2659. e.preventDefault();
  2660. console.log('[Modal] Apply button clicked, collecting filter values...');
  2661. // Collecter les valeurs du modal
  2662. const brandRadio = document.querySelector('#modalBrandList input[name="modal-brand"]:checked');
  2663. if (brandRadio) {
  2664. currentFilters.brand = brandRadio.id !== 'modal-all-brands' ? brandRadio.value : '';
  2665. console.log('[Modal] Brand:', currentFilters.brand);
  2666. } else {
  2667. currentFilters.brand = '';
  2668. console.log('[Modal] Brand: No selection found');
  2669. }
  2670. // Conditions
  2671. const conditionRadio = document.querySelector('#modalConditionList input[name="modal-condition"]:checked');
  2672. if (conditionRadio) {
  2673. currentFilters.condition = conditionRadio.id !== 'modal-all-conditions' ? conditionRadio.value : '';
  2674. console.log('[Modal] Condition:', currentFilters.condition);
  2675. } else {
  2676. currentFilters.condition = '';
  2677. }
  2678. // Featured
  2679. const featuredRadio = document.querySelector('#filtersModal input[name="modal-featured"]:checked');
  2680. if (featuredRadio) {
  2681. currentFilters.featured = featuredRadio.id !== 'modal-featured-all' ? featuredRadio.value : '';
  2682. console.log('[Modal] Featured:', currentFilters.featured);
  2683. } else {
  2684. currentFilters.featured = '';
  2685. }
  2686. // Digital
  2687. const digitalRadio = document.querySelector('#filtersModal input[name="modal-digital"]:checked');
  2688. if (digitalRadio) {
  2689. currentFilters.digital = digitalRadio.id !== 'modal-digital-all' ? digitalRadio.value : '';
  2690. console.log('[Modal] Digital:', currentFilters.digital);
  2691. } else {
  2692. currentFilters.digital = '';
  2693. }
  2694. // Availability
  2695. const availabilityRadio = document.querySelector('#filtersModal input[name="modal-availability"]:checked');
  2696. if (availabilityRadio) {
  2697. currentFilters.availability = availabilityRadio.id !== 'modal-availability-all' ? availabilityRadio.value : '';
  2698. console.log('[Modal] Availability:', currentFilters.availability);
  2699. } else {
  2700. currentFilters.availability = '';
  2701. }
  2702. // Rating
  2703. const ratingRadio = document.querySelector('#filtersModal input[name="modal-rating"]:checked');
  2704. if (ratingRadio && ratingRadio.id !== 'modal-rating-all') {
  2705. currentFilters.ratingMin = ratingRadio.value;
  2706. currentFilters.rating = ratingRadio.value;
  2707. console.log('[Modal] Rating:', currentFilters.ratingMin);
  2708. } else {
  2709. currentFilters.ratingMin = '';
  2710. currentFilters.rating = '';
  2711. }
  2712. // Prix
  2713. const priceMinInput = document.getElementById('modal-priceMin');
  2714. const priceMaxInput = document.getElementById('modal-priceMax');
  2715. currentFilters.priceMin = priceMinInput ? priceMinInput.value.trim() : '';
  2716. currentFilters.priceMax = priceMaxInput ? priceMaxInput.value.trim() : '';
  2717. console.log('[Modal] Price:', currentFilters.priceMin, '-', currentFilters.priceMax);
  2718. // Poids
  2719. const weightMinInput = document.getElementById('modal-weightMin');
  2720. const weightMaxInput = document.getElementById('modal-weightMax');
  2721. currentFilters.weightMin = weightMinInput ? weightMinInput.value.trim() : '';
  2722. currentFilters.weightMax = weightMaxInput ? weightMaxInput.value.trim() : '';
  2723. console.log('[Modal] Weight:', currentFilters.weightMin, '-', currentFilters.weightMax);
  2724. // Couleur
  2725. const colorRadio = document.querySelector('#modalColorList input[name="modal-color"]:checked');
  2726. if (colorRadio) {
  2727. currentFilters.color = colorRadio.id !== 'modal-all-color' ? colorRadio.value : '';
  2728. console.log('[Modal] Color:', currentFilters.color);
  2729. } else {
  2730. currentFilters.color = '';
  2731. }
  2732. // Taille
  2733. const sizeRadio = document.querySelector('#modalSizeList input[name="modal-size"]:checked');
  2734. if (sizeRadio) {
  2735. currentFilters.size = sizeRadio.id !== 'modal-all-size' ? sizeRadio.value : '';
  2736. console.log('[Modal] Size:', currentFilters.size);
  2737. } else {
  2738. currentFilters.size = '';
  2739. }
  2740. // Matériau
  2741. const materialRadio = document.querySelector('#modalMaterialList input[name="modal-material"]:checked');
  2742. if (materialRadio) {
  2743. currentFilters.material = materialRadio.id !== 'modal-all-material' ? materialRadio.value : '';
  2744. console.log('[Modal] Material:', currentFilters.material);
  2745. } else {
  2746. currentFilters.material = '';
  2747. }
  2748. // Boutiques
  2749. const shopRadio = document.querySelector('#modalShopList input[name="modal-shop"]:checked');
  2750. if (shopRadio) {
  2751. currentFilters.shop = shopRadio.id !== 'modal-all-shops' ? shopRadio.value : '';
  2752. console.log('[Modal] Shop:', currentFilters.shop);
  2753. } else {
  2754. currentFilters.shop = '';
  2755. }
  2756. currentFilters.page = 1;
  2757. console.log('[Modal] All filters collected:', currentFilters);
  2758. // Fermer le modal d'abord
  2759. const modalElement = document.getElementById('filtersModal');
  2760. const modal = bootstrap.Modal.getInstance(modalElement);
  2761. if (modal) {
  2762. modal.hide();
  2763. console.log('[Modal] Modal closed');
  2764. }
  2765. // Appliquer les filtres après un court délai pour laisser le modal se fermer
  2766. setTimeout(() => { // Vérifier que applyFilters existe
  2767. if (typeof window.applyFilters === 'function') {
  2768. console.log('[Modal] Calling applyFilters()...');
  2769. window.applyFilters();
  2770. // Mettre à jour le compteur de filtres actifs
  2771. setTimeout(() => {
  2772. if (typeof updateActiveFiltersCount === 'function') {
  2773. updateActiveFiltersCount();
  2774. }
  2775. }, 100);
  2776. } else {
  2777. console.error('[Modal] applyFilters is not available!', typeof window.applyFilters);
  2778. // Essayer de recharger la page avec les nouveaux paramètres
  2779. const url = new URL(window.location.href);
  2780. Object.keys(currentFilters).forEach(key => {
  2781. if (currentFilters[key] && currentFilters[key] !== '') {
  2782. let apiKey = key;
  2783. if (key === 'priceMin') {
  2784. apiKey = 'price_min';
  2785. } else if (key === 'priceMax') {
  2786. apiKey = 'price_max';
  2787. } else if (key === 'ratingMin') {
  2788. apiKey = 'rating_min';
  2789. } else if (key === 'weightMin') {
  2790. apiKey = 'weight_min';
  2791. } else if (key === 'weightMax') {
  2792. apiKey = 'weight_max';
  2793. } else if (key === 'stockStatus') {
  2794. apiKey = 'stock_status';
  2795. }
  2796. url.searchParams.set(apiKey, currentFilters[key]);
  2797. } else {
  2798. let apiKey = key;
  2799. if (key === 'priceMin') {
  2800. apiKey = 'price_min';
  2801. } else if (key === 'priceMax') {
  2802. apiKey = 'price_max';
  2803. } else if (key === 'ratingMin') {
  2804. apiKey = 'rating_min';
  2805. } else if (key === 'weightMin') {
  2806. apiKey = 'weight_min';
  2807. } else if (key === 'weightMax') {
  2808. apiKey = 'weight_max';
  2809. } else if (key === 'stockStatus') {
  2810. apiKey = 'stock_status';
  2811. }
  2812. url.searchParams.delete(apiKey);
  2813. }
  2814. });
  2815. window.location.href = url.toString();
  2816. }
  2817. }, 300);
  2818. });
  2819. } else {
  2820. console.error('[Modal] Apply button not found!');
  2821. }
  2822. // Bouton Réinitialiser
  2823. const resetBtn = document.getElementById('modalResetFiltersBtn');
  2824. if (resetBtn) {
  2825. resetBtn.addEventListener('click', function () {
  2826. console.log('[Modal] Reset button clicked');
  2827. // Réinitialiser tous les filtres du modal
  2828. document.querySelectorAll('#filtersModal input[type="radio"]').forEach(radio => {
  2829. if (radio.id && radio.id.includes('-all')) {
  2830. radio.checked = true;
  2831. } else {
  2832. radio.checked = false;
  2833. }
  2834. });
  2835. const priceMinInput = document.getElementById('modal-priceMin');
  2836. const priceMaxInput = document.getElementById('modal-priceMax');
  2837. const weightMinInput = document.getElementById('modal-weightMin');
  2838. const weightMaxInput = document.getElementById('modal-weightMax');
  2839. if (priceMinInput) 
  2840. priceMinInput.value = '';
  2841. if (priceMaxInput) 
  2842. priceMaxInput.value = '';
  2843. if (weightMinInput) 
  2844. weightMinInput.value = '';
  2845. if (weightMaxInput) 
  2846. weightMaxInput.value = '';
  2847. // Réinitialiser currentFilters (garder category et sort)
  2848. const category = currentFilters.category || '';
  2849. const sort = currentFilters.sort || 'newest';
  2850. currentFilters = {
  2851. category: category,
  2852. sort: sort,
  2853. page: 1,
  2854. brand: '',
  2855. condition: '',
  2856. featured: '',
  2857. digital: '',
  2858. availability: '',
  2859. ratingMin: '',
  2860. rating: '',
  2861. priceMin: '',
  2862. priceMax: '',
  2863. weightMin: '',
  2864. weightMax: '',
  2865. color: '',
  2866. size: '',
  2867. material: '',
  2868. shop: ''
  2869. };
  2870. console.log('[Modal] Filters reset, currentFilters:', currentFilters);
  2871. // Fermer le modal d'abord
  2872. const modalElement = document.getElementById('filtersModal');
  2873. const modal = bootstrap.Modal.getInstance(modalElement);
  2874. if (modal) {
  2875. modal.hide();
  2876. console.log('[Modal] Modal closed after reset');
  2877. }
  2878. // Appliquer les filtres après un court délai
  2879. setTimeout(() => {
  2880. if (typeof window.applyFilters === 'function') {
  2881. window.applyFilters();
  2882. } else {
  2883. console.error('[Modal] applyFilters not available, reloading page...');
  2884. // Recharger la page avec les filtres réinitialisés
  2885. const url = new URL(window.location.href);
  2886. url.searchParams.delete('price_min');
  2887. url.searchParams.delete('price_max');
  2888. url.searchParams.delete('rating_min');
  2889. url.searchParams.delete('weight_min');
  2890. url.searchParams.delete('weight_max');
  2891. url.searchParams.delete('brand');
  2892. url.searchParams.delete('condition');
  2893. url.searchParams.delete('featured');
  2894. url.searchParams.delete('digital');
  2895. url.searchParams.delete('availability');
  2896. url.searchParams.delete('color');
  2897. url.searchParams.delete('size');
  2898. url.searchParams.delete('material');
  2899. url.searchParams.delete('shop');
  2900. window.location.href = url.toString();
  2901. }
  2902. }, 300);
  2903. });
  2904. } else {
  2905. console.error('[Modal] Reset button not found!');
  2906. }
  2907. // S'assurer que les filtres dynamiques sont chargés au premier chargement si disponibles
  2908. setTimeout(() => {
  2909. loadDynamicFiltersToModal();
  2910. }, 500);
  2911. });
  2912. </script>{% endblock %}