var/cache/dev/twig/c0/c04a5fdd74c8e9f784ba4242ea93c0af.php line 56

Open in your IDE?
  1. <?php
  2. use Twig\Environment;
  3. use Twig\Error\LoaderError;
  4. use Twig\Error\RuntimeError;
  5. use Twig\Extension\CoreExtension;
  6. use Twig\Extension\SandboxExtension;
  7. use Twig\Markup;
  8. use Twig\Sandbox\SecurityError;
  9. use Twig\Sandbox\SecurityNotAllowedTagError;
  10. use Twig\Sandbox\SecurityNotAllowedFilterError;
  11. use Twig\Sandbox\SecurityNotAllowedFunctionError;
  12. use Twig\Source;
  13. use Twig\Template;
  14. use Twig\TemplateWrapper;
  15. /* home/listing.html.twig */
  16. class __TwigTemplate_aa414e651db26efa98eb1f11d53e7f48 extends Template
  17. {
  18.     private Source $source;
  19.     /**
  20.      * @var array<string, Template>
  21.      */
  22.     private array $macros = [];
  23.     public function __construct(Environment $env)
  24.     {
  25.         parent::__construct($env);
  26.         $this->source $this->getSourceContext();
  27.         $this->blocks = [
  28.             'body' => [$this'block_body'],
  29.             'title' => [$this'block_title'],
  30.             'stylesheets' => [$this'block_stylesheets'],
  31.             'javascripts' => [$this'block_javascripts'],
  32.         ];
  33.     }
  34.     protected function doGetParent(array $context): bool|string|Template|TemplateWrapper
  35.     {
  36.         // line 1
  37.         return "base_home.html.twig";
  38.     }
  39.     protected function doDisplay(array $context, array $blocks = []): iterable
  40.     {
  41.         $macros $this->macros;
  42.         $__internal_5a27a8ba21ca79b61932376b2fa922d2 $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
  43.         $__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template""home/listing.html.twig"));
  44.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
  45.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template""home/listing.html.twig"));
  46.         $this->parent $this->load("base_home.html.twig"1);
  47.         yield from $this->parent->unwrap()->yield($contextarray_merge($this->blocks$blocks));
  48.         
  49.         $__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
  50.         
  51.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
  52.     }
  53.     // line 3
  54.     /**
  55.      * @return iterable<null|scalar|\Stringable>
  56.      */
  57.     public function block_body(array $context, array $blocks = []): iterable
  58.     {
  59.         $macros $this->macros;
  60.         $__internal_5a27a8ba21ca79b61932376b2fa922d2 $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
  61.         $__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block""body"));
  62.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
  63.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block""body"));
  64.         // line 4
  65.         yield "
  66. \t<!-- Start Banner Area -->
  67. \t<section class=\"banner-area organic-breadcrumb\">
  68. \t\t<div class=\"container\">
  69. \t\t\t<div class=\"breadcrumb-banner d-flex flex-wrap align-items-center justify-content-end\">
  70. \t\t\t\t<div class=\"col-first\">
  71. \t\t\t\t\t<h1>Page de listage de produit</h1>
  72. \t\t\t\t\t<nav class=\"d-flex align-items-center\">
  73. \t\t\t\t\t\t<a href=\"";
  74.         // line 12
  75.         yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_home");
  76.         yield "\">Accueil<span class=\"lnr lnr-arrow-right\"></span>
  77. \t\t\t\t\t\t</a>
  78. \t\t\t\t\t\t<a href=\"javascript:void(0);\">Liste des produits</a>
  79. \t\t\t\t\t</nav>
  80. \t\t\t\t</div>
  81. \t\t\t</div>
  82. \t\t</div>
  83. \t</section>
  84. \t<!-- End Banner Area -->
  85. \t<div class=\"container\">
  86. \t\t<div class=\"row\">
  87. \t\t\t<div class=\"col-xl-3 col-lg-4 col-md-5\">
  88. \t\t\t\t<!-- Bouton toggle sidebar mobile -->
  89. \t\t\t\t<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\">
  90. \t\t\t\t\t<i class=\"lnr lnr-menu me-2\"></i>Filtres et catégories
  91. \t\t\t\t\t<i class=\"lnr lnr-chevron-down ms-2 toggle-icon\"></i>
  92. \t\t\t\t</button>
  93. \t\t\t\t<!-- Sidebar avec collapse -->
  94. \t\t\t\t<div class=\"collapse d-md-block\" id=\"listingSidebarCollapse\">
  95. \t\t\t\t\t<div class=\"sidebar-categories\">
  96. \t\t\t\t\t\t<div class=\"head\">Catégories</div>
  97. \t\t\t\t\t\t<ul class=\"main-categories\">
  98. \t\t\t\t\t\t\t<li class=\"main-nav-list\">
  99. \t\t\t\t\t\t\t\t<a href=\"";
  100.         // line 37
  101.         yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_listing");
  102.         yield "\" class=\"category-link ";
  103.         if ((($tmp =  !($context["currentCategory"] ?? null)) && $tmp instanceof Markup ? (string) $tmp $tmp)) {
  104.             yield "active";
  105.         }
  106.         yield "\">
  107. \t\t\t\t\t\t\t\t\t<span class=\"lnr lnr-tag\"></span>Toutes les catégories
  108. \t\t\t\t\t\t\t\t\t<span class=\"number\">(";
  109.         // line 39
  110.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["totalProducts"] ?? null), "html"nulltrue);
  111.         yield ")</span>
  112. \t\t\t\t\t\t\t\t</a>
  113. \t\t\t\t\t\t\t</li>
  114. \t\t\t\t\t\t\t";
  115.         // line 42
  116.         $context['_parent'] = $context;
  117.         $context['_seq'] = CoreExtension::ensureTraversable(($context["categories"] ?? null));
  118.         $context['_iterated'] = false;
  119.         foreach ($context['_seq'] as $context["_key"] => $context["category"]) {
  120.             // line 43
  121.             yield "\t\t\t\t\t\t\t\t<li class=\"main-nav-list\">
  122. \t\t\t\t\t\t\t\t\t<a href=\"";
  123.             // line 44
  124.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_listing", ["category" => CoreExtension::getAttribute($this->env$this->source$context["category"], "slug", [], "any"falsefalsefalse44)]), "html"nulltrue);
  125.             yield "\" class=\"category-link ";
  126.             if ((($context["currentCategory"] ?? null) == CoreExtension::getAttribute($this->env$this->source$context["category"], "slug", [], "any"falsefalsefalse44))) {
  127.                 yield "active";
  128.             }
  129.             yield "\" data-category=\"";
  130.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["category"], "slug", [], "any"falsefalsefalse44), "html"nulltrue);
  131.             yield "\">
  132. \t\t\t\t\t\t\t\t\t\t<span class=\"lnr lnr-tag\"></span>
  133. \t\t\t\t\t\t\t\t\t\t";
  134.             // line 46
  135.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["category"], "name", [], "any"falsefalsefalse46), "html"nulltrue);
  136.             yield "
  137. \t\t\t\t\t\t\t\t\t\t<span class=\"number\">(";
  138.             // line 47
  139.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), CoreExtension::getAttribute($this->env$this->source$context["category"], "products", [], "any"falsefalsefalse47)), "html"nulltrue);
  140.             yield ")</span>
  141. \t\t\t\t\t\t\t\t\t</a>
  142. \t\t\t\t\t\t\t\t</li>
  143. \t\t\t\t\t\t\t";
  144.             $context['_iterated'] = true;
  145.         }
  146.         // line 50
  147.         if (!$context['_iterated']) {
  148.             // line 51
  149.             yield "\t\t\t\t\t\t\t\t<li class=\"main-nav-list\">
  150. \t\t\t\t\t\t\t\t\t<span>Aucune catégorie</span>
  151. \t\t\t\t\t\t\t\t</li>
  152. \t\t\t\t\t\t\t";
  153.         }
  154.         $_parent $context['_parent'];
  155.         unset($context['_seq'], $context['_key'], $context['category'], $context['_parent'], $context['_iterated']);
  156.         $context array_intersect_key($context$_parent) + $_parent;
  157.         // line 55
  158.         yield "\t\t\t\t\t\t</ul>
  159. \t\t\t\t\t</div>
  160. \t\t\t\t\t<div class=\"sidebar-filter mt-50\" id=\"brandsFilter\">
  161. \t\t\t\t\t\t<div class=\"top-filter-head\">Filtres</div>
  162. \t\t\t\t\t\t<div class=\"common-filter\">
  163. \t\t\t\t\t\t\t<div class=\"head\">Marques</div>
  164. \t\t\t\t\t\t\t<ul class=\"brand-list\" id=\"brandList\">
  165. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  166. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"all-brands\" name=\"brand\" checked>
  167. \t\t\t\t\t\t\t\t\t<label for=\"all-brands\">Toutes les marques</label>
  168. \t\t\t\t\t\t\t\t</li>
  169. \t\t\t\t\t\t\t\t";
  170.         // line 66
  171.         $context['_parent'] = $context;
  172.         $context['_seq'] = CoreExtension::ensureTraversable(($context["brands"] ?? null));
  173.         foreach ($context['_seq'] as $context["_key"] => $context["brand"]) {
  174.             // line 67
  175.             yield "\t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  176. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"brand-";
  177.             // line 68
  178.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["brand"], "id", [], "any"falsefalsefalse68), "html"nulltrue);
  179.             yield "\" name=\"brand\" value=\"";
  180.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["brand"], "slug", [], "any"falsefalsefalse68), "html"nulltrue);
  181.             yield "\">
  182. \t\t\t\t\t\t\t\t\t\t<label for=\"brand-";
  183.             // line 69
  184.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["brand"], "id", [], "any"falsefalsefalse69), "html"nulltrue);
  185.             yield "\">";
  186.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["brand"], "name", [], "any"falsefalsefalse69), "html"nulltrue);
  187.             yield "<span>(";
  188.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["brand"], "getActiveProductsCount", [], "method"falsefalsefalse69), "html"nulltrue);
  189.             yield ")</span>
  190. \t\t\t\t\t\t\t\t\t\t</label>
  191. \t\t\t\t\t\t\t\t\t</li>
  192. \t\t\t\t\t\t\t\t";
  193.         }
  194.         $_parent $context['_parent'];
  195.         unset($context['_seq'], $context['_key'], $context['brand'], $context['_parent']);
  196.         $context array_intersect_key($context$_parent) + $_parent;
  197.         // line 73
  198.         yield "\t\t\t\t\t\t\t</ul>
  199. \t\t\t\t\t\t</div>
  200. \t\t\t\t\t\t<!-- Filtres avancés -->
  201. \t\t\t\t\t\t<div class=\"common-filter\">
  202. \t\t\t\t\t\t\t<div class=\"head\">Boutiques</div>
  203. \t\t\t\t\t\t\t<div id=\"shopsFilter\">
  204. \t\t\t\t\t\t\t\t<form action=\"#\">
  205. \t\t\t\t\t\t\t\t\t<ul
  206. \t\t\t\t\t\t\t\t\t\tclass=\"shop-list\" id=\"shopList\"><!-- Les boutiques seront chargées dynamiquement -->
  207. \t\t\t\t\t\t\t\t\t</ul>
  208. \t\t\t\t\t\t\t\t</form>
  209. \t\t\t\t\t\t\t</div>
  210. \t\t\t\t\t\t</div>
  211. \t\t\t\t\t\t<div class=\"common-filter\">
  212. \t\t\t\t\t\t\t<div class=\"head\">Condition (";
  213.         // line 88
  214.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), ($context["conditions"] ?? null)), "html"nulltrue);
  215.         yield ")</div>
  216. \t\t\t\t\t\t\t<ul class=\"condition-list\" id=\"conditionList\">
  217. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  218. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"all-conditions\" name=\"condition\" checked>
  219. \t\t\t\t\t\t\t\t\t<label for=\"all-conditions\">Toutes les conditions</label>
  220. \t\t\t\t\t\t\t\t</li>
  221. \t\t\t\t\t\t\t\t";
  222.         // line 94
  223.         $context['_parent'] = $context;
  224.         $context['_seq'] = CoreExtension::ensureTraversable(($context["conditions"] ?? null));
  225.         $context['_iterated'] = false;
  226.         foreach ($context['_seq'] as $context["_key"] => $context["condition"]) {
  227.             // line 95
  228.             yield "\t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  229. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"condition-";
  230.             // line 96
  231.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["condition"], "id", [], "any"falsefalsefalse96), "html"nulltrue);
  232.             yield "\" name=\"condition\" value=\"";
  233.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["condition"], "slug", [], "any"falsefalsefalse96), "html"nulltrue);
  234.             yield "\">
  235. \t\t\t\t\t\t\t\t\t\t<label for=\"condition-";
  236.             // line 97
  237.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["condition"], "id", [], "any"falsefalsefalse97), "html"nulltrue);
  238.             yield "\">";
  239.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["condition"], "name", [], "any"falsefalsefalse97), "html"nulltrue);
  240.             yield "<span>(";
  241.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["condition"], "getActiveProductsCount", [], "method"falsefalsefalse97), "html"nulltrue);
  242.             yield ")</span>
  243. \t\t\t\t\t\t\t\t\t\t</label>
  244. \t\t\t\t\t\t\t\t\t</li>
  245. \t\t\t\t\t\t\t\t";
  246.             $context['_iterated'] = true;
  247.         }
  248.         // line 100
  249.         if (!$context['_iterated']) {
  250.             // line 101
  251.             yield "\t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  252. \t\t\t\t\t\t\t\t\t\t<span class=\"text-muted\">Aucune condition disponible</span>
  253. \t\t\t\t\t\t\t\t\t</li>
  254. \t\t\t\t\t\t\t\t";
  255.         }
  256.         $_parent $context['_parent'];
  257.         unset($context['_seq'], $context['_key'], $context['condition'], $context['_parent'], $context['_iterated']);
  258.         $context array_intersect_key($context$_parent) + $_parent;
  259.         // line 105
  260.         yield "\t\t\t\t\t\t\t</ul>
  261. \t\t\t\t\t\t</div>
  262. \t\t\t\t\t\t<div class=\"common-filter\">
  263. \t\t\t\t\t\t\t<div class=\"head\">Produits vedettes</div>
  264. \t\t\t\t\t\t\t<form action=\"#\">
  265. \t\t\t\t\t\t\t\t<ul>
  266. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  267. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"featured-all\" name=\"featured\" checked>
  268. \t\t\t\t\t\t\t\t\t\t<label for=\"featured-all\">Tous les produits</label>
  269. \t\t\t\t\t\t\t\t\t</li>
  270. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  271. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"featured-only\" name=\"featured\" value=\"true\">
  272. \t\t\t\t\t\t\t\t\t\t<label for=\"featured-only\">Produits vedettes uniquement</label>
  273. \t\t\t\t\t\t\t\t\t</li>
  274. \t\t\t\t\t\t\t\t</ul>
  275. \t\t\t\t\t\t\t</form>
  276. \t\t\t\t\t\t</div>
  277. \t\t\t\t\t\t<div class=\"common-filter\">
  278. \t\t\t\t\t\t\t<div class=\"head\">Type de produit</div>
  279. \t\t\t\t\t\t\t<form action=\"#\">
  280. \t\t\t\t\t\t\t\t<ul>
  281. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  282. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"digital-all\" name=\"digital\" checked>
  283. \t\t\t\t\t\t\t\t\t\t<label for=\"digital-all\">Tous les types</label>
  284. \t\t\t\t\t\t\t\t\t</li>
  285. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  286. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"digital-physical\" name=\"digital\" value=\"false\">
  287. \t\t\t\t\t\t\t\t\t\t<label for=\"digital-physical\">Produits physiques</label>
  288. \t\t\t\t\t\t\t\t\t</li>
  289. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  290. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"digital-digital\" name=\"digital\" value=\"true\">
  291. \t\t\t\t\t\t\t\t\t\t<label for=\"digital-digital\">Produits numériques</label>
  292. \t\t\t\t\t\t\t\t\t</li>
  293. \t\t\t\t\t\t\t\t</ul>
  294. \t\t\t\t\t\t\t</form>
  295. \t\t\t\t\t\t</div>
  296. \t\t\t\t\t\t<div class=\"common-filter\">
  297. \t\t\t\t\t\t\t<div class=\"head\">Disponibilité</div>
  298. \t\t\t\t\t\t\t<form action=\"#\">
  299. \t\t\t\t\t\t\t\t<ul>
  300. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  301. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"availability-all\" name=\"availability\" checked>
  302. \t\t\t\t\t\t\t\t\t\t<label for=\"availability-all\">Tous</label>
  303. \t\t\t\t\t\t\t\t\t</li>
  304. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  305. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"availability-in-stock\" name=\"availability\" value=\"in_stock\">
  306. \t\t\t\t\t\t\t\t\t\t<label for=\"availability-in-stock\">En stock</label>
  307. \t\t\t\t\t\t\t\t\t</li>
  308. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  309. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"availability-low-stock\" name=\"availability\" value=\"low_stock\">
  310. \t\t\t\t\t\t\t\t\t\t<label for=\"availability-low-stock\">Stock faible</label>
  311. \t\t\t\t\t\t\t\t\t</li>
  312. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  313. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"availability-out-of-stock\" name=\"availability\" value=\"out_of_stock\">
  314. \t\t\t\t\t\t\t\t\t\t<label for=\"availability-out-of-stock\">Rupture de stock</label>
  315. \t\t\t\t\t\t\t\t\t</li>
  316. \t\t\t\t\t\t\t\t</ul>
  317. \t\t\t\t\t\t\t</form>
  318. \t\t\t\t\t\t</div>
  319. \t\t\t\t\t\t<div class=\"common-filter\">
  320. \t\t\t\t\t\t\t<div class=\"head\">Note minimale</div>
  321. \t\t\t\t\t\t\t<form action=\"#\">
  322. \t\t\t\t\t\t\t\t<ul>
  323. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  324. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"rating-all\" name=\"rating\" checked>
  325. \t\t\t\t\t\t\t\t\t\t<label for=\"rating-all\">Toutes les notes</label>
  326. \t\t\t\t\t\t\t\t\t</li>
  327. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  328. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"rating-4\" name=\"rating\" value=\"4\">
  329. \t\t\t\t\t\t\t\t\t\t<label for=\"rating-4\">4 étoiles et plus</label>
  330. \t\t\t\t\t\t\t\t\t</li>
  331. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  332. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"rating-3\" name=\"rating\" value=\"3\">
  333. \t\t\t\t\t\t\t\t\t\t<label for=\"rating-3\">3 étoiles et plus</label>
  334. \t\t\t\t\t\t\t\t\t</li>
  335. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  336. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"rating-2\" name=\"rating\" value=\"2\">
  337. \t\t\t\t\t\t\t\t\t\t<label for=\"rating-2\">2 étoiles et plus</label>
  338. \t\t\t\t\t\t\t\t\t</li>
  339. \t\t\t\t\t\t\t\t</ul>
  340. \t\t\t\t\t\t\t</form>
  341. \t\t\t\t\t\t</div>
  342. \t\t\t\t\t\t<div class=\"common-filter\">
  343. \t\t\t\t\t\t\t<div class=\"head\">Poids</div>
  344. \t\t\t\t\t\t\t<div class=\"weight-range-area\">
  345. \t\t\t\t\t\t\t\t<div class=\"weight-inputs d-flex\">
  346. \t\t\t\t\t\t\t\t\t<div class=\"weight-input\">
  347. \t\t\t\t\t\t\t\t\t\t<label>Min (kg):</label>
  348. \t\t\t\t\t\t\t\t\t\t<input type=\"number\" id=\"weightMin\" placeholder=\"0\" step=\"0.1\" min=\"0\">
  349. \t\t\t\t\t\t\t\t\t</div>
  350. \t\t\t\t\t\t\t\t\t<div class=\"weight-input\">
  351. \t\t\t\t\t\t\t\t\t\t<label>Max (kg):</label>
  352. \t\t\t\t\t\t\t\t\t\t<input type=\"number\" id=\"weightMax\" placeholder=\"100\" step=\"0.1\" min=\"0\">
  353. \t\t\t\t\t\t\t\t\t</div>
  354. \t\t\t\t\t\t\t\t</div>
  355. \t\t\t\t\t\t\t</div>
  356. \t\t\t\t\t\t</div>
  357. \t\t\t\t\t\t<div class=\"common-filter\">
  358. \t\t\t\t\t\t\t<div class=\"head\">Couleur</div>
  359. \t\t\t\t\t\t\t<div id=\"colorsFilter\">
  360. \t\t\t\t\t\t\t\t<form action=\"#\">
  361. \t\t\t\t\t\t\t\t\t<ul
  362. \t\t\t\t\t\t\t\t\t\tclass=\"color-list\" id=\"colorList\"><!-- Les couleurs seront chargées dynamiquement -->
  363. \t\t\t\t\t\t\t\t\t</ul>
  364. \t\t\t\t\t\t\t\t</form>
  365. \t\t\t\t\t\t\t</div>
  366. \t\t\t\t\t\t</div>
  367. \t\t\t\t\t\t<div class=\"common-filter\">
  368. \t\t\t\t\t\t\t<div class=\"head\">Taille</div>
  369. \t\t\t\t\t\t\t<div id=\"sizesFilter\">
  370. \t\t\t\t\t\t\t\t<form action=\"#\">
  371. \t\t\t\t\t\t\t\t\t<ul
  372. \t\t\t\t\t\t\t\t\t\tclass=\"size-list\" id=\"sizeList\"><!-- Les tailles seront chargées dynamiquement -->
  373. \t\t\t\t\t\t\t\t\t</ul>
  374. \t\t\t\t\t\t\t\t</form>
  375. \t\t\t\t\t\t\t</div>
  376. \t\t\t\t\t\t</div>
  377. \t\t\t\t\t\t<div class=\"common-filter\">
  378. \t\t\t\t\t\t\t<div class=\"head\">Matériau</div>
  379. \t\t\t\t\t\t\t<div id=\"materialsFilter\">
  380. \t\t\t\t\t\t\t\t<form action=\"#\">
  381. \t\t\t\t\t\t\t\t\t<ul
  382. \t\t\t\t\t\t\t\t\t\tclass=\"material-list\" id=\"materialList\"><!-- Les matériaux seront chargés dynamiquement -->
  383. \t\t\t\t\t\t\t\t\t</ul>
  384. \t\t\t\t\t\t\t\t</form>
  385. \t\t\t\t\t\t\t</div>
  386. \t\t\t\t\t\t</div>
  387. \t\t\t\t\t\t<div class=\"common-filter\">
  388. \t\t\t\t\t\t\t<div class=\"head\">Prix</div>
  389. \t\t\t\t\t\t\t<div class=\"price-range-area\">
  390. \t\t\t\t\t\t\t\t<div id=\"price-range\"></div>
  391. \t\t\t\t\t\t\t\t<div class=\"value-wrapper d-flex\">
  392. \t\t\t\t\t\t\t\t\t<div class=\"price\">Prix:</div>
  393. \t\t\t\t\t\t\t\t\t<span>\$</span>
  394. \t\t\t\t\t\t\t\t\t<div id=\"lower-value\"></div>
  395. \t\t\t\t\t\t\t\t\t<div class=\"to\">to</div>
  396. \t\t\t\t\t\t\t\t\t<span>\$</span>
  397. \t\t\t\t\t\t\t\t\t<div id=\"upper-value\"></div>
  398. \t\t\t\t\t\t\t\t</div>
  399. \t\t\t\t\t\t\t</div>
  400. \t\t\t\t\t\t</div>
  401. \t\t\t\t\t</div>
  402. \t\t\t\t</div>
  403. \t\t\t\t<!-- Fin du collapse sidebar -->
  404. \t\t\t</div>
  405. \t\t\t<div class=\"col-xl-9 col-lg-8 col-md-7\">
  406. \t\t\t<!-- Barre de tri et bouton de filtres -->
  407. \t\t\t<div class=\"row mb-4\">
  408. \t\t\t\t<div
  409. \t\t\t\t\tclass=\"col-md-6 mb-3 mb-md-0\">
  410. \t\t\t\t\t<!-- Bouton pour ouvrir le modal de filtres -->
  411. \t\t\t\t\t<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;\">
  412. \t\t\t\t\t\t<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);\">
  413. \t\t\t\t\t\t\t<i class=\"lnr lnr-filter\" style=\"font-size: 1.3rem; color: white; font-weight: bold;\"></i>
  414. \t\t\t\t\t\t</div>
  415. \t\t\t\t\t\t<span style=\"font-size: 1.1rem; font-weight: 600; color: #333;\">Filtres</span>
  416. \t\t\t\t\t\t<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>
  417. \t\t\t\t\t</button>
  418. \t\t\t\t</div>
  419. \t\t\t\t<div class=\"col-md-6\">
  420. \t\t\t\t\t<select class=\"form-select\" id=\"sortSelect\" onchange=\"applySorting()\">
  421. \t\t\t\t\t\t<option value=\"newest\" ";
  422.         // line 276
  423.         if ((($context["currentSort"] ?? null) == "newest")) {
  424.             yield " selected ";
  425.         }
  426.         yield ">Plus récents</option>
  427. \t\t\t\t\t\t<option value=\"price_asc\" ";
  428.         // line 277
  429.         if ((($context["currentSort"] ?? null) == "price_asc")) {
  430.             yield " selected ";
  431.         }
  432.         yield ">Prix croissant</option>
  433. \t\t\t\t\t\t<option value=\"price_desc\" ";
  434.         // line 278
  435.         if ((($context["currentSort"] ?? null) == "price_desc")) {
  436.             yield " selected ";
  437.         }
  438.         yield ">Prix décroissant</option>
  439. \t\t\t\t\t\t<option value=\"name_asc\" ";
  440.         // line 279
  441.         if ((($context["currentSort"] ?? null) == "name_asc")) {
  442.             yield " selected ";
  443.         }
  444.         yield ">Nom A-Z</option>
  445. \t\t\t\t\t\t<option value=\"name_desc\" ";
  446.         // line 280
  447.         if ((($context["currentSort"] ?? null) == "name_desc")) {
  448.             yield " selected ";
  449.         }
  450.         yield ">Nom Z-A</option>
  451. \t\t\t\t\t\t<option value=\"popular\" ";
  452.         // line 281
  453.         if ((($context["currentSort"] ?? null) == "popular")) {
  454.             yield " selected ";
  455.         }
  456.         yield ">Plus populaires</option>
  457. \t\t\t\t\t</select>
  458. \t\t\t\t</div>
  459. \t\t\t</div>
  460. \t\t\t<!-- Start Best Seller -->
  461. \t\t\t<section class=\"lattest-product-area pb-40 category-list\">
  462. \t\t\t\t<div class=\"row\" id=\"productsContainer\">
  463. \t\t\t\t\t";
  464.         // line 289
  465.         $context['_parent'] = $context;
  466.         $context['_seq'] = CoreExtension::ensureTraversable(($context["products"] ?? null));
  467.         $context['_iterated'] = false;
  468.         foreach ($context['_seq'] as $context["_key"] => $context["product"]) {
  469.             // line 290
  470.             yield "\t\t\t\t\t\t<div class=\"col-lg-4 col-md-6\">
  471. \t\t\t\t\t\t\t<div class=\"single-product\">
  472. \t\t\t\t\t\t\t\t<div class=\"product-image-container mb-2\" onmouseenter=\"showImageNav(";
  473.             // line 292
  474.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse292), "html"nulltrue);
  475.             yield ")\" onmouseleave=\"hideImageNav(";
  476.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse292), "html"nulltrue);
  477.             yield ")\">
  478. \t\t\t\t\t\t\t\t\t<a href=\"";
  479.             // line 293
  480.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_product_show", ["slug" => CoreExtension::getAttribute($this->env$this->source$context["product"], "slug", [], "any"falsefalsefalse293)]), "html"nulltrue);
  481.             yield "\">
  482. \t\t\t\t\t\t\t\t\t\t";
  483.             // line 294
  484.             $context["allImages"] = ((CoreExtension::getAttribute($this->env$this->source$context["product"], "images", [], "any"truetruefalse294)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env$this->source$context["product"], "images", [], "any"falsefalsefalse294), [])) : ([]));
  485.             // line 295
  486.             yield "\t\t\t\t\t\t\t\t\t\t";
  487.             if (CoreExtension::getAttribute($this->env$this->source$context["product"], "variants", [], "any"truetruefalse295)) {
  488.                 // line 296
  489.                 yield "\t\t\t\t\t\t\t\t\t\t\t";
  490.                 $context['_parent'] = $context;
  491.                 $context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env$this->source$context["product"], "variants", [], "any"falsefalsefalse296));
  492.                 foreach ($context['_seq'] as $context["_key"] => $context["variant"]) {
  493.                     // line 297
  494.                     yield "\t\t\t\t\t\t\t\t\t\t\t\t";
  495.                     if ((CoreExtension::getAttribute($this->env$this->source$context["variant"], "isActive", [], "any"falsefalsefalse297) && CoreExtension::getAttribute($this->env$this->source$context["variant"], "images", [], "any"truetruefalse297))) {
  496.                         // line 298
  497.                         yield "\t\t\t\t\t\t\t\t\t\t\t\t\t";
  498.                         $context['_parent'] = $context;
  499.                         $context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env$this->source$context["variant"], "images", [], "any"falsefalsefalse298));
  500.                         foreach ($context['_seq'] as $context["_key"] => $context["variantImg"]) {
  501.                             // line 299
  502.                             yield "\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
  503.                             $context["allImages"] = Twig\Extension\CoreExtension::merge(($context["allImages"] ?? null), [$context["variantImg"]]);
  504.                             // line 300
  505.                             yield "\t\t\t\t\t\t\t\t\t\t\t\t\t";
  506.                         }
  507.                         $_parent $context['_parent'];
  508.                         unset($context['_seq'], $context['_key'], $context['variantImg'], $context['_parent']);
  509.                         $context array_intersect_key($context$_parent) + $_parent;
  510.                         // line 301
  511.                         yield "\t\t\t\t\t\t\t\t\t\t\t\t";
  512.                     }
  513.                     // line 302
  514.                     yield "\t\t\t\t\t\t\t\t\t\t\t";
  515.                 }
  516.                 $_parent $context['_parent'];
  517.                 unset($context['_seq'], $context['_key'], $context['variant'], $context['_parent']);
  518.                 $context array_intersect_key($context$_parent) + $_parent;
  519.                 // line 303
  520.                 yield "\t\t\t\t\t\t\t\t\t\t";
  521.             }
  522.             // line 304
  523.             yield "\t\t\t\t\t\t\t\t\t\t";
  524.             if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), ($context["allImages"] ?? null)) > 0)) {
  525.                 // line 305
  526.                 yield "\t\t\t\t\t\t\t\t\t\t\t<img class=\"img-fluid main-product-img\" id=\"main-img-";
  527.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse305), "html"nulltrue);
  528.                 yield "\" src=\"";
  529.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl((($_v0 = ($context["allImages"] ?? null)) && is_array($_v0) || $_v0 instanceof ArrayAccess ? ($_v0[0] ?? null) : null)), "html"nulltrue);
  530.                 yield "\" alt=\"";
  531.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "name", [], "any"falsefalsefalse305), "html"nulltrue);
  532.                 yield "\">
  533. \t\t\t\t\t\t\t\t\t\t";
  534.             } else {
  535.                 // line 307
  536.                 yield "\t\t\t\t\t\t\t\t\t\t\t<img class=\"img-fluid main-product-img\" id=\"main-img-";
  537.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse307), "html"nulltrue);
  538.                 yield "\" src=\"";
  539.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl("ui/img/product/p1.jpg"), "html"nulltrue);
  540.                 yield "\" alt=\"";
  541.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "name", [], "any"falsefalsefalse307), "html"nulltrue);
  542.                 yield "\">
  543. \t\t\t\t\t\t\t\t\t\t";
  544.             }
  545.             // line 309
  546.             yield "\t\t\t\t\t\t\t\t\t</a>
  547. \t\t\t\t\t\t\t\t\t<!-- Boutons de navigation (visibles au survol) -->
  548. \t\t\t\t\t\t\t\t\t";
  549.             // line 312
  550.             $context["allImages"] = ((CoreExtension::getAttribute($this->env$this->source$context["product"], "images", [], "any"truetruefalse312)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env$this->source$context["product"], "images", [], "any"falsefalsefalse312), [])) : ([]));
  551.             // line 313
  552.             yield "\t\t\t\t\t\t\t\t\t";
  553.             if (CoreExtension::getAttribute($this->env$this->source$context["product"], "variants", [], "any"truetruefalse313)) {
  554.                 // line 314
  555.                 yield "\t\t\t\t\t\t\t\t\t\t";
  556.                 $context['_parent'] = $context;
  557.                 $context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env$this->source$context["product"], "variants", [], "any"falsefalsefalse314));
  558.                 foreach ($context['_seq'] as $context["_key"] => $context["variant"]) {
  559.                     // line 315
  560.                     yield "\t\t\t\t\t\t\t\t\t\t\t";
  561.                     if ((CoreExtension::getAttribute($this->env$this->source$context["variant"], "isActive", [], "any"falsefalsefalse315) && CoreExtension::getAttribute($this->env$this->source$context["variant"], "images", [], "any"truetruefalse315))) {
  562.                         // line 316
  563.                         yield "\t\t\t\t\t\t\t\t\t\t\t\t";
  564.                         $context['_parent'] = $context;
  565.                         $context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env$this->source$context["variant"], "images", [], "any"falsefalsefalse316));
  566.                         foreach ($context['_seq'] as $context["_key"] => $context["variantImg"]) {
  567.                             // line 317
  568.                             yield "\t\t\t\t\t\t\t\t\t\t\t\t\t";
  569.                             $context["allImages"] = Twig\Extension\CoreExtension::merge(($context["allImages"] ?? null), [$context["variantImg"]]);
  570.                             // line 318
  571.                             yield "\t\t\t\t\t\t\t\t\t\t\t\t";
  572.                         }
  573.                         $_parent $context['_parent'];
  574.                         unset($context['_seq'], $context['_key'], $context['variantImg'], $context['_parent']);
  575.                         $context array_intersect_key($context$_parent) + $_parent;
  576.                         // line 319
  577.                         yield "\t\t\t\t\t\t\t\t\t\t\t";
  578.                     }
  579.                     // line 320
  580.                     yield "\t\t\t\t\t\t\t\t\t\t";
  581.                 }
  582.                 $_parent $context['_parent'];
  583.                 unset($context['_seq'], $context['_key'], $context['variant'], $context['_parent']);
  584.                 $context array_intersect_key($context$_parent) + $_parent;
  585.                 // line 321
  586.                 yield "\t\t\t\t\t\t\t\t\t";
  587.             }
  588.             // line 322
  589.             yield "\t\t\t\t\t\t\t\t\t";
  590.             if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), ($context["allImages"] ?? null)) > 1)) {
  591.                 // line 323
  592.                 yield "\t\t\t\t\t\t\t\t\t\t<button class=\"img-nav-btn img-nav-left\" id=\"img-left-";
  593.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse323), "html"nulltrue);
  594.                 yield "\" onclick=\"prevImage(";
  595.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse323), "html"nulltrue);
  596.                 yield ")\" style=\"display: none;\">
  597. \t\t\t\t\t\t\t\t\t\t\t<span class=\"lnr lnr-chevron-left\"></span>
  598. \t\t\t\t\t\t\t\t\t\t</button>
  599. \t\t\t\t\t\t\t\t\t\t<button class=\"img-nav-btn img-nav-right\" id=\"img-right-";
  600.                 // line 326
  601.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse326), "html"nulltrue);
  602.                 yield "\" onclick=\"nextImage(";
  603.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse326), "html"nulltrue);
  604.                 yield ")\" style=\"display: none;\">
  605. \t\t\t\t\t\t\t\t\t\t\t<span class=\"lnr lnr-chevron-right\"></span>
  606. \t\t\t\t\t\t\t\t\t\t</button>
  607. \t\t\t\t\t\t\t\t\t\t<!-- Indicateurs de position -->
  608. \t\t\t\t\t\t\t\t\t\t<div class=\"img-indicators\" id=\"img-indicators-";
  609.                 // line 331
  610.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse331), "html"nulltrue);
  611.                 yield "\" style=\"display: none;\">
  612. \t\t\t\t\t\t\t\t\t\t\t";
  613.                 // line 332
  614.                 $context['_parent'] = $context;
  615.                 $context['_seq'] = CoreExtension::ensureTraversable(($context["allImages"] ?? null));
  616.                 $context['loop'] = [
  617.                   'parent' => $context['_parent'],
  618.                   'index0' => 0,
  619.                   'index'  => 1,
  620.                   'first'  => true,
  621.                 ];
  622.                 if (is_array($context['_seq']) || (is_object($context['_seq']) && $context['_seq'] instanceof \Countable)) {
  623.                     $length count($context['_seq']);
  624.                     $context['loop']['revindex0'] = $length 1;
  625.                     $context['loop']['revindex'] = $length;
  626.                     $context['loop']['length'] = $length;
  627.                     $context['loop']['last'] = === $length;
  628.                 }
  629.                 foreach ($context['_seq'] as $context["_key"] => $context["img"]) {
  630.                     // line 333
  631.                     yield "\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"indicator\" id=\"indicator-";
  632.                     yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse333), "html"nulltrue);
  633.                     yield "-";
  634.                     yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["loop"], "index0", [], "any"falsefalsefalse333), "html"nulltrue);
  635.                     yield "\" onclick=\"showImage(";
  636.                     yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse333), "html"nulltrue);
  637.                     yield ", ";
  638.                     yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["loop"], "index0", [], "any"falsefalsefalse333), "html"nulltrue);
  639.                     yield ")\"></span>
  640. \t\t\t\t\t\t\t\t\t\t\t";
  641.                     ++$context['loop']['index0'];
  642.                     ++$context['loop']['index'];
  643.                     $context['loop']['first'] = false;
  644.                     if (isset($context['loop']['revindex0'], $context['loop']['revindex'])) {
  645.                         --$context['loop']['revindex0'];
  646.                         --$context['loop']['revindex'];
  647.                         $context['loop']['last'] = === $context['loop']['revindex0'];
  648.                     }
  649.                 }
  650.                 $_parent $context['_parent'];
  651.                 unset($context['_seq'], $context['_key'], $context['img'], $context['_parent'], $context['loop']);
  652.                 $context array_intersect_key($context$_parent) + $_parent;
  653.                 // line 335
  654.                 yield "\t\t\t\t\t\t\t\t\t\t</div>
  655. \t\t\t\t\t\t\t\t\t";
  656.             }
  657.             // line 337
  658.             yield "\t\t\t\t\t\t\t\t</div>
  659. \t\t\t\t\t\t\t\t<div class=\"product-details\">
  660. \t\t\t\t\t\t\t\t\t<a href=\"";
  661.             // line 340
  662.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_product_show", ["slug" => CoreExtension::getAttribute($this->env$this->source$context["product"], "slug", [], "any"falsefalsefalse340)]), "html"nulltrue);
  663.             yield "\">
  664. \t\t\t\t\t\t\t\t\t\t<h6>";
  665.             // line 341
  666.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "name", [], "any"falsefalsefalse341), "html"nulltrue);
  667.             yield "</h6>
  668. \t\t\t\t\t\t\t\t\t</a>
  669. \t\t\t\t\t\t\t\t\t<!-- Affichage de la boutique -->
  670. \t\t\t\t\t\t\t\t\t<div class=\"shop-info\">
  671. \t\t\t\t\t\t\t\t\t\t<small class=\"text-muted\">
  672. \t\t\t\t\t\t\t\t\t\t\t<i class=\"lnr lnr-store\"></i>
  673. \t\t\t\t\t\t\t\t\t\t\tVendu par :
  674. \t\t\t\t\t\t\t\t\t\t\t";
  675.             // line 349
  676.             if ((CoreExtension::getAttribute($this->env$this->source$context["product"], "shop", [], "any"truetruefalse349) && CoreExtension::getAttribute($this->env$this->source$context["product"], "shop", [], "any"falsefalsefalse349))) {
  677.                 // line 350
  678.                 yield "\t\t\t\t\t\t\t\t\t\t\t\t<a href=\"";
  679.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_shop_show", ["slug" => CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source$context["product"], "shop", [], "any"falsefalsefalse350), "slug", [], "any"falsefalsefalse350)]), "html"nulltrue);
  680.                 yield "\" class=\"shop-link\">
  681. \t\t\t\t\t\t\t\t\t\t\t\t\t";
  682.                 // line 351
  683.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source$context["product"], "shop", [], "any"falsefalsefalse351), "name", [], "any"falsefalsefalse351), "html"nulltrue);
  684.                 yield "
  685. \t\t\t\t\t\t\t\t\t\t\t\t</a>
  686. \t\t\t\t\t\t\t\t\t\t\t";
  687.             } else {
  688.                 // line 354
  689.                 yield "\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"text-muted\">Boutique inconnue</span>
  690. \t\t\t\t\t\t\t\t\t\t\t";
  691.             }
  692.             // line 356
  693.             yield "\t\t\t\t\t\t\t\t\t\t</small>
  694. \t\t\t\t\t\t\t\t\t</div>
  695. \t\t\t\t\t\t\t\t\t<div class=\"price\">
  696. \t\t\t\t\t\t\t\t\t\t<h6>";
  697.             // line 360
  698.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatNumber(CoreExtension::getAttribute($this->env$this->source$context["product"], "price", [], "any"falsefalsefalse360), 2"."" "), "html"nulltrue);
  699.             yield "
  700. \t\t\t\t\t\t\t\t\t\t\tHTG</h6>
  701. \t\t\t\t\t\t\t\t\t\t";
  702.             // line 362
  703.             if ((($tmp CoreExtension::getAttribute($this->env$this->source$context["product"], "compareAtPrice", [], "any"falsefalsefalse362)) && $tmp instanceof Markup ? (string) $tmp $tmp)) {
  704.                 // line 363
  705.                 yield "\t\t\t\t\t\t\t\t\t\t\t<h6 class=\"l-through\">";
  706.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatNumber(CoreExtension::getAttribute($this->env$this->source$context["product"], "compareAtPrice", [], "any"falsefalsefalse363), 2"."" "), "html"nulltrue);
  707.                 yield "
  708. \t\t\t\t\t\t\t\t\t\t\t\tHTG</h6>
  709. \t\t\t\t\t\t\t\t\t\t";
  710.             }
  711.             // line 366
  712.             yield "\t\t\t\t\t\t\t\t\t</div>
  713. \t\t\t\t\t\t\t\t\t<div class=\"prd-bottom\">
  714. \t\t\t\t\t\t\t\t\t\t<a href=\"javascript:void(0)\" class=\"social-info add-to-cart\" data-product-id=\"";
  715.             // line 368
  716.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse368), "html"nulltrue);
  717.             yield "\" data-qty=\"1\">
  718. \t\t\t\t\t\t\t\t\t\t\t<span class=\"ti-bag\"></span>
  719. \t\t\t\t\t\t\t\t\t\t\t<p class=\"hover-text\">+ panier</p>
  720. \t\t\t\t\t\t\t\t\t\t</a>
  721. \t\t\t\t\t\t\t\t\t\t<a href=\"#\" class=\"social-info wishlist-btn\" data-product-id=\"";
  722.             // line 372
  723.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse372), "html"nulltrue);
  724.             yield "\" ";
  725.             if ((($tmp CoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "user", [], "any"falsefalsefalse372)) && $tmp instanceof Markup ? (string) $tmp $tmp)) {
  726.                 yield " onclick=\"toggleWishlist(";
  727.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse372), "html"nulltrue);
  728.                 yield ", this); return false;\" ";
  729.             } else {
  730.                 yield " onclick=\"alert('Vous devez être connecté pour ajouter aux favoris'); return false;\" ";
  731.             }
  732.             yield ">
  733. \t\t\t\t\t\t\t\t\t\t\t<span class=\"lnr lnr-heart\"></span>
  734. \t\t\t\t\t\t\t\t\t\t\t<p class=\"hover-text\">Favoris</p>
  735. \t\t\t\t\t\t\t\t\t\t</a>
  736. \t\t\t\t\t\t\t\t\t\t<a href=\"#\" class=\"social-info comparison-btn\" data-product-id=\"";
  737.             // line 376
  738.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse376), "html"nulltrue);
  739.             yield "\" ";
  740.             if ((($tmp CoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "user", [], "any"falsefalsefalse376)) && $tmp instanceof Markup ? (string) $tmp $tmp)) {
  741.                 yield " onclick=\"toggleComparison(";
  742.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse376), "html"nulltrue);
  743.                 yield ", this); return false;\" ";
  744.             } else {
  745.                 yield " onclick=\"alert('Vous devez être connecté pour comparer des produits'); return false;\" ";
  746.             }
  747.             yield ">
  748. \t\t\t\t\t\t\t\t\t\t\t<span class=\"lnr lnr-sync\"></span>
  749. \t\t\t\t\t\t\t\t\t\t\t<p class=\"hover-text\">Comparer</p>
  750. \t\t\t\t\t\t\t\t\t\t</a>
  751. \t\t\t\t\t\t\t\t\t\t<a href=\"";
  752.             // line 380
  753.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_product_show", ["slug" => CoreExtension::getAttribute($this->env$this->source$context["product"], "slug", [], "any"falsefalsefalse380)]), "html"nulltrue);
  754.             yield "\" class=\"social-info\">
  755. \t\t\t\t\t\t\t\t\t\t\t<span class=\"lnr lnr-move\"></span>
  756. \t\t\t\t\t\t\t\t\t\t\t<p class=\"hover-text\">Voir plus</p>
  757. \t\t\t\t\t\t\t\t\t\t</a>
  758. \t\t\t\t\t\t\t\t\t</div>
  759. \t\t\t\t\t\t\t\t</div>
  760. \t\t\t\t\t\t\t</div>
  761. \t\t\t\t\t\t</div>
  762. \t\t\t\t\t";
  763.             $context['_iterated'] = true;
  764.         }
  765.         // line 388
  766.         if (!$context['_iterated']) {
  767.             // line 389
  768.             yield "\t\t\t\t\t\t<div class=\"col-12\">
  769. \t\t\t\t\t\t\t<p>Aucun produit disponible.</p>
  770. \t\t\t\t\t\t</div>
  771. \t\t\t\t\t";
  772.         }
  773.         $_parent $context['_parent'];
  774.         unset($context['_seq'], $context['_key'], $context['product'], $context['_parent'], $context['_iterated']);
  775.         $context array_intersect_key($context$_parent) + $_parent;
  776.         // line 393
  777.         yield "\t\t\t\t</div>
  778. \t\t\t\t<!-- Bouton Charger plus de produits -->
  779. \t\t\t\t";
  780.         // line 396
  781.         if ((($context["totalPages"] ?? null) > ($context["currentPage"] ?? null))) {
  782.             // line 397
  783.             yield "\t\t\t\t\t<div class=\"text-center mt-4 mb-4\" id=\"loadMoreContainer\">
  784. \t\t\t\t\t\t<button type=\"button\" class=\"btn btn-primary btn-lg\" id=\"loadMoreBtn\" onclick=\"loadMoreProducts()\">
  785. \t\t\t\t\t\t\t<i class=\"lnr lnr-plus-circle me-2\"></i>Charger plus de produits
  786. \t\t\t\t\t\t</button>
  787. \t\t\t\t\t</div>
  788. \t\t\t\t";
  789.         }
  790.         // line 403
  791.         yield "
  792. \t\t\t\t<!-- Message de fin -->
  793. \t\t\t\t<div id=\"noMoreProducts\" class=\"text-center py-4\" style=\"display: none;\">
  794. \t\t\t\t\t<p class=\"text-muted\">
  795. \t\t\t\t\t\t<i class=\"lnr lnr-checkmark-circle\"></i>
  796. \t\t\t\t\t\tTous les produits ont été chargés</p>
  797. \t\t\t\t</div>
  798. \t\t\t</section>
  799. \t\t\t<!-- End Best Seller -->
  800. \t\t</div>
  801. \t</div>
  802. </div>";
  803.         
  804.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
  805.         
  806.         $__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
  807.         yield from [];
  808.     }
  809.     // line 414
  810.     /**
  811.      * @return iterable<null|scalar|\Stringable>
  812.      */
  813.     public function block_title(array $context, array $blocks = []): iterable
  814.     {
  815.         $macros $this->macros;
  816.         $__internal_5a27a8ba21ca79b61932376b2fa922d2 $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
  817.         $__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block""title"));
  818.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
  819.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block""title"));
  820.         yield "Liste des produits | MaketOu";
  821.         
  822.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
  823.         
  824.         $__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
  825.         yield from [];
  826.     }
  827.     /**
  828.      * @return iterable<null|scalar|\Stringable>
  829.      */
  830.     public function block_stylesheets(array $context, array $blocks = []): iterable
  831.     {
  832.         $macros $this->macros;
  833.         $__internal_5a27a8ba21ca79b61932376b2fa922d2 $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
  834.         $__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block""stylesheets"));
  835.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
  836.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block""stylesheets"));
  837.         // line 415
  838.         yield "<style>
  839. \t/* Styles pour la navigation des images */
  840. \t.product-image-container {
  841. \t\tposition: relative;
  842. \t\toverflow: hidden;
  843. \t\tborder-radius: 8px;
  844. \t}
  845. \t.img-nav-btn {
  846. \t\tposition: absolute;
  847. \t\ttop: 50%;
  848. \t\ttransform: translateY(-50%);
  849. \t\tbackground: rgba(0, 0, 0, 0.6);
  850. \t\tcolor: white;
  851. \t\tborder: none;
  852. \t\twidth: 35px;
  853. \t\theight: 35px;
  854. \t\tborder-radius: 50%;
  855. \t\tdisplay: flex;
  856. \t\talign-items: center;
  857. \t\tjustify-content: center;
  858. \t\tcursor: pointer;
  859. \t\ttransition: all 0.3s ease;
  860. \t\tz-index: 10;
  861. \t}
  862. \t.img-nav-btn:hover {
  863. \t\tbackground: #095ad3;
  864. \t\ttransform: translateY(-50%) scale(1.1);
  865. \t}
  866. \t.img-nav-left {
  867. \t\tleft: 10px;
  868. \t}
  869. \t.img-nav-right {
  870. \t\tright: 10px;
  871. \t}
  872. \t.img-indicators {
  873. \t\tposition: absolute;
  874. \t\tbottom: 10px;
  875. \t\tleft: 50%;
  876. \t\ttransform: translateX(-50%);
  877. \t\tdisplay: flex;
  878. \t\tgap: 5px;
  879. \t\tz-index: 10;
  880. \t}
  881. \t.indicator {
  882. \t\twidth: 8px;
  883. \t\theight: 8px;
  884. \t\tborder-radius: 50%;
  885. \t\tbackground: rgba(255, 255, 255, 0.5);
  886. \t\tcursor: pointer;
  887. \t\tmargin-bottom: 10px;
  888. \t\ttransition: all 0.3s ease;
  889. \t\tborder: 1px solid rgba(255, 255, 255, 0.3);
  890. \t}
  891. \t.indicator.active {
  892. \t\tbackground: #ffa200;
  893. \t\tborder-color: #ffa200;
  894. \t\ttransform: scale(1.2);
  895. \t}
  896. \t.indicator:hover {
  897. \t\tbackground: rgba(255, 255, 255, 0.8);
  898. \t}
  899. \t/* Styles pour l'affichage de la boutique */
  900. \t.shop-info {
  901. \t\tmargin: 8px 0;
  902. \t\tpadding: 5px 0;
  903. \t\tborder-bottom: 1px solid #f0f0f0;
  904. \t}
  905. \t.shop-link {
  906. \t\tcolor: #007bff;
  907. \t\ttext-decoration: none;
  908. \t\tfont-weight: 500;
  909. \t}
  910. \t.shop-link:hover {
  911. \t\tcolor: #ffa200;
  912. \t\ttext-decoration: underline;
  913. \t}
  914. \t/* Animation pour le survol */
  915. \t.product-image-container:hover .main-product-img {
  916. \t\ttransform: scale(1.05);
  917. \t\ttransition: transform 0.3s ease;
  918. \t}
  919. \t.main-product-img {
  920. \t\ttransition: transform 0.3s ease;
  921. \t\twidth: 100%;
  922. \t\theight: 100%;
  923. \t\tobject-fit: cover; /* Recadre l'image pour remplir le conteneur */
  924. \t\tobject-position: center; /* Centre l'image */
  925. \t\tdisplay: block;
  926. \t}
  927. \t/* Styles pour les images des produits associés (Deals de la semaine) */
  928. \t.related-product-img-container {
  929. \t\tposition: relative;
  930. \t\toverflow: hidden;
  931. \t\tborder-radius: 8px;
  932. \t\twidth: 100%;
  933. \t\theight: 200px; /* Hauteur fixe pour les produits associés */
  934. \t\tbackground: #f8f9fa; /* Fond au cas où l'image ne charge pas */
  935. \t}
  936. \t.related-product-img {
  937. \t\twidth: 100%;
  938. \t\theight: 100%;
  939. \t\tobject-fit: cover; /* Recadre l'image pour remplir le conteneur */
  940. \t\tobject-position: center; /* Centre l'image */
  941. \t\tdisplay: block;
  942. \t\ttransition: transform 0.3s ease;
  943. \t}
  944. \t.related-product-img-container:hover .related-product-img {
  945. \t\ttransform: scale(1.05);
  946. \t}
  947. \t/* Styles pour l'image de catégorie dans les deals */
  948. \t.category-banner-container {
  949. \t\tdisplay: block;
  950. \t\tposition: relative;
  951. \t\toverflow: hidden;
  952. \t\tborder-radius: 8px;
  953. \t\twidth: 100%;
  954. \t\theight: 400px; /* Hauteur fixe pour la bannière de catégorie */
  955. \t\tbackground: #f8f9fa; /* Fond au cas où l'image ne charge pas */
  956. \t}
  957. \t.category-banner-img {
  958. \t\twidth: 100%;
  959. \t\theight: 100%;
  960. \t\tobject-fit: cover; /* Recadre l'image pour remplir le conteneur */
  961. \t\tobject-position: center; /* Centre l'image */
  962. \t\tdisplay: block;
  963. \t\ttransition: transform 0.3s ease;
  964. \t}
  965. \t.category-banner-container:hover .category-banner-img {
  966. \t\ttransform: scale(1.05);
  967. \t}
  968. \t/* Styles pour les images de la modal Quick Product View */
  969. \t.quick-view-carousel .item {
  970. \t\twidth: 100%;
  971. \t\theight: 400px; /* Hauteur fixe pour les images du carousel */
  972. \t\tbackground-size: cover;
  973. \t\tbackground-position: center;
  974. \t\tbackground-repeat: no-repeat;
  975. \t\tborder-radius: 8px;
  976. \t}
  977. \t/* Contraindre le conteneur d'image pour maintenir les proportions */
  978. \t.product-image-container {
  979. \t\tposition: relative;
  980. \t\toverflow: hidden;
  981. \t\tborder-radius: 8px;
  982. \t\twidth: 200px;
  983. \t\taspect-ratio: 1 / 1; /* Conteneur carré */
  984. \t\tbackground: #f8f9fa; /* Fond au cas où l'image ne charge pas */
  985. \t}
  986. \t/* Styles pour le bouton de filtre amélioré */
  987. \t.filter-icon-circle {
  988. \t\ttransition: all 0.3s ease;
  989. \t}
  990. \tbutton[data-bs-target=\"#filtersModal\"]:hover .filter-icon-circle {
  991. \t\tbackground: linear-gradient(135deg, #ff8c00 0%, #ff7700 100%) !important;
  992. \t\ttransform: rotate(15deg) scale(1.1);
  993. \t\tbox-shadow: 0 4px 15px rgba(255, 162, 0, 0.5);
  994. \t}
  995. \tbutton[data-bs-target=\"#filtersModal\"]:hover {
  996. \t\ttransform: translateY(-2px);
  997. \t}
  998. \tbutton[data-bs-target=\"#filtersModal\"]:active {
  999. \t\ttransform: translateY(0);
  1000. \t}
  1001. \tbutton[data-bs-target=\"#filtersModal\"]:hover span {
  1002. \t\tcolor: #ffa200 !important;
  1003. \t}
  1004. \t/* Styles pour le modal de filtres */
  1005. \t#filtersModal .modal-body {
  1006. \t\tpadding: 1.5rem;
  1007. \t}
  1008. \t#filtersModal .modal-body::-webkit-scrollbar {
  1009. \t\twidth: 8px;
  1010. \t}
  1011. \t#filtersModal .modal-body::-webkit-scrollbar-track {
  1012. \t\tbackground: #f1f1f1;
  1013. \t\tborder-radius: 4px;
  1014. \t}
  1015. \t#filtersModal .modal-body::-webkit-scrollbar-thumb {
  1016. \t\tbackground: #ffa200;
  1017. \t\tborder-radius: 4px;
  1018. \t}
  1019. \t#filtersModal .modal-body::-webkit-scrollbar-thumb:hover {
  1020. \t\tbackground: #e69100;
  1021. \t}
  1022. \t#filtersModal .modal-footer {
  1023. \t\tbox-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
  1024. \t}
  1025. \t/* Styles pour les filtres avancés */
  1026. \t.weight-range-area {
  1027. \t\tpadding: 15px 0;
  1028. \t}
  1029. \t.weight-inputs {
  1030. \t\tgap: 15px;
  1031. \t\talign-items: center;
  1032. \t}
  1033. \t.weight-input {
  1034. \t\tflex: 1;
  1035. \t}
  1036. \t.weight-input label {
  1037. \t\tdisplay: block;
  1038. \t\tmargin-bottom: 5px;
  1039. \t\tfont-weight: 500;
  1040. \t\tcolor: #333;
  1041. \t}
  1042. \t.weight-input input {
  1043. \t\twidth: 100%;
  1044. \t\tpadding: 8px 12px;
  1045. \t\tborder: 1px solid #ddd;
  1046. \t\tborder-radius: 4px;
  1047. \t\tfont-size: 14px;
  1048. \t}
  1049. \t.weight-input input:focus {
  1050. \t\toutline: none;
  1051. \t\tborder-color: #ffa200;
  1052. \t\tbox-shadow: 0 0 0 2px rgba(255, 162, 0, 0.2);
  1053. \t}
  1054. \t/* Styles pour les filtres dynamiques */
  1055. \t.shop-list,
  1056. \t.color-list,
  1057. \t.size-list,
  1058. \t.material-list,
  1059. \t.condition-list {
  1060. \t\tmax-height: 200px;
  1061. \t\toverflow-y: auto;
  1062. \t\tpadding-right: 5px;
  1063. \t}
  1064. \t.shop-list::-webkit-scrollbar,
  1065. \t.color-list::-webkit-scrollbar,
  1066. \t.size-list::-webkit-scrollbar,
  1067. \t.material-list::-webkit-scrollbar,
  1068. \t.condition-list::-webkit-scrollbar {
  1069. \t\twidth: 6px;
  1070. \t}
  1071. \t.shop-list::-webkit-scrollbar-track,
  1072. \t.color-list::-webkit-scrollbar-track,
  1073. \t.size-list::-webkit-scrollbar-track,
  1074. \t.material-list::-webkit-scrollbar-track,
  1075. \t.condition-list::-webkit-scrollbar-track {
  1076. \t\tbackground: #f1f1f1;
  1077. \t\tborder-radius: 3px;
  1078. \t}
  1079. \t.shop-list::-webkit-scrollbar-thumb,
  1080. \t.color-list::-webkit-scrollbar-thumb,
  1081. \t.size-list::-webkit-scrollbar-thumb,
  1082. \t.material-list::-webkit-scrollbar-thumb,
  1083. \t.condition-list::-webkit-scrollbar-thumb {
  1084. \t\tbackground: #ffa200;
  1085. \t\tborder-radius: 3px;
  1086. \t}
  1087. \t.shop-list::-webkit-scrollbar-thumb:hover,
  1088. \t.color-list::-webkit-scrollbar-thumb:hover,
  1089. \t.size-list::-webkit-scrollbar-thumb:hover,
  1090. \t.material-list::-webkit-scrollbar-thumb:hover,
  1091. \t.condition-list::-webkit-scrollbar-thumb:hover {
  1092. \t\tbackground: #e69100;
  1093. \t}
  1094. \t/* Animation pour les filtres qui apparaissent */
  1095. \t.common-filter {
  1096. \t\ttransition: all 0.3s ease;
  1097. \t}
  1098. \t.common-filter[style*=\"display: none\"] {
  1099. \t\topacity: 0;
  1100. \t\ttransform: translateY(-10px);
  1101. \t}
  1102. \t.common-filter[style*=\"display: block\"] {
  1103. \t\topacity: 1;
  1104. \t\ttransform: translateY(0);
  1105. \t}
  1106. \t/* Styles pour les indicateurs de chargement */
  1107. \t.loading-indicator {
  1108. \t\tdisplay: inline-block;
  1109. \t\twidth: 20px;
  1110. \t\theight: 20px;
  1111. \t\tborder: 3px solid #f3f3f3;
  1112. \t\tborder-top: 3px solid #ffa200;
  1113. \t\tborder-radius: 50%;
  1114. \t\tanimation: spin 1s linear infinite;
  1115. \t}
  1116. \t@keyframes spin {
  1117. \t\t0% {
  1118. \t\t\ttransform: rotate(0deg);
  1119. \t\t}
  1120. \t\t100% {
  1121. \t\t\ttransform: rotate(360deg);
  1122. \t\t}
  1123. \t}
  1124. \t/* Styles pour le loader de chargement infini */
  1125. \t.infinite-loader {
  1126. \t\tdisplay: flex;
  1127. \t\tflex-direction: column;
  1128. \t\talign-items: center;
  1129. \t\tjustify-content: center;
  1130. \t\tpadding: 20px;
  1131. \t}
  1132. \t.loader-spinner {
  1133. \t\twidth: 50px;
  1134. \t\theight: 50px;
  1135. \t\tborder: 4px solid #f3f3f3;
  1136. \t\tborder-top: 4px solid #ffa200;
  1137. \t\tborder-radius: 50%;
  1138. \t\tanimation: spin 1s linear infinite;
  1139. \t}
  1140. \t#infiniteScrollLoader {
  1141. \t\tmin-height: 100px;
  1142. \t}
  1143. \t#noMoreProducts {
  1144. \t\tmin-height: 60px;
  1145. \t\tcolor: #6c757d;
  1146. \t}
  1147. \t#noMoreProducts i {
  1148. \t\tfont-size: 24px;
  1149. \t\tcolor: #28a745;
  1150. \t\tmargin-right: 8px;
  1151. \t}
  1152. \t/* Responsive pour les filtres */
  1153. \t@media(max-width: 768px) {
  1154. \t\t.weight-inputs {
  1155. \t\t\tflex-direction: column;
  1156. \t\t\tgap: 10px;
  1157. \t\t}
  1158. \t\t.weight-input {
  1159. \t\t\twidth: 100%;
  1160. \t\t}
  1161. \t\t.common-filter {
  1162. \t\t\tmargin-bottom: 20px;
  1163. \t\t}
  1164. \t\t.shop-list,
  1165. \t\t.color-list,
  1166. \t\t.size-list,
  1167. \t\t.material-list,
  1168. \t\t.condition-list {
  1169. \t\t\tmax-height: 150px;
  1170. \t\t}
  1171. \t}
  1172. \t/* Bouton toggle sidebar mobile */
  1173. \t.listing-sidebar-toggle {
  1174. \t\tborder-radius: 0.5rem;
  1175. \t\tpadding: 0.75rem;
  1176. \t\tfont-weight: 500;
  1177. \t\tdisplay: flex;
  1178. \t\talign-items: center;
  1179. \t\tjustify-content: center;
  1180. \t}
  1181. \t.listing-sidebar-toggle .toggle-icon {
  1182. \t\ttransition: transform 0.3s ease;
  1183. \t\tdisplay: inline-block;
  1184. \t}
  1185. \t/* État fermé (par défaut) */
  1186. \t.listing-sidebar-toggle[aria-expanded=\"false\"] .toggle-icon,
  1187. \t.listing-sidebar-toggle.collapsed .toggle-icon {
  1188. \t\ttransform: rotate(0deg);
  1189. \t}
  1190. \t/* État ouvert */
  1191. \t.listing-sidebar-toggle[aria-expanded=\"true\"] .toggle-icon,
  1192. \t.listing-sidebar-toggle:not(.collapsed) .toggle-icon {
  1193. \t\ttransform: rotate(180deg);
  1194. \t}
  1195. \t/* Sidebar mobile */
  1196. \t@media(max-width: 767.98px) {
  1197. \t\t#listingSidebarCollapse {
  1198. \t\t\tmargin-bottom: 1rem;
  1199. \t\t}
  1200. \t}
  1201. </style>";
  1202.         
  1203.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
  1204.         
  1205.         $__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
  1206.         yield from [];
  1207.     }
  1208.     // line 839
  1209.     /**
  1210.      * @return iterable<null|scalar|\Stringable>
  1211.      */
  1212.     public function block_javascripts(array $context, array $blocks = []): iterable
  1213.     {
  1214.         $macros $this->macros;
  1215.         $__internal_5a27a8ba21ca79b61932376b2fa922d2 $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
  1216.         $__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block""javascripts"));
  1217.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
  1218.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block""javascripts"));
  1219.         // line 840
  1220.         yield "<script>
  1221. // === Script isolé : chargement de plus de produits (comme les boutiques) ===
  1222. var currentPage = ";
  1223.         // line 842
  1224.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["currentPage"] ?? null), "html"nulltrue);
  1225.         yield ";
  1226. var totalPages = ";
  1227.         // line 843
  1228.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["totalPages"] ?? null), "html"nulltrue);
  1229.         yield ";
  1230. var isLoading = false;
  1231. function loadMoreProducts() {
  1232.     if (isLoading || currentPage >= totalPages) return;
  1233.     isLoading = true;
  1234.     var btn = document.getElementById('loadMoreBtn');
  1235.     var productsContainer = document.getElementById('productsContainer');
  1236.     if (!btn || !productsContainer) {
  1237.         isLoading = false;
  1238.         return;
  1239.     }
  1240.     var originalText = btn.innerHTML;
  1241.     btn.disabled = true;
  1242.     btn.innerHTML = '<span class=\"spinner-border spinner-border-sm me-2\"></span>Chargement...';
  1243.     currentPage++;
  1244.     var url = new URL(window.location.href);
  1245.     url.searchParams.set('page', currentPage);
  1246.     fetch(url.toString(), {
  1247.         headers: { 'X-Requested-With': 'XMLHttpRequest' }
  1248.     })
  1249.     .then(function(response) { return response.json(); })
  1250.     .then(function(data) {
  1251.         if (data.success && data.products) {
  1252.             var tempDiv = document.createElement('div');
  1253.             tempDiv.innerHTML = data.products;
  1254.             var newProducts = tempDiv.querySelectorAll('.col-lg-4, .col-lg-3, [class*=\"col-lg\"]');
  1255.             newProducts.forEach(function(product) {
  1256.                 productsContainer.appendChild(product);
  1257.             });
  1258.             currentPage = data.pagination.currentPage;
  1259.             totalPages = data.pagination.totalPages;
  1260.             // Initialiser les images et events pour les nouveaux produits
  1261.             if (typeof initializeProductImages === 'function') initializeProductImages(tempDiv);
  1262.             if (typeof initializeProductEventListeners === 'function') initializeProductEventListeners(tempDiv);
  1263.             if (currentPage >= totalPages) {
  1264.                 document.getElementById('loadMoreContainer').style.display = 'none';
  1265.             } else {
  1266.                 btn.disabled = false;
  1267.                 btn.innerHTML = originalText;
  1268.             }
  1269.         } else {
  1270.             document.getElementById('loadMoreContainer').style.display = 'none';
  1271.         }
  1272.         isLoading = false;
  1273.     })
  1274.     .catch(function(error) {
  1275.         btn.disabled = false;
  1276.         btn.innerHTML = originalText;
  1277.         isLoading = false;
  1278.     });
  1279. }
  1280. </script>
  1281. <script>
  1282. \t// Variables globales pour la navigation des images
  1283. const productImages = {};
  1284. const currentImageIndex = {};
  1285. // Variables pour le chargement infini (utilise les var globales définies au-dessus)
  1286. let hasMoreProducts = totalPages > currentPage;
  1287. let currentFilters = {
  1288. category: '";
  1289.         // line 915
  1290.         yield (((array_key_exists("currentCategory"$context) &&  !(null === $context["currentCategory"]))) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["currentCategory"], "html"nulltrue)) : (""));
  1291.         yield "',
  1292. brand: '";
  1293.         // line 916
  1294.         yield (((array_key_exists("currentBrand"$context) &&  !(null === $context["currentBrand"]))) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["currentBrand"], "html"nulltrue)) : (""));
  1295.         yield "',
  1296. sort: '";
  1297.         // line 917
  1298.         yield (((array_key_exists("currentSort"$context) &&  !(null === $context["currentSort"]))) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["currentSort"], "html"nulltrue)) : ("newest"));
  1299.         yield "',
  1300. priceMin: '";
  1301.         // line 918
  1302.         yield (((array_key_exists("priceMin"$context) &&  !(null === $context["priceMin"]))) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["priceMin"], "html"nulltrue)) : (""));
  1303.         yield "',
  1304. priceMax: '";
  1305.         // line 919
  1306.         yield (((array_key_exists("priceMax"$context) &&  !(null === $context["priceMax"]))) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["priceMax"], "html"nulltrue)) : (""));
  1307.         yield "'
  1308. };
  1309. // Modal style eBay pour remplacer les alertes - Définir AVANT son utilisation
  1310. function showEbayModal(message, type = 'info') {
  1311. const modalId = 'ebayAlertModal';
  1312. let existingModal = document.getElementById(modalId);
  1313. if (existingModal) {
  1314. existingModal.remove();
  1315. }
  1316. const modal = document.createElement('div');
  1317. modal.id = modalId;
  1318. modal.className = 'modal fade';
  1319. modal.setAttribute('tabindex', '-1');
  1320. modal.setAttribute('aria-labelledby', 'ebayAlertModalLabel');
  1321. modal.setAttribute('aria-hidden', 'true');
  1322. // Définir les couleurs et icônes selon le type
  1323. let iconClass = 'ti-info-alt';
  1324. let iconColor = '#0064D2';
  1325. let title = 'Information';
  1326. switch (type) {
  1327. case 'success': iconClass = 'ti-check-box';
  1328. iconColor = '#0A7C42';
  1329. title = 'Succès';
  1330. break;
  1331. case 'error':
  1332. case 'danger': iconClass = 'ti-alert';
  1333. iconColor = '#D32F2F';
  1334. title = 'Erreur';
  1335. break;
  1336. case 'warning': iconClass = 'ti-alert-triangle';
  1337. iconColor = '#F57C00';
  1338. title = 'Attention';
  1339. break;
  1340. default: iconClass = 'ti-info-alt';
  1341. iconColor = '#0064D2';
  1342. title = 'Information';
  1343. }
  1344. modal.innerHTML = `
  1345. <div class=\"modal-dialog modal-dialog-centered\">
  1346. <div class=\"modal-content\" style=\"border-radius: 8px; border: none; box-shadow: 0 4px 20px rgba(0,0,0,0.15);\">
  1347. <div class=\"modal-header\" style=\"border-bottom: 1px solid #e0e0e0; padding: 20px 24px;\">
  1348. <h5 class=\"modal-title\" id=\"ebayAlertModalLabel\" style=\"font-weight: 600; font-size: 18px; color: #333;\">
  1349. <i class=\"\${iconClass}\" style=\"color: \${iconColor}; margin-right: 8px;\"></i>\${title}
  1350. </h5>
  1351. <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\" style=\"margin: 0;\"></button>
  1352. </div>
  1353. <div class=\"modal-body\" style=\"padding: 24px; text-align: center;\">
  1354. <p style=\"margin: 0; font-size: 16px; color: #333; line-height: 1.5;\">\${message}</p>
  1355. </div>
  1356. <div class=\"modal-footer\" style=\"border-top: 1px solid #e0e0e0; padding: 16px 24px; justify-content: center;\">
  1357. <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;\">
  1358. OK
  1359. </button>
  1360. </div>
  1361. </div>
  1362. </div>
  1363. `;
  1364. document.body.appendChild(modal);
  1365. const bsModal = new bootstrap.Modal(modal);
  1366. bsModal.show();
  1367. // Supprimer le modal du DOM après fermeture
  1368. modal.addEventListener('hidden.bs.modal', function () {
  1369. modal.remove();
  1370. });
  1371. }
  1372. // Rendre la fonction globale
  1373. window.showEbayModal = showEbayModal;
  1374. document.addEventListener('DOMContentLoaded', function () { // Initialisation des données d'images pour chaque produit
  1375. ";
  1376.         // line 996
  1377.         $context['_parent'] = $context;
  1378.         $context['_seq'] = CoreExtension::ensureTraversable(($context["products"] ?? null));
  1379.         foreach ($context['_seq'] as $context["_key"] => $context["product"]) {
  1380.             // line 997
  1381.             $context["allImages"] = ((CoreExtension::getAttribute($this->env$this->source$context["product"], "images", [], "any"truetruefalse997)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env$this->source$context["product"], "images", [], "any"falsefalsefalse997), [])) : ([]));
  1382.             // line 998
  1383.             if (CoreExtension::getAttribute($this->env$this->source$context["product"], "variants", [], "any"truetruefalse998)) {
  1384.                 // line 999
  1385.                 yield "\t";
  1386.                 $context['_parent'] = $context;
  1387.                 $context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env$this->source$context["product"], "variants", [], "any"falsefalsefalse999));
  1388.                 foreach ($context['_seq'] as $context["_key"] => $context["variant"]) {
  1389.                     // line 1000
  1390.                     yield "\t\t";
  1391.                     if ((CoreExtension::getAttribute($this->env$this->source$context["variant"], "isActive", [], "any"falsefalsefalse1000) && CoreExtension::getAttribute($this->env$this->source$context["variant"], "images", [], "any"truetruefalse1000))) {
  1392.                         // line 1001
  1393.                         yield "\t\t\t";
  1394.                         $context['_parent'] = $context;
  1395.                         $context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env$this->source$context["variant"], "images", [], "any"falsefalsefalse1001));
  1396.                         foreach ($context['_seq'] as $context["_key"] => $context["variantImg"]) {
  1397.                             // line 1002
  1398.                             yield "\t\t\t\t";
  1399.                             $context["allImages"] = Twig\Extension\CoreExtension::merge(($context["allImages"] ?? null), [$context["variantImg"]]);
  1400.                             // line 1003
  1401.                             yield "\t\t\t";
  1402.                         }
  1403.                         $_parent $context['_parent'];
  1404.                         unset($context['_seq'], $context['_key'], $context['variantImg'], $context['_parent']);
  1405.                         $context array_intersect_key($context$_parent) + $_parent;
  1406.                         // line 1004
  1407.                         yield "\t\t";
  1408.                     }
  1409.                     // line 1005
  1410.                     yield "\t";
  1411.                 }
  1412.                 $_parent $context['_parent'];
  1413.                 unset($context['_seq'], $context['_key'], $context['variant'], $context['_parent']);
  1414.                 $context array_intersect_key($context$_parent) + $_parent;
  1415.             }
  1416.             // line 1007
  1417.             if ((Twig\Extension\CoreExtension::length($this->env->getCharset(), ($context["allImages"] ?? null)) > 0)) {
  1418.                 yield "productImages[";
  1419.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse1007), "html"nulltrue);
  1420.                 yield "] = [";
  1421.                 $context['_parent'] = $context;
  1422.                 $context['_seq'] = CoreExtension::ensureTraversable(($context["allImages"] ?? null));
  1423.                 $context['loop'] = [
  1424.                   'parent' => $context['_parent'],
  1425.                   'index0' => 0,
  1426.                   'index'  => 1,
  1427.                   'first'  => true,
  1428.                 ];
  1429.                 if (is_array($context['_seq']) || (is_object($context['_seq']) && $context['_seq'] instanceof \Countable)) {
  1430.                     $length count($context['_seq']);
  1431.                     $context['loop']['revindex0'] = $length 1;
  1432.                     $context['loop']['revindex'] = $length;
  1433.                     $context['loop']['length'] = $length;
  1434.                     $context['loop']['last'] = === $length;
  1435.                 }
  1436.                 foreach ($context['_seq'] as $context["_key"] => $context["img"]) {
  1437.                     yield "'";
  1438.                     yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl($context["img"]), "html"nulltrue);
  1439.                     yield "'";
  1440.                     if ((($tmp =  !CoreExtension::getAttribute($this->env$this->source$context["loop"], "last", [], "any"falsefalsefalse1007)) && $tmp instanceof Markup ? (string) $tmp $tmp)) {
  1441.                         yield ",";
  1442.                     }
  1443.                     ++$context['loop']['index0'];
  1444.                     ++$context['loop']['index'];
  1445.                     $context['loop']['first'] = false;
  1446.                     if (isset($context['loop']['revindex0'], $context['loop']['revindex'])) {
  1447.                         --$context['loop']['revindex0'];
  1448.                         --$context['loop']['revindex'];
  1449.                         $context['loop']['last'] = === $context['loop']['revindex0'];
  1450.                     }
  1451.                 }
  1452.                 $_parent $context['_parent'];
  1453.                 unset($context['_seq'], $context['_key'], $context['img'], $context['_parent'], $context['loop']);
  1454.                 $context array_intersect_key($context$_parent) + $_parent;
  1455.                 yield "];
  1456. currentImageIndex[";
  1457.                 // line 1008
  1458.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse1008), "html"nulltrue);
  1459.                 yield "] = 0;
  1460. console.log('Initialized product ";
  1461.                 // line 1009
  1462.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse1009), "html"nulltrue);
  1463.                 yield " with images:', productImages[";
  1464.                 yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["product"], "id", [], "any"falsefalsefalse1009), "html"nulltrue);
  1465.                 yield "]);";
  1466.             }
  1467.         }
  1468.         $_parent $context['_parent'];
  1469.         unset($context['_seq'], $context['_key'], $context['product'], $context['_parent']);
  1470.         $context array_intersect_key($context$_parent) + $_parent;
  1471.         // line 1010
  1472.         yield "
  1473. // Gestion du panier
  1474. const cartButtons = document.querySelectorAll('.add-to-cart');
  1475. cartButtons.forEach(button => {
  1476. button.addEventListener('click', function () {
  1477. const productId = this.getAttribute('data-product-id');
  1478. const qty = this.getAttribute('data-qty');
  1479. fetch('";
  1480.         // line 1018
  1481.         yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_cart_add");
  1482.         yield "', {
  1483. method: 'POST',
  1484. headers: {
  1485. 'Content-Type': 'application/x-www-form-urlencoded'
  1486. },
  1487. body: 'productId=' + productId + '&qty=' + qty
  1488. }).then(response => response.json()).then(data => {
  1489. if (data.ok) {
  1490. showEbayModal('Produit ajouté au panier !', 'success');
  1491. // Mettre à jour le compteur du panier si présent
  1492. const cartBadge = document.querySelector('.cart-badge');
  1493. if (cartBadge) {
  1494. cartBadge.textContent = data.totalQty;
  1495. }
  1496. } else {
  1497. showEbayModal(data.message || 'Erreur lors de l\\'ajout au panier', 'error');
  1498. }
  1499. }).catch(error => {
  1500. console.error('Erreur:', error);
  1501. showEbayModal('Erreur lors de l\\'ajout au panier', 'error');
  1502. });
  1503. });
  1504. });
  1505. });
  1506. // Fonctions pour la navigation des images
  1507. window.showImageNav = function(productId) {
  1508. const leftBtn = document.getElementById('img-left-' + productId);
  1509. const rightBtn = document.getElementById('img-right-' + productId);
  1510. const indicators = document.getElementById('img-indicators-' + productId);
  1511. if (leftBtn) {
  1512. leftBtn.style.display = 'flex';
  1513. }
  1514. if (rightBtn) {
  1515. rightBtn.style.display = 'flex';
  1516. }
  1517. if (indicators) {
  1518. indicators.style.display = 'flex';
  1519. }
  1520. };
  1521. window.hideImageNav = function(productId) {
  1522. const leftBtn = document.getElementById('img-left-' + productId);
  1523. const rightBtn = document.getElementById('img-right-' + productId);
  1524. const indicators = document.getElementById('img-indicators-' + productId);
  1525. if (leftBtn) {
  1526. leftBtn.style.display = 'none';
  1527. }
  1528. if (rightBtn) {
  1529. rightBtn.style.display = 'none';
  1530. }
  1531. if (indicators) {
  1532. indicators.style.display = 'none';
  1533. }
  1534. };
  1535. window.showImage = function(productId, imageIndex) {
  1536. if (! productImages[productId] || ! productImages[productId][imageIndex]) {
  1537. console.log('Image not found for product', productId, 'index', imageIndex);
  1538. return;
  1539. }
  1540. const img = document.getElementById('main-img-' + productId);
  1541. if (img) {
  1542. console.log('Changing image to:', productImages[productId][imageIndex]);
  1543. // Force le rechargement de l'image
  1544. img.style.opacity = '0.5';
  1545. setTimeout(() => {
  1546. img.src = productImages[productId][imageIndex];
  1547. img.style.opacity = '1';
  1548. currentImageIndex[productId] = imageIndex;
  1549. updateIndicators(productId);
  1550. }, 100);
  1551. } else {
  1552. console.log('Image element not found:', 'main-img-' + productId);
  1553. }
  1554. };
  1555. window.nextImage = function(productId) {
  1556. if (! productImages[productId]) {
  1557. console.log('No images for product', productId);
  1558. return;
  1559. }
  1560. const nextIndex = (currentImageIndex[productId] + 1) % productImages[productId].length;
  1561. showImage(productId, nextIndex);
  1562. };
  1563. window.prevImage = function(productId) {
  1564. if (! productImages[productId]) {
  1565. console.log('No images for product', productId);
  1566. return;
  1567. }
  1568. const prevIndex = currentImageIndex[productId] === 0 ? productImages[productId].length - 1 : currentImageIndex[productId] - 1;
  1569. showImage(productId, prevIndex);
  1570. };
  1571. function updateIndicators(productId) {
  1572. const indicators = document.querySelectorAll('#img-indicators-' + productId + ' .indicator');
  1573. indicators.forEach((indicator, index) => {
  1574. indicator.classList.toggle('active', index === currentImageIndex[productId]);
  1575. });
  1576. }
  1577. // Fonction pour tracker la vue d'un produit
  1578. function trackProductView(productId) {
  1579. fetch (`/api/track-product-view/\${productId}`, {
  1580. method: 'POST',
  1581. headers: {
  1582. 'Content-Type': 'application/json',
  1583. 'X-Requested-With': 'XMLHttpRequest'
  1584. }
  1585. }).then(response => response.json()).then(data => {
  1586. if (data.success) {
  1587. console.log (`Vue enregistrée pour le produit \${productId}:`, data.viewCount);
  1588. }
  1589. }).catch(error => {
  1590. console.error('Erreur lors du tracking:', error);
  1591. });
  1592. }
  1593. // Fonction pour tracker la vue d'une boutique
  1594. function trackShopView(shopId) {
  1595. fetch (`/api/track-shop-view/\${shopId}`, {
  1596. method: 'POST',
  1597. headers: {
  1598. 'Content-Type': 'application/json',
  1599. 'X-Requested-With': 'XMLHttpRequest'
  1600. }
  1601. }).then(response => response.json()).then(data => {
  1602. if (data.success) {
  1603. console.log (`Vue enregistrée pour la boutique \${shopId}:`, data.viewCount);
  1604. }
  1605. }).catch(error => {
  1606. console.error('Erreur lors du tracking:', error);
  1607. });
  1608. }
  1609. // Ajouter le tracking aux événements existants
  1610. document.addEventListener('DOMContentLoaded', function () { // Tracking au survol des produits (avec délai)
  1611. const productCards = document.querySelectorAll('.single_product_item');
  1612. productCards.forEach(card => {
  1613. const productId = card.dataset.productId;
  1614. const shopId = card.dataset.shopId;
  1615. if (productId) {
  1616. let hoverTimeout;
  1617. card.addEventListener('mouseenter', () => { // Tracker la vue du produit après 2 secondes de survol
  1618. hoverTimeout = setTimeout(() => {
  1619. trackProductView(productId);
  1620. }, 2000);
  1621. });
  1622. card.addEventListener('mouseleave', () => {
  1623. clearTimeout(hoverTimeout);
  1624. });
  1625. // Tracking au clic sur le lien du produit
  1626. const productLink = card.querySelector('.product_img a');
  1627. if (productLink) {
  1628. productLink.addEventListener('click', () => {
  1629. trackProductView(productId);
  1630. });
  1631. }
  1632. }
  1633. // Tracking au clic sur le lien de la boutique
  1634. if (shopId) {
  1635. const shopLink = card.querySelector('.shop-link');
  1636. if (shopLink) {
  1637. shopLink.addEventListener('click', () => {
  1638. trackShopView(shopId);
  1639. });
  1640. }
  1641. }
  1642. });
  1643. });
  1644. </script>
  1645. <script>
  1646. \t// Système de filtrage AJAX avancé
  1647. // currentFilters est déjà déclaré plus haut, on met juste à jour les valeurs
  1648. if (typeof currentFilters !== 'undefined') { // Mettre à jour les valeurs existantes
  1649. currentFilters.category = '";
  1650.         // line 1211
  1651.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["currentCategory"] ?? null), "html"nulltrue);
  1652.         yield "';
  1653. currentFilters.brand = '";
  1654.         // line 1212
  1655.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["currentBrand"] ?? null), "html"nulltrue);
  1656.         yield "';
  1657. currentFilters.condition = '";
  1658.         // line 1213
  1659.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["currentCondition"] ?? null), "html"nulltrue);
  1660.         yield "';
  1661. currentFilters.sort = '";
  1662.         // line 1214
  1663.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["currentSort"] ?? null), "html"nulltrue);
  1664.         yield "';
  1665. currentFilters.priceMin = '";
  1666.         // line 1215
  1667.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["priceMin"] ?? null), "html"nulltrue);
  1668.         yield "';
  1669. currentFilters.priceMax = '";
  1670.         // line 1216
  1671.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["priceMax"] ?? null), "html"nulltrue);
  1672.         yield "';
  1673. currentFilters.page = ";
  1674.         // line 1217
  1675.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["currentPage"] ?? null), "html"nulltrue);
  1676.         yield ";
  1677. currentFilters.q = currentFilters.q || '";
  1678.         // line 1218
  1679.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1218), "query", [], "any"falsefalsefalse1218), "get", ["q"""], "method"falsefalsefalse1218), "html"nulltrue);
  1680.         yield "';
  1681. // Ajouter les nouveaux filtres depuis l'URL s'ils n'existent pas
  1682. if (! currentFilters.shop) 
  1683. currentFilters.shop = '";
  1684.         // line 1221
  1685.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1221), "query", [], "any"falsefalsefalse1221), "get", ["shop"""], "method"falsefalsefalse1221), "html"nulltrue);
  1686.         yield "';
  1687. if (! currentFilters.featured) 
  1688. currentFilters.featured = '";
  1689.         // line 1224
  1690.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1224), "query", [], "any"falsefalsefalse1224), "get", ["featured"""], "method"falsefalsefalse1224), "html"nulltrue);
  1691.         yield "';
  1692. if (! currentFilters.digital) 
  1693. currentFilters.digital = '";
  1694.         // line 1227
  1695.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1227), "query", [], "any"falsefalsefalse1227), "get", ["digital"""], "method"falsefalsefalse1227), "html"nulltrue);
  1696.         yield "';
  1697. if (! currentFilters.stockStatus) 
  1698. currentFilters.stockStatus = '";
  1699.         // line 1230
  1700.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1230), "query", [], "any"falsefalsefalse1230), "get", ["stock_status"""], "method"falsefalsefalse1230), "html"nulltrue);
  1701.         yield "';
  1702. if (! currentFilters.ratingMin) 
  1703. currentFilters.ratingMin = '";
  1704.         // line 1233
  1705.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1233), "query", [], "any"falsefalsefalse1233), "get", ["rating_min"""], "method"falsefalsefalse1233), "html"nulltrue);
  1706.         yield "';
  1707. if (! currentFilters.rating) 
  1708. currentFilters.rating = '";
  1709.         // line 1236
  1710.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1236), "query", [], "any"falsefalsefalse1236), "get", ["rating_min"""], "method"falsefalsefalse1236), "html"nulltrue);
  1711.         yield "';
  1712. if (! currentFilters.weightMin) 
  1713. currentFilters.weightMin = '";
  1714.         // line 1239
  1715.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1239), "query", [], "any"falsefalsefalse1239), "get", ["weight_min"""], "method"falsefalsefalse1239), "html"nulltrue);
  1716.         yield "';
  1717. if (! currentFilters.weightMax) 
  1718. currentFilters.weightMax = '";
  1719.         // line 1242
  1720.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1242), "query", [], "any"falsefalsefalse1242), "get", ["weight_max"""], "method"falsefalsefalse1242), "html"nulltrue);
  1721.         yield "';
  1722. if (! currentFilters.color) 
  1723. currentFilters.color = '";
  1724.         // line 1245
  1725.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1245), "query", [], "any"falsefalsefalse1245), "get", ["color"""], "method"falsefalsefalse1245), "html"nulltrue);
  1726.         yield "';
  1727. if (! currentFilters.size) 
  1728. currentFilters.size = '";
  1729.         // line 1248
  1730.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1248), "query", [], "any"falsefalsefalse1248), "get", ["size"""], "method"falsefalsefalse1248), "html"nulltrue);
  1731.         yield "';
  1732. if (! currentFilters.material) 
  1733. currentFilters.material = '";
  1734.         // line 1251
  1735.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1251), "query", [], "any"falsefalsefalse1251), "get", ["material"""], "method"falsefalsefalse1251), "html"nulltrue);
  1736.         yield "';
  1737. if (! currentFilters.condition) 
  1738. currentFilters.condition = '";
  1739.         // line 1254
  1740.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1254), "query", [], "any"falsefalsefalse1254), "get", ["condition"""], "method"falsefalsefalse1254), "html"nulltrue);
  1741.         yield "';
  1742. if (! currentFilters.availability) 
  1743. currentFilters.availability = '";
  1744.         // line 1257
  1745.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1257), "query", [], "any"falsefalsefalse1257), "get", ["availability"""], "method"falsefalsefalse1257), "html"nulltrue);
  1746.         yield "';
  1747. } else { // Si currentFilters n'existe pas, le créer avec les valeurs de l'URL
  1748. let currentFilters = {
  1749. category: '";
  1750.         // line 1261
  1751.         yield (((array_key_exists("currentCategory"$context) &&  !(null === $context["currentCategory"]))) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["currentCategory"], "html"nulltrue)) : (""));
  1752.         yield "',
  1753. brand: '";
  1754.         // line 1262
  1755.         yield (((array_key_exists("currentBrand"$context) &&  !(null === $context["currentBrand"]))) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["currentBrand"], "html"nulltrue)) : (""));
  1756.         yield "',
  1757. condition: '";
  1758.         // line 1263
  1759.         yield (((array_key_exists("currentCondition"$context) &&  !(null === $context["currentCondition"]))) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["currentCondition"], "html"nulltrue)) : (""));
  1760.         yield "',
  1761. sort: '";
  1762.         // line 1264
  1763.         yield (((array_key_exists("currentSort"$context) &&  !(null === $context["currentSort"]))) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($context["currentSort"], "html"nulltrue)) : ("newest"));
  1764.         yield "',
  1765. priceMin: '";
  1766.         // line 1265
  1767.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1265), "query", [], "any"falsefalsefalse1265), "get", ["price_min"""], "method"falsefalsefalse1265), "html"nulltrue);
  1768.         yield "',
  1769. priceMax: '";
  1770.         // line 1266
  1771.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1266), "query", [], "any"falsefalsefalse1266), "get", ["price_max"""], "method"falsefalsefalse1266), "html"nulltrue);
  1772.         yield "',
  1773. page: ";
  1774.         // line 1267
  1775.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["currentPage"] ?? null), "html"nulltrue);
  1776.         yield ",
  1777. q: '";
  1778.         // line 1268
  1779.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1268), "query", [], "any"falsefalsefalse1268), "get", ["q"""], "method"falsefalsefalse1268), "html"nulltrue);
  1780.         yield "',
  1781. // Nouveaux filtres depuis l'URL
  1782. shop: '";
  1783.         // line 1270
  1784.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1270), "query", [], "any"falsefalsefalse1270), "get", ["shop"""], "method"falsefalsefalse1270), "html"nulltrue);
  1785.         yield "',
  1786. featured: '";
  1787.         // line 1271
  1788.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1271), "query", [], "any"falsefalsefalse1271), "get", ["featured"""], "method"falsefalsefalse1271), "html"nulltrue);
  1789.         yield "',
  1790. digital: '";
  1791.         // line 1272
  1792.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1272), "query", [], "any"falsefalsefalse1272), "get", ["digital"""], "method"falsefalsefalse1272), "html"nulltrue);
  1793.         yield "',
  1794. stockStatus: '";
  1795.         // line 1273
  1796.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1273), "query", [], "any"falsefalsefalse1273), "get", ["stock_status"""], "method"falsefalsefalse1273), "html"nulltrue);
  1797.         yield "',
  1798. ratingMin: '";
  1799.         // line 1274
  1800.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1274), "query", [], "any"falsefalsefalse1274), "get", ["rating_min"""], "method"falsefalsefalse1274), "html"nulltrue);
  1801.         yield "',
  1802. rating: '";
  1803.         // line 1275
  1804.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1275), "query", [], "any"falsefalsefalse1275), "get", ["rating_min"""], "method"falsefalsefalse1275), "html"nulltrue);
  1805.         yield "',
  1806. weightMin: '";
  1807.         // line 1276
  1808.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1276), "query", [], "any"falsefalsefalse1276), "get", ["weight_min"""], "method"falsefalsefalse1276), "html"nulltrue);
  1809.         yield "',
  1810. weightMax: '";
  1811.         // line 1277
  1812.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1277), "query", [], "any"falsefalsefalse1277), "get", ["weight_max"""], "method"falsefalsefalse1277), "html"nulltrue);
  1813.         yield "',
  1814. color: '";
  1815.         // line 1278
  1816.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1278), "query", [], "any"falsefalsefalse1278), "get", ["color"""], "method"falsefalsefalse1278), "html"nulltrue);
  1817.         yield "',
  1818. size: '";
  1819.         // line 1279
  1820.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1279), "query", [], "any"falsefalsefalse1279), "get", ["size"""], "method"falsefalsefalse1279), "html"nulltrue);
  1821.         yield "',
  1822. material: '";
  1823.         // line 1280
  1824.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1280), "query", [], "any"falsefalsefalse1280), "get", ["material"""], "method"falsefalsefalse1280), "html"nulltrue);
  1825.         yield "',
  1826. condition: '";
  1827.         // line 1281
  1828.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1281), "query", [], "any"falsefalsefalse1281), "get", ["condition"""], "method"falsefalsefalse1281), "html"nulltrue);
  1829.         yield "',
  1830. availability: '";
  1831.         // line 1282
  1832.         yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->sourceCoreExtension::getAttribute($this->env$this->source, ($context["app"] ?? null), "request", [], "any"falsefalsefalse1282), "query", [], "any"falsefalsefalse1282), "get", ["availability"""], "method"falsefalsefalse1282), "html"nulltrue);
  1833.         yield "'
  1834. };
  1835. }
  1836. // Fonction pour appliquer les filtres
  1837. function applyFilters() {
  1838. const url = new URL('";
  1839.         // line 1288
  1840.         yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_api_products_filter");
  1841.         yield "', window.location.origin);
  1842. // Ajouter les paramètres
  1843. Object.keys(currentFilters).forEach(key => {
  1844. if (currentFilters[key] && currentFilters[key] !== '') { // Mapper les clés pour l'API
  1845. let apiKey = key;
  1846. if (key === 'priceMin') {
  1847. apiKey = 'price_min';
  1848. } else if (key === 'priceMax') {
  1849. apiKey = 'price_max';
  1850. } else if (key === 'ratingMin') {
  1851. apiKey = 'rating_min';
  1852. } else if (key === 'weightMin') {
  1853. apiKey = 'weight_min';
  1854. } else if (key === 'weightMax') {
  1855. apiKey = 'weight_max';
  1856. } else if (key === 'stockStatus') {
  1857. apiKey = 'stock_status';
  1858. }
  1859. url.searchParams.set(apiKey, currentFilters[key]);
  1860. }
  1861. });
  1862. // Afficher le loader
  1863. document.getElementById('productsContainer').innerHTML = '<div class=\"col-12 text-center\"><i class=\"fa fa-spinner fa-spin\"></i> Chargement...</div>';
  1864. // Faire la requête AJAX
  1865. fetch(url).then(response => response.json()).then(data => {
  1866. if (data.success) { // Mettre à jour les produits
  1867. document.getElementById('productsContainer').innerHTML = data.products;
  1868. // Mettre à jour la pagination
  1869. currentPage = data.pagination.currentPage;
  1870. totalPages = data.pagination.totalPages;
  1871. hasMoreProducts = currentPage < totalPages;
  1872. // Préserver l'état des filtres statiques avant la mise à jour
  1873. const preservedFilters = {
  1874. featured: currentFilters.featured,
  1875. digital: currentFilters.digital,
  1876. availability: currentFilters.availability,
  1877. rating: currentFilters.rating || currentFilters.ratingMin
  1878. };
  1879. // Mettre à jour les marques disponibles (toujours afficher toutes)
  1880. updateAvailableBrands(data.availableBrands || []);
  1881. // Mettre à jour les conditions disponibles (toujours afficher toutes)
  1882. updateAvailableConditions(data.availableConditions || []);
  1883. // Mettre à jour les boutiques disponibles (toujours afficher toutes)
  1884. updateAvailableShops(data.availableShops || []);
  1885. // Mettre à jour les attributs disponibles (toujours afficher tous)
  1886. updateAvailableAttributes(data.availableAttributes || {});
  1887. // Restaurer l'état des filtres statiques après la mise à jour
  1888. restoreStaticFiltersState(preservedFilters);
  1889. // Réinitialiser les filtres depuis currentFilters pour s'assurer qu'ils sont tous cochés
  1890. if (typeof initializeFiltersFromURL === 'function') {
  1891. initializeFiltersFromURL();
  1892. }
  1893. // Mettre à jour l'URL
  1894. updateURL();
  1895. // Mettre à jour l'affichage du bouton
  1896. updateLoadMoreButton();
  1897. // Réinitialiser les event listeners pour les nouveaux produits
  1898. const tempDiv = document.createElement('div');
  1899. tempDiv.innerHTML = data.products;
  1900. initializeProductEventListeners(tempDiv);
  1901. initializeProductImages(tempDiv);
  1902. // Mettre à jour les filtres actifs
  1903. if (typeof updateActiveFilters === 'function') {
  1904. updateActiveFilters();
  1905. }
  1906. }
  1907. }).catch(error => {
  1908. console.error('Erreur lors du filtrage:', error);
  1909. document.getElementById('productsContainer').innerHTML = '<div class=\"col-12 text-center text-danger\">Erreur lors du chargement des produits</div>';
  1910. });
  1911. }
  1912. // Exposer la fonction globalement
  1913. window.applyFilters = applyFilters;
  1914. // Fonction pour appliquer le tri
  1915. function applySorting() {
  1916. currentFilters.sort = document.getElementById('sortSelect').value;
  1917. currentFilters.page = 1;
  1918. applyFilters();
  1919. }
  1920. // Fonction pour appliquer le filtre de prix
  1921. function applyPriceFilter() {
  1922. currentFilters.priceMin = document.getElementById('priceMin').value;
  1923. currentFilters.priceMax = document.getElementById('priceMax').value;
  1924. currentFilters.page = 1;
  1925. applyFilters();
  1926. }
  1927. // Fonction pour appliquer le filtre de poids
  1928. function applyWeightFilter() {
  1929. currentFilters.weightMin = document.getElementById('weightMin').value;
  1930. currentFilters.weightMax = document.getElementById('weightMax').value;
  1931. currentFilters.page = 1;
  1932. applyFilters();
  1933. }
  1934. // Fonction pour changer de page
  1935. function changePage(page) {
  1936. currentFilters.page = page;
  1937. applyFilters();
  1938. }
  1939. // Fonction pour mettre à jour les marques disponibles
  1940. function updateAvailableBrands(brands) {
  1941. const brandsFilter = document.getElementById('brandsFilter');
  1942. const brandList = document.getElementById('brandList');
  1943. const currentBrandValue = currentFilters.brand || '';
  1944. // Toujours afficher le filtre des marques
  1945. brandsFilter.style.display = 'block';
  1946. // Si pas de marques disponibles, garder les marques existantes ou afficher un message
  1947. if (brands.length === 0) { // Ne pas réinitialiser si la liste existe déjà et a du contenu
  1948. if (brandList && brandList.children.length > 0) { // Juste mettre à jour les états cochés
  1949. const existingRadios = brandList.querySelectorAll('input[name=\"brand\"]');
  1950. existingRadios.forEach(radio => {
  1951. if (currentBrandValue && radio.value === currentBrandValue) {
  1952. radio.checked = true;
  1953. } else if (! currentBrandValue && radio.id === 'all-brands') {
  1954. radio.checked = true;
  1955. } else {
  1956. radio.checked = false;
  1957. }
  1958. });
  1959. return; // Garder les marques existantes
  1960. }
  1961. // Sinon, afficher un message
  1962. brandList.innerHTML = '<li class=\"filter-list\"><span class=\"text-muted\">Aucune marque disponible</span></li>';
  1963. return;
  1964. }
  1965. let html = '';
  1966. const isAllSelected = ! currentBrandValue || currentBrandValue === '';
  1967. html += `<li class=\"filter-list\"><input class=\"pixel-radio\" type=\"radio\" id=\"all-brands\" name=\"brand\" \${
  1968. isAllSelected ? 'checked' : ''
  1969. }><label for=\"all-brands\">Toutes les marques</label></li>`;
  1970. brands.forEach(brand => {
  1971. const isSelected = currentBrandValue === brand.slug;
  1972. html += `<li class=\"filter-list\">
  1973.                 <input class=\"pixel-radio\" type=\"radio\" id=\"brand-\${
  1974. brand.id
  1975. }\" name=\"brand\" value=\"\${
  1976. brand.slug
  1977. }\" \${
  1978. isSelected ? 'checked' : ''
  1979. }>
  1980.                 <label for=\"brand-\${
  1981. brand.id
  1982. }\">\${
  1983. brand.name
  1984. }<span>(\${
  1985. brand.productCount
  1986. })</span></label>
  1987.             </li>`;
  1988. });
  1989. brandList.innerHTML = html;
  1990. // Ajouter les event listeners
  1991. brandList.querySelectorAll('input[name=\"brand\"]').forEach(radio => {
  1992. radio.addEventListener('change', function () {
  1993. if (this.id === 'all-brands') {
  1994. currentFilters.brand = '';
  1995. } else {
  1996. currentFilters.brand = this.value;
  1997. } currentFilters.page = 1;
  1998. applyFilters();
  1999. });
  2000. });
  2001. }
  2002. // Fonction pour mettre à jour les conditions disponibles
  2003. function updateAvailableConditions(conditions) {
  2004. const conditionList = document.getElementById('conditionList');
  2005. const currentConditionValue = currentFilters.condition || '';
  2006. // Toujours garder le contenu statique et juste mettre à jour les états cochés
  2007. if (conditionList && conditionList.children.length > 0) {
  2008. const existingRadios = conditionList.querySelectorAll('input[name=\"condition\"]');
  2009. existingRadios.forEach(radio => {
  2010. if (currentConditionValue && radio.value === currentConditionValue) {
  2011. radio.checked = true;
  2012. } else if (! currentConditionValue && radio.id === 'all-conditions') {
  2013. radio.checked = true;
  2014. } else {
  2015. radio.checked = false;
  2016. }
  2017. });
  2018. return; // Garder les conditions existantes
  2019. }
  2020. // Si pas de contenu statique, générer dynamiquement
  2021. if (conditions.length === 0) {
  2022. conditionList.innerHTML = '<li class=\"filter-list\"><span class=\"text-muted\">Aucune condition disponible</span></li>';
  2023. return;
  2024. }
  2025. let html = '';
  2026. const isAllSelected = ! currentConditionValue || currentConditionValue === '';
  2027. html += `<li class=\"filter-list\"><input class=\"pixel-radio\" type=\"radio\" id=\"all-conditions\" name=\"condition\" \${
  2028. isAllSelected ? 'checked' : ''
  2029. }><label for=\"all-conditions\">Toutes les conditions</label></li>`;
  2030. conditions.forEach(condition => {
  2031. const isSelected = currentConditionValue === condition.slug;
  2032. html += `<li class=\"filter-list\">
  2033.                 <input class=\"pixel-radio\" type=\"radio\" id=\"condition-\${
  2034. condition.id
  2035. }\" name=\"condition\" value=\"\${
  2036. condition.slug
  2037. }\" \${
  2038. isSelected ? 'checked' : ''
  2039. }>
  2040.                 <label for=\"condition-\${
  2041. condition.id
  2042. }\">\${
  2043. condition.name
  2044. }<span>(\${
  2045. condition.productCount
  2046. })</span></label>
  2047.             </li>`;
  2048. });
  2049. conditionList.innerHTML = html;
  2050. // Ajouter les event listeners
  2051. conditionList.querySelectorAll('input[name=\"condition\"]').forEach(radio => {
  2052. radio.addEventListener('change', function () {
  2053. if (this.id === 'all-conditions') {
  2054. currentFilters.condition = '';
  2055. } else {
  2056. currentFilters.condition = this.value;
  2057. } currentFilters.page = 1;
  2058. applyFilters();
  2059. });
  2060. });
  2061. }
  2062. // Fonction pour mettre à jour les boutiques disponibles
  2063. function updateAvailableShops(shops) {
  2064. const shopsFilter = document.getElementById('shopsFilter');
  2065. const shopList = document.getElementById('shopList');
  2066. const currentShopValue = currentFilters.shop || '';
  2067. // Toujours afficher le filtre des boutiques
  2068. shopsFilter.style.display = 'block';
  2069. // Si pas de boutiques disponibles, afficher un message ou garder les boutiques existantes
  2070. if (shops.length === 0) { // Ne pas réinitialiser si la liste existe déjà et a du contenu
  2071. if (shopList && shopList.children.length > 0) {
  2072. return; // Garder les boutiques existantes
  2073. }
  2074. // Sinon, afficher un message
  2075. shopList.innerHTML = '<li class=\"filter-list\"><span class=\"text-muted\">Aucune boutique disponible</span></li>';
  2076. return;
  2077. }
  2078. let html = '';
  2079. const isAllSelected = ! currentShopValue || currentShopValue === '';
  2080. html += `<li class=\"filter-list\"><input class=\"pixel-radio\" type=\"radio\" id=\"all-shops\" name=\"shop\" \${
  2081. isAllSelected ? 'checked' : ''
  2082. }><label for=\"all-shops\">Toutes les boutiques</label></li>`;
  2083. shops.forEach(shop => {
  2084. const isSelected = currentShopValue === shop.slug;
  2085. html += `<li class=\"filter-list\">
  2086.                 <input class=\"pixel-radio\" type=\"radio\" id=\"shop-\${
  2087. shop.id
  2088. }\" name=\"shop\" value=\"\${
  2089. shop.slug
  2090. }\" \${
  2091. isSelected ? 'checked' : ''
  2092. }>
  2093.                 <label for=\"shop-\${
  2094. shop.id
  2095. }\">\${
  2096. shop.name
  2097. }<span>(\${
  2098. shop.productCount
  2099. })</span></label>
  2100.             </li>`;
  2101. });
  2102. shopList.innerHTML = html;
  2103. // Mettre à jour aussi le modal
  2104. const modalShopList = document.getElementById('modalShopList');
  2105. if (modalShopList) {
  2106. 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-');
  2107. }
  2108. // Ajouter les event listeners (seulement pour la sidebar, le modal gère ses propres listeners)
  2109. shopList.querySelectorAll('input[name=\"shop\"]').forEach(radio => {
  2110. radio.addEventListener('change', function () {
  2111. if (this.id === 'all-shops') {
  2112. currentFilters.shop = '';
  2113. } else {
  2114. currentFilters.shop = this.value;
  2115. } currentFilters.page = 1;
  2116. applyFilters();
  2117. });
  2118. });
  2119. }
  2120. // Fonction pour mettre à jour les attributs disponibles
  2121. function updateAvailableAttributes(attributes) {
  2122. // Toujours afficher tous les filtres d'attributs, même s'ils ne sont pas pertinents
  2123. // Couleurs
  2124. updateAttributeFilter('colors', attributes.colors, 'colorList', 'colorsFilter');
  2125. // Tailles
  2126. updateAttributeFilter('sizes', attributes.sizes, 'sizeList', 'sizesFilter');
  2127. // Matériaux
  2128. updateAttributeFilter('materials', attributes.materials, 'materialList', 'materialsFilter');
  2129. // Conditions
  2130. updateAttributeFilter('conditions', attributes.conditions, 'conditionList', 'conditionsFilter');
  2131. }
  2132. // Fonction générique pour mettre à jour les filtres d'attributs
  2133. function updateAttributeFilter(attributeType, attributes, listId, filterId) {
  2134. const filter = document.getElementById(filterId);
  2135. const list = document.getElementById(listId);
  2136. const currentValue = currentFilters[attributeType] || '';
  2137. // Toujours afficher les filtres d'attributs
  2138. filter.style.display = 'block';
  2139. // Si pas d'attributs disponibles, afficher un message ou garder les attributs existants
  2140. if (! attributes || attributes.length === 0) { // Ne pas réinitialiser si la liste existe déjà et a du contenu
  2141. if (list && list.children.length > 0) {
  2142. return; // Garder les attributs existants
  2143. }
  2144. // Sinon, afficher un message
  2145. list.innerHTML = '<li class=\"filter-list\"><span class=\"text-muted\">Aucun attribut disponible</span></li>';
  2146. return;
  2147. }
  2148. let html = '';
  2149. const isAllSelected = ! currentValue || currentValue === '';
  2150. html += `<li class=\"filter-list\"><input class=\"pixel-radio\" type=\"radio\" id=\"all-\${attributeType}\" name=\"\${attributeType}\" \${
  2151. isAllSelected ? 'checked' : ''
  2152. }><label for=\"all-\${attributeType}\">Tous</label></li>`;
  2153. attributes.forEach(attr => {
  2154. const isSelected = currentValue === attr.value;
  2155. html += `<li class=\"filter-list\">
  2156.                 <input class=\"pixel-radio\" type=\"radio\" id=\"\${attributeType}-\${
  2157. attr.value
  2158. }\" name=\"\${attributeType}\" value=\"\${
  2159. attr.value
  2160. }\" \${
  2161. isSelected ? 'checked' : ''
  2162. }>
  2163.                 <label for=\"\${attributeType}-\${
  2164. attr.value
  2165. }\">\${
  2166. attr.value
  2167. }<span>(\${
  2168. attr.count
  2169. })</span></label>
  2170.             </li>`;
  2171. });
  2172. list.innerHTML = html;
  2173. // Mettre à jour aussi le modal (si l'élément existe)
  2174. const modalListId = 'modal' + listId.charAt(0).toUpperCase() + listId.slice(1); // modalColorList, modalSizeList, etc.
  2175. const modalList = document.getElementById(modalListId);
  2176. if (modalList) { // Adapter le HTML pour le modal (changer les IDs et names)
  2177. 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}-`);
  2178. modalList.innerHTML = modalHtml;
  2179. }
  2180. // Ajouter les event listeners (seulement pour la sidebar, le modal gère ses propres listeners)
  2181. list.querySelectorAll (`input[name=\"\${attributeType}\"]`).forEach(radio => {
  2182. radio.addEventListener('change', function () {
  2183. if (this.id === `all-\${attributeType}`) {
  2184. currentFilters[attributeType] = '';
  2185. } else {
  2186. currentFilters[attributeType] = this.value;
  2187. } currentFilters.page = 1;
  2188. applyFilters();
  2189. });
  2190. });
  2191. } else {
  2192. filter.style.display = 'none';
  2193. }
  2194. }
  2195. // Fonction pour mettre à jour l'URL
  2196. function updateURL () {
  2197. const url = new URL(window.location);
  2198. // Supprimer les anciens paramètres
  2199. const paramsToRemove = [
  2200. 'category',
  2201. 'brand',
  2202. 'sort',
  2203. 'price_min',
  2204. 'price_max',
  2205. 'page',
  2206. 'shop',
  2207. 'featured',
  2208. 'digital',
  2209. 'stock_status',
  2210. 'rating_min',
  2211. 'weight_min',
  2212. 'weight_max',
  2213. 'color',
  2214. 'size',
  2215. 'material',
  2216. 'condition',
  2217. 'availability'
  2218. ];
  2219. paramsToRemove.forEach(param => url.searchParams.delete(param));
  2220. // Ajouter les nouveaux paramètres
  2221. Object.keys(currentFilters).forEach(key => {
  2222. if (currentFilters[key]) {
  2223. url.searchParams.set(key, currentFilters[key]);
  2224. }
  2225. });
  2226. // Mettre à jour l'URL sans recharger la page
  2227. window.history.pushState({}, '', url);
  2228. }
  2229. // Fonction pour restaurer l'état des filtres statiques
  2230. function restoreStaticFiltersState (preservedFilters) { // Restaurer featured
  2231. if (preservedFilters.featured) {
  2232. const featuredInput = document.querySelector (`input[name=\"featured\"][value=\"\${
  2233. preservedFilters.featured
  2234. }\"]`);
  2235. if (featuredInput) {
  2236. featuredInput.checked = true;
  2237. } else {
  2238. const featuredAll = document.getElementById('featured-all');
  2239. if (featuredAll) 
  2240. featuredAll.checked = true;
  2241. }
  2242. } else {
  2243. const featuredAll = document.getElementById('featured-all');
  2244. if (featuredAll) 
  2245. featuredAll.checked = true;
  2246. }
  2247. // Restaurer digital
  2248. if (preservedFilters.digital) {
  2249. const digitalInput = document.querySelector (`input[name=\"digital\"][value=\"\${
  2250. preservedFilters.digital
  2251. }\"]`);
  2252. if (digitalInput) {
  2253. digitalInput.checked = true;
  2254. } else {
  2255. const digitalAll = document.getElementById('digital-all');
  2256. if (digitalAll) 
  2257. digitalAll.checked = true;
  2258. }
  2259. } else {
  2260. const digitalAll = document.getElementById('digital-all');
  2261. if (digitalAll) 
  2262. digitalAll.checked = true;
  2263. }
  2264. // Restaurer availability
  2265. if (preservedFilters.availability) {
  2266. const availabilityInput = document.querySelector (`input[name=\"availability\"][value=\"\${
  2267. preservedFilters.availability
  2268. }\"]`);
  2269. if (availabilityInput) {
  2270. availabilityInput.checked = true;
  2271. } else {
  2272. const availabilityAll = document.getElementById('availability-all');
  2273. if (availabilityAll) 
  2274. availabilityAll.checked = true;
  2275. }
  2276. } else {
  2277. const availabilityAll = document.getElementById('availability-all');
  2278. if (availabilityAll) 
  2279. availabilityAll.checked = true;
  2280. }
  2281. // Restaurer rating (mapping vers ratingMin)
  2282. if (preservedFilters.rating) {
  2283. const ratingInput = document.querySelector (`input[name=\"rating\"][value=\"\${
  2284. preservedFilters.rating
  2285. }\"]`);
  2286. if (ratingInput) {
  2287. ratingInput.checked = true;
  2288. } else {
  2289. const ratingAll = document.getElementById('rating-all');
  2290. if (ratingAll) 
  2291. ratingAll.checked = true;
  2292. }
  2293. } else {
  2294. const ratingAll = document.getElementById('rating-all');
  2295. if (ratingAll) 
  2296. ratingAll.checked = true;
  2297. }
  2298. }
  2299. // Fonction pour initialiser tous les filtres avec les valeurs de l'URL
  2300. function initializeFiltersFromURL () { // Initialiser les filtres statiques
  2301. const staticFilters = {
  2302. featured: currentFilters.featured || '',
  2303. digital: currentFilters.digital || '',
  2304. availability: currentFilters.availability || '',
  2305. rating: currentFilters.rating || currentFilters.ratingMin || ''
  2306. };
  2307. // Cocher les filtres statiques
  2308. Object.keys(staticFilters).forEach(filterName => {
  2309. const value = staticFilters[filterName];
  2310. if (value) {
  2311. const input = document.querySelector(`input[name=\"\${filterName}\"][value=\"\${value}\"]`);
  2312. if (input) {
  2313. input.checked = true;
  2314. } else { // Cocher \"all\" si la valeur n'existe pas
  2315. const allInput = document.getElementById (`\${filterName}-all`);
  2316. if (allInput) 
  2317. allInput.checked = true;
  2318. }
  2319. } else {
  2320. const allInput = document.getElementById (`\${filterName}-all`);
  2321. if (allInput) 
  2322. allInput.checked = true;
  2323. }
  2324. });
  2325. // Initialiser les filtres de poids
  2326. if (currentFilters.weightMin) {
  2327. const weightMinInput = document.getElementById('weightMin');
  2328. if (weightMinInput) 
  2329. weightMinInput.value = currentFilters.weightMin;
  2330. }
  2331. if (currentFilters.weightMax) {
  2332. const weightMaxInput = document.getElementById('weightMax');
  2333. if (weightMaxInput) 
  2334. weightMaxInput.value = currentFilters.weightMax;
  2335. }
  2336. // Initialiser les filtres de marque (si déjà chargés)
  2337. if (currentFilters.brand) {
  2338. const brandInput = document.querySelector (`input[name=\"brand\"][value=\"\${
  2339. currentFilters.brand
  2340. }\"]`);
  2341. if (brandInput) {
  2342. brandInput.checked = true;
  2343. } else {
  2344. const allBrands = document.getElementById('all-brands');
  2345. if (allBrands) 
  2346. allBrands.checked = true;
  2347. }
  2348. }
  2349. // Initialiser les filtres de boutique (si déjà chargés)
  2350. if (currentFilters.shop) {
  2351. const shopInput = document.querySelector (`input[name=\"shop\"][value=\"\${
  2352. currentFilters.shop
  2353. }\"]`);
  2354. if (shopInput) {
  2355. shopInput.checked = true;
  2356. } else {
  2357. const allShops = document.getElementById('all-shops');
  2358. if (allShops) 
  2359. allShops.checked = true;
  2360. }
  2361. }
  2362. // Initialiser les filtres d'attributs (si déjà chargés)
  2363. ['color', 'size', 'material', 'condition'].forEach(attrType => {
  2364. if (currentFilters[attrType]) {
  2365. const attrInput = document.querySelector(`input[name=\"\${attrType}\"][value=\"\${
  2366. currentFilters[attrType]
  2367. }\"]`);
  2368. if (attrInput) {
  2369. attrInput.checked = true;
  2370. } else {
  2371. const allAttr = document.getElementById (`all-\${attrType}`);
  2372. if (allAttr) 
  2373. allAttr.checked = true;
  2374. }
  2375. }
  2376. });
  2377. }
  2378. // Initialiser les filtres
  2379. document.addEventListener('DOMContentLoaded', function () { // Initialiser les filtres depuis l'URL
  2380. initializeFiltersFromURL();
  2381. // Event listeners pour les filtres statiques
  2382. const staticFilters = ['featured', 'digital', 'availability'];
  2383. staticFilters.forEach(filterName => {
  2384. const inputs = document.querySelectorAll (`input[name=\"\${filterName}\"]`);
  2385. inputs.forEach(input => {
  2386. input.addEventListener('change', function () {
  2387. if (this.id.includes('-all')) {
  2388. currentFilters[filterName] = '';
  2389. } else {
  2390. currentFilters[filterName] = this.value;
  2391. }
  2392. currentFilters.page = 1;
  2393. applyFilters();
  2394. });
  2395. });
  2396. });
  2397. // Event listener spécial pour rating (mapping vers ratingMin)
  2398. const ratingInputs = document.querySelectorAll('input[name=\"rating\"]');
  2399. ratingInputs.forEach(input => {
  2400. input.addEventListener('change', function () {
  2401. if (this.id === 'rating-all') {
  2402. currentFilters.ratingMin = '';
  2403. currentFilters.rating = '';
  2404. } else {
  2405. currentFilters.ratingMin = this.value;
  2406. currentFilters.rating = this.value;
  2407. }
  2408. currentFilters.page = 1;
  2409. applyFilters();
  2410. });
  2411. });
  2412. // Event listeners pour les filtres de poids
  2413. const weightInputs = ['weightMin', 'weightMax'];
  2414. weightInputs.forEach(inputId => {
  2415. const input = document.getElementById(inputId);
  2416. if (input) {
  2417. input.addEventListener('change', applyWeightFilter);
  2418. input.addEventListener('input', debounce(applyWeightFilter, 500));
  2419. }
  2420. });
  2421. // Event listeners pour les filtres de prix
  2422. const priceInputs = ['priceMin', 'priceMax'];
  2423. priceInputs.forEach(inputId => {
  2424. const input = document.getElementById(inputId);
  2425. if (input) {
  2426. input.addEventListener('change', applyPriceFilter);
  2427. input.addEventListener('input', debounce(applyPriceFilter, 500));
  2428. }
  2429. });
  2430. // Ajouter les event listeners pour les marques existantes
  2431. const brandList = document.getElementById('brandList');
  2432. if (brandList) {
  2433. brandList.querySelectorAll('input[name=\"brand\"]').forEach(radio => {
  2434. radio.addEventListener('change', function () {
  2435. if (this.id === 'all-brands') {
  2436. currentFilters.brand = '';
  2437. } else {
  2438. currentFilters.brand = this.value;
  2439. }
  2440. currentFilters.page = 1;
  2441. applyFilters();
  2442. });
  2443. });
  2444. }
  2445. // Appliquer les filtres initiaux si une catégorie est sélectionnée
  2446. if (currentFilters.category) {
  2447. applyFilters();
  2448. } else { // Initialiser l'affichage du bouton au chargement de la page
  2449. updateLoadMoreButton();
  2450. }
  2451. });
  2452. // Fonction de debounce pour éviter trop de requêtes
  2453. function debounce (func, wait) {
  2454. let timeout;
  2455. return function executedFunction(...args) {
  2456. const later = () => {
  2457. clearTimeout(timeout);
  2458. func(...args);
  2459. };
  2460. clearTimeout(timeout);
  2461. timeout = setTimeout(later, wait);
  2462. };
  2463. }
  2464. // Les fonctions toggleComparison, toggleWishlist, et showNotification sont définies globalement dans base_home.html.twig
  2465. // Pas besoin de les redéfinir ici
  2466. // Fonctions pour le dropshipping
  2467. function generateDropshipLink (productId) {
  2468. showEbayPromptModal('URL originale (optionnel):', '').then(originalUrl => {
  2469. if (originalUrl === null) 
  2470. return;
  2471.  // Utilisateur a annulé
  2472. const formData = new FormData();
  2473. formData.append('originalUrl', originalUrl || '');
  2474. fetch (`/api/dropship/generate/\${productId}`, {
  2475. method: 'POST',
  2476. body: formData
  2477. }).then(response => response.json()).then(data => {
  2478. if (data.success) { // Afficher le lien généré dans une modal
  2479. showDropshipLinkModal(data.data);
  2480. } else {
  2481. showNotification(data.message || 'Erreur lors de la génération du lien', 'error');
  2482. }
  2483. }).catch(error => {
  2484. console.error('Erreur:', error);
  2485. showNotification('Erreur lors de la génération du lien', 'error');
  2486. });
  2487. });
  2488. }
  2489. function showDropshipLinkModal (linkData) {
  2490. const modal = document.createElement('div');
  2491. modal.className = 'modal fade show';
  2492. modal.style.display = 'block';
  2493. modal.innerHTML = `
  2494.         <div class=\"modal-dialog\">
  2495.             <div class=\"modal-content\">
  2496.                 <div class=\"modal-header\">
  2497.                     <h5 class=\"modal-title\">
  2498.                         <i class=\"fa fa-share-alt\"></i> Lien de dropshipping généré
  2499.                     </h5>
  2500.                     <button type=\"button\" class=\"btn-close\" onclick=\"closeModal(this)\"></button>
  2501.                 </div>
  2502.                 <div class=\"modal-body\">
  2503.                     <div class=\"mb-3\">
  2504.                         <label class=\"form-label\">Lien de dropshipping :</label>
  2505.                         <div class=\"input-group\">
  2506.                             <input type=\"text\" class=\"form-control\" value=\"\${
  2507. linkData.dropshipUrl
  2508. }\" readonly id=\"dropshipUrl\">
  2509.                             <button class=\"btn btn-outline-secondary\" onclick=\"copyToClipboard('\${
  2510. linkData.dropshipUrl
  2511. }')\">
  2512.                                 <i class=\"fa fa-copy\"></i>
  2513.                             </button>
  2514.                         </div>
  2515.                     </div>
  2516.                     <div class=\"row\">
  2517.                         <div class=\"col-md-6\">
  2518.                             <div class=\"card\">
  2519.                                 <div class=\"card-body text-center\">
  2520.                                     <h6 class=\"card-title\">Taux de commission</h6>
  2521.                                     <h4 class=\"text-primary\">\${
  2522. linkData.commissionRate
  2523. }%</h4>
  2524.                                 </div>
  2525.                             </div>
  2526.                         </div>
  2527.                         <div class=\"col-md-6\">
  2528.                             <div class=\"card\">
  2529.                                 <div class=\"card-body text-center\">
  2530.                                     <h6 class=\"card-title\">Commission par vente</h6>
  2531.                                     <h4 class=\"text-success\">\${
  2532. linkData.commissionAmount
  2533. } HTG</h4>
  2534.                                 </div>
  2535.                             </div>
  2536.                         </div>
  2537.                     </div>
  2538.                     <div class=\"alert alert-info\">
  2539.                         <i class=\"fa fa-info-circle\"></i>
  2540.                         <strong>Note :</strong> Ce lien expire le \${
  2541. new Date(linkData.expiresAt).toLocaleDateString('fr-FR')
  2542. }.
  2543.                     </div>
  2544.                 </div>
  2545.                 <div class=\"modal-footer\">
  2546.                     <button type=\"button\" class=\"btn btn-secondary\" onclick=\"closeModal(this)\">Fermer</button>
  2547.                     <a href=\"/dropship/dashboard\" class=\"btn btn-primary\">
  2548.                         <i class=\"fa fa-tachometer\"></i> Voir le dashboard
  2549.                     </a>
  2550.                 </div>
  2551.             </div>
  2552.         </div>
  2553.     `;
  2554. document.body.appendChild(modal);
  2555. // Ajouter le backdrop
  2556. const backdrop = document.createElement('div');
  2557. backdrop.className = 'modal-backdrop fade show';
  2558. document.body.appendChild(backdrop);
  2559. }
  2560. function closeModal (button) {
  2561. const modal = button.closest('.modal');
  2562. const backdrop = document.querySelector('.modal-backdrop');
  2563. if (modal) {
  2564. modal.remove();
  2565. }
  2566. if (backdrop) {
  2567. backdrop.remove();
  2568. }
  2569. }
  2570. function copyToClipboard (text) {
  2571. navigator.clipboard.writeText(text).then(function () {
  2572. showNotification('Lien copié dans le presse-papiers !', 'success');
  2573. }, function (err) {
  2574. console.error('Erreur lors de la copie:', err);
  2575. showNotification('Erreur lors de la copie du lien', 'error');
  2576. });
  2577. }
  2578. // Fonction pour initialiser le chargement infini
  2579. function initInfiniteScroll () {
  2580. const loader = document.getElementById('infiniteScrollLoader');
  2581. const noMoreProducts = document.getElementById('noMoreProducts');
  2582. // Afficher le loader si il y a plus de produits
  2583. if (hasMoreProducts && loader) {
  2584. loader.style.display = 'block';
  2585. } else if (!hasMoreProducts && noMoreProducts) {
  2586. noMoreProducts.style.display = 'block';
  2587. }
  2588. // Observer pour détecter quand l'utilisateur arrive en bas
  2589. const observer = new IntersectionObserver((entries) => {
  2590. entries.forEach(entry => {
  2591. if (entry.isIntersecting && !isLoading && hasMoreProducts) {
  2592. loadMoreProducts();
  2593. }
  2594. });
  2595. }, {
  2596. root: null,
  2597. rootMargin: '200px', // Démarrer le chargement 200px avant d'arriver en bas
  2598. threshold: 0.1
  2599. });
  2600. // Observer le loader
  2601. if (loader) {
  2602. observer.observe(loader);
  2603. }
  2604. }
  2605. // Fonction pour mettre à jour l'affichage du bouton
  2606. function updateLoadMoreButton () {
  2607. const loadMoreContainer = document.getElementById('loadMoreContainer');
  2608. const noMoreProducts = document.getElementById('noMoreProducts');
  2609. if (hasMoreProducts) {
  2610. if (loadMoreContainer) {
  2611. loadMoreContainer.style.display = 'block';
  2612. }
  2613. if (noMoreProducts) {
  2614. noMoreProducts.style.display = 'none';
  2615. }
  2616. } else {
  2617. if (loadMoreContainer) {
  2618. loadMoreContainer.style.display = 'none';
  2619. }
  2620. if (noMoreProducts) {
  2621. noMoreProducts.style.display = 'block';
  2622. }
  2623. }
  2624. }
  2625. // Fonction pour initialiser les images des nouveaux produits
  2626. function initializeProductImages (container) {
  2627. const productCards = container.querySelectorAll('.single-product');
  2628. productCards.forEach(card => {
  2629. const productId = card.querySelector('.add-to-cart') ?. getAttribute('data-product-id');
  2630. if (productId) { // Récupérer les images depuis les attributs data ou depuis le DOM
  2631. const imgElement = card.querySelector('.main-product-img');
  2632. if (imgElement && imgElement.dataset.images) {
  2633. const images = JSON.parse(imgElement.dataset.images);
  2634. productImages[productId] = images;
  2635. currentImageIndex[productId] = 0;
  2636. }
  2637. }
  2638. });
  2639. }
  2640. // Fonction pour initialiser les event listeners des nouveaux produits
  2641. function initializeProductEventListeners (container) { // Boutons d'ajout au panier
  2642. const cartButtons = container.querySelectorAll('.add-to-cart');
  2643. cartButtons.forEach(button => {
  2644. button.addEventListener('click', function () {
  2645. const productId = this.getAttribute('data-product-id');
  2646. const qty = this.getAttribute('data-qty');
  2647. fetch('";
  2648.         // line 2206
  2649.         yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_cart_add");
  2650.         yield "', {
  2651. method: 'POST',
  2652. headers: {
  2653. 'Content-Type': 'application/x-www-form-urlencoded'
  2654. },
  2655. body: 'productId=' + productId + '&qty=' + qty
  2656. }).then(response => response.json()).then(data => {
  2657. if (data.ok) {
  2658. showEbayModal('Produit ajouté au panier !', 'success');
  2659. const cartBadge = document.querySelector('.cart-badge');
  2660. if (cartBadge) {
  2661. cartBadge.textContent = data.totalQty;
  2662. }
  2663. } else {
  2664. showEbayModal(data.message || 'Erreur lors de l\\'ajout au panier', 'error');
  2665. }
  2666. }).catch(error => {
  2667. console.error('Erreur:', error);
  2668. showEbayModal('Erreur lors de l\\'ajout au panier', 'error');
  2669. });
  2670. });
  2671. });
  2672. }
  2673. // La fonction showEbayModal est déjà définie plus haut
  2674. // Modal style eBay pour remplacer prompt()
  2675. function showEbayPromptModal (message, defaultValue = '') {
  2676. return new Promise((resolve) => {
  2677. const modalId = 'ebayPromptModal';
  2678. let existingModal = document.getElementById(modalId);
  2679. if (existingModal) {
  2680. existingModal.remove();
  2681. }
  2682. const modal = document.createElement('div');
  2683. modal.id = modalId;
  2684. modal.className = 'modal fade';
  2685. modal.setAttribute('tabindex', '-1');
  2686. modal.setAttribute('aria-labelledby', 'ebayPromptModalLabel');
  2687. modal.setAttribute('aria-hidden', 'true');
  2688. modal.innerHTML = `
  2689. <div class=\"modal-dialog modal-dialog-centered\">
  2690. <div class=\"modal-content\" style=\"border-radius: 8px; border: none; box-shadow: 0 4px 20px rgba(0,0,0,0.15);\">
  2691. <div class=\"modal-header\" style=\"border-bottom: 1px solid #e0e0e0; padding: 20px 24px;\">
  2692. <h5 class=\"modal-title\" id=\"ebayPromptModalLabel\" style=\"font-weight: 600; font-size: 18px; color: #333;\">
  2693. <i class=\"ti-pencil-alt\" style=\"color: #0064D2; margin-right: 8px;\"></i>Saisie
  2694. </h5>
  2695. <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\" style=\"margin: 0;\"></button>
  2696. </div>
  2697. <div class=\"modal-body\" style=\"padding: 24px;\">
  2698. <p style=\"margin-bottom: 16px; font-size: 16px; color: #333;\">\${message}</p>
  2699. <input type=\"text\" class=\"form-control\" id=\"ebayPromptInput\" value=\"\${defaultValue}\" style=\"border-radius: 4px; border: 1px solid #ccc; padding: 10px; font-size: 14px;\" autofocus>
  2700. </div>
  2701. <div class=\"modal-footer\" style=\"border-top: 1px solid #e0e0e0; padding: 16px 24px; justify-content: flex-end;\">
  2702. <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\" style=\"min-width: 80px; border-radius: 4px; font-weight: 500; margin-right: 8px;\">
  2703. Annuler
  2704. </button>
  2705. <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;\">
  2706. OK
  2707. </button>
  2708. </div>
  2709. </div>
  2710. </div>
  2711. `;
  2712. document.body.appendChild(modal);
  2713. const bsModal = new bootstrap.Modal(modal);
  2714. const input = modal.querySelector('#ebayPromptInput');
  2715. const confirmBtn = modal.querySelector('#ebayPromptConfirm');
  2716. // Gérer la confirmation
  2717. confirmBtn.addEventListener('click', function () {
  2718. const value = input.value;
  2719. bsModal.hide();
  2720. resolve(value);
  2721. });
  2722. // Gérer l'annulation
  2723. modal.addEventListener('hidden.bs.modal', function () {
  2724. if (input.value === undefined || input.value === '') {
  2725. resolve(null);
  2726. }
  2727. modal.remove();
  2728. });
  2729. // Gérer la touche Entrée
  2730. input.addEventListener('keypress', function (e) {
  2731. if (e.key === 'Enter') {
  2732. confirmBtn.click();
  2733. }
  2734. });
  2735. bsModal.show();
  2736. input.focus();
  2737. input.select();
  2738. });
  2739. }
  2740. // Remplacer window.alert et window.prompt
  2741. window.alert = showEbayModal;
  2742. window.prompt = showEbayPromptModal;
  2743. </script>
  2744. <script>
  2745. \t// Gestion du toggle de la sidebar sur mobile
  2746. document.addEventListener('DOMContentLoaded', function () {
  2747. const sidebarToggle = document.querySelector('.listing-sidebar-toggle');
  2748. const sidebarCollapse = document.getElementById('listingSidebarCollapse');
  2749. if (sidebarToggle && sidebarCollapse) { // Bootstrap 5 utilise des événements natifs
  2750. sidebarCollapse.addEventListener('show.bs.collapse', function () {
  2751. sidebarToggle.classList.remove('collapsed');
  2752. sidebarToggle.setAttribute('aria-expanded', 'true');
  2753. });
  2754. sidebarCollapse.addEventListener('hide.bs.collapse', function () {
  2755. sidebarToggle.classList.add('collapsed');
  2756. sidebarToggle.setAttribute('aria-expanded', 'false');
  2757. });
  2758. sidebarCollapse.addEventListener('shown.bs.collapse', function () {
  2759. sidebarToggle.classList.remove('collapsed');
  2760. });
  2761. sidebarCollapse.addEventListener('hidden.bs.collapse', function () {
  2762. sidebarToggle.classList.add('collapsed');
  2763. });
  2764. // Vérifier l'état initial
  2765. if (sidebarCollapse.classList.contains('show')) {
  2766. sidebarToggle.classList.remove('collapsed');
  2767. sidebarToggle.setAttribute('aria-expanded', 'true');
  2768. } else {
  2769. sidebarToggle.classList.add('collapsed');
  2770. sidebarToggle.setAttribute('aria-expanded', 'false');
  2771. }
  2772. }
  2773. });
  2774. // Synchroniser la recherche avec la barre de recherche du header
  2775. const headerSearchInput = document.getElementById('search_input');
  2776. if (headerSearchInput && typeof currentFilters !== 'undefined') { // Mettre à jour la valeur de la barre de recherche du header
  2777. headerSearchInput.value = currentFilters.q || '';
  2778. // Afficher le bouton clear si nécessaire
  2779. const clearSearchBtn = document.getElementById('clear_search_btn');
  2780. if (clearSearchBtn) {
  2781. clearSearchBtn.style.display = currentFilters.q ? 'block' : 'none';
  2782. }
  2783. }
  2784. // Réinitialiser tous les filtres
  2785. const resetFiltersBtn = document.getElementById('resetFiltersBtn');
  2786. if (resetFiltersBtn) {
  2787. resetFiltersBtn.addEventListener('click', function () {
  2788. resetAllFilters();
  2789. });
  2790. }
  2791. // Fonction pour réinitialiser tous les filtres
  2792. function resetAllFilters () {
  2793. currentFilters = {
  2794. category: '',
  2795. brand: '',
  2796. sort: 'newest',
  2797. priceMin: '',
  2798. priceMax: '',
  2799. page: 1,
  2800. q: '',
  2801. shop: '',
  2802. featured: '',
  2803. digital: '',
  2804. stockStatus: '',
  2805. ratingMin: '',
  2806. rating: '',
  2807. weightMin: '',
  2808. weightMax: '',
  2809. color: '',
  2810. size: '',
  2811. material: '',
  2812. condition: '',
  2813. availability: ''
  2814. };
  2815. // Réinitialiser la barre de recherche du header
  2816. const headerSearchInput = document.getElementById('search_input');
  2817. if (headerSearchInput) {
  2818. headerSearchInput.value = '';
  2819. }
  2820. const clearSearchBtn = document.getElementById('clear_search_btn');
  2821. if (clearSearchBtn) {
  2822. clearSearchBtn.style.display = 'none';
  2823. }
  2824. // Réinitialiser les inputs
  2825. document.getElementById('sortSelect').value = 'newest';
  2826. // Réinitialiser les radios
  2827. document.querySelectorAll('input[type=\"radio\"]').forEach(radio => {
  2828. 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')) {
  2829. radio.checked = true;
  2830. } else {
  2831. radio.checked = false;
  2832. }
  2833. });
  2834. // Réinitialiser les inputs de prix et poids
  2835. const weightMinInput = document.getElementById('weightMin');
  2836. const weightMaxInput = document.getElementById('weightMax');
  2837. if (weightMinInput) 
  2838. weightMinInput.value = '';
  2839. if (weightMaxInput) 
  2840. weightMaxInput.value = '';
  2841. applyFilters();
  2842. updateActiveFilters();
  2843. }
  2844. // Fonction pour mettre à jour l'affichage des filtres actifs
  2845. function updateActiveFilters () {
  2846. const activeFiltersDiv = document.getElementById('activeFilters');
  2847. const activeFiltersList = document.getElementById('activeFiltersList');
  2848. const filters = [];
  2849. if (currentFilters.q) {
  2850. filters.push (`<span class=\"badge bg-primary\">Recherche: \"\${
  2851. currentFilters.q
  2852. }\"</span>`);
  2853. }
  2854. if (currentFilters.category) {
  2855. filters.push(`<span class=\"badge bg-secondary\">Catégorie</span>`);
  2856. }
  2857. if (currentFilters.brand) {
  2858. filters.push(`<span class=\"badge bg-secondary\">Marque</span>`);
  2859. }
  2860. if (currentFilters.priceMin || currentFilters.priceMax) {
  2861. filters.push(`<span class=\"badge bg-info\">Prix</span>`);
  2862. }
  2863. if (currentFilters.featured === 'true') {
  2864. filters.push(`<span class=\"badge bg-warning\">Produits vedettes</span>`);
  2865. }
  2866. if (currentFilters.digital === 'true') {
  2867. filters.push(`<span class=\"badge bg-success\">Numériques</span>`);
  2868. }
  2869. if (currentFilters.digital === 'false') {
  2870. filters.push(`<span class=\"badge bg-success\">Physiques</span>`);
  2871. }
  2872. if (currentFilters.availability) {
  2873. filters.push(`<span class=\"badge bg-info\">Disponibilité</span>`);
  2874. }
  2875. if (currentFilters.ratingMin || currentFilters.rating) {
  2876. const ratingValue = currentFilters.ratingMin || currentFilters.rating;
  2877. filters.push (`<span class=\"badge bg-warning\">Note ≥ \${ratingValue}</span>`);
  2878. }
  2879. if (currentFilters.weightMin || currentFilters.weightMax) {
  2880. filters.push(`<span class=\"badge bg-secondary\">Poids</span>`);
  2881. }
  2882. if (filters.length > 0) {
  2883. activeFiltersList.innerHTML = filters.join(' ');
  2884. activeFiltersDiv.style.display = 'block';
  2885. } else {
  2886. activeFiltersDiv.style.display = 'none';
  2887. }
  2888. }
  2889. // Mettre à jour les filtres actifs au chargement et après chaque filtrage
  2890. document.addEventListener('DOMContentLoaded', function () {
  2891. updateActiveFilters();
  2892. });
  2893. // Mettre à jour les filtres actifs après chaque application de filtres
  2894. const originalApplyFilters = applyFilters;
  2895. applyFilters = function () {
  2896. originalApplyFilters();
  2897. setTimeout(updateActiveFilters, 100);
  2898. // Mettre à jour le compteur de filtres actifs
  2899. updateActiveFiltersCount();
  2900. };
  2901. // S'assurer que applyFilters est accessible globalement
  2902. window.applyFilters = applyFilters;
  2903. // Fonction pour mettre à jour le compteur de filtres actifs
  2904. function updateActiveFiltersCount () {
  2905. const count = Object.keys(currentFilters).filter(key => {
  2906. const value = currentFilters[key];
  2907. return value && value !== '' && key !== 'page' && key !== 'sort';
  2908. }).length;
  2909. const badge = document.getElementById('activeFiltersCount');
  2910. if (badge) {
  2911. if (count > 0) {
  2912. badge.textContent = count;
  2913. badge.style.display = 'inline-block';
  2914. } else {
  2915. badge.style.display = 'none';
  2916. }
  2917. }
  2918. }
  2919. // Initialiser le compteur au chargement
  2920. document.addEventListener('DOMContentLoaded', function () {
  2921. updateActiveFiltersCount();
  2922. });
  2923. </script>
  2924. <!-- Modal de filtres -->
  2925. <div class=\"modal fade\" id=\"filtersModal\" tabindex=\"-1\" aria-labelledby=\"filtersModalLabel\" aria-hidden=\"true\">
  2926. \t<div class=\"modal-dialog modal-lg\">
  2927. \t\t<div class=\"modal-content\" style=\"display: flex; flex-direction: column; max-height: 90vh;\">
  2928. \t\t\t<div class=\"modal-header\" style=\"flex-shrink: 0;\">
  2929. \t\t\t\t<h5 class=\"modal-title\" id=\"filtersModalLabel\">
  2930. \t\t\t\t\t<i class=\"lnr lnr-filter me-2\"></i>Filtres de recherche
  2931. \t\t\t\t</h5>
  2932. \t\t\t\t<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>
  2933. \t\t\t</div>
  2934. \t\t\t<div class=\"modal-body\" style=\"overflow-y: auto; flex: 1; min-height: 0;\">
  2935. \t\t\t\t<div
  2936. \t\t\t\t\tclass=\"row\">
  2937. \t\t\t\t\t<!-- Colonne gauche -->
  2938. \t\t\t\t\t<div
  2939. \t\t\t\t\t\tclass=\"col-md-6\">
  2940. \t\t\t\t\t\t<!-- Marques -->
  2941. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  2942. \t\t\t\t\t\t\t<div class=\"head\">Marques</div>
  2943. \t\t\t\t\t\t\t<ul class=\"brand-list\" id=\"modalBrandList\">
  2944. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  2945. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-all-brands\" name=\"modal-brand\" checked>
  2946. \t\t\t\t\t\t\t\t\t<label for=\"modal-all-brands\">Toutes les marques</label>
  2947. \t\t\t\t\t\t\t\t</li>
  2948. \t\t\t\t\t\t\t\t";
  2949.         // line 2543
  2950.         $context['_parent'] = $context;
  2951.         $context['_seq'] = CoreExtension::ensureTraversable(($context["brands"] ?? null));
  2952.         foreach ($context['_seq'] as $context["_key"] => $context["brand"]) {
  2953.             // line 2544
  2954.             yield "\t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  2955. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-brand-";
  2956.             // line 2545
  2957.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["brand"], "id", [], "any"falsefalsefalse2545), "html"nulltrue);
  2958.             yield "\" name=\"modal-brand\" value=\"";
  2959.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["brand"], "slug", [], "any"falsefalsefalse2545), "html"nulltrue);
  2960.             yield "\">
  2961. \t\t\t\t\t\t\t\t\t\t<label for=\"modal-brand-";
  2962.             // line 2546
  2963.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["brand"], "id", [], "any"falsefalsefalse2546), "html"nulltrue);
  2964.             yield "\">";
  2965.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["brand"], "name", [], "any"falsefalsefalse2546), "html"nulltrue);
  2966.             yield "<span>(";
  2967.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["brand"], "getActiveProductsCount", [], "method"falsefalsefalse2546), "html"nulltrue);
  2968.             yield ")</span>
  2969. \t\t\t\t\t\t\t\t\t\t</label>
  2970. \t\t\t\t\t\t\t\t\t</li>
  2971. \t\t\t\t\t\t\t\t";
  2972.         }
  2973.         $_parent $context['_parent'];
  2974.         unset($context['_seq'], $context['_key'], $context['brand'], $context['_parent']);
  2975.         $context array_intersect_key($context$_parent) + $_parent;
  2976.         // line 2550
  2977.         yield "\t\t\t\t\t\t\t</ul>
  2978. \t\t\t\t\t\t</div>
  2979. \t\t\t\t\t\t<!-- Conditions -->
  2980. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  2981. \t\t\t\t\t\t\t<div class=\"head\">Condition</div>
  2982. \t\t\t\t\t\t\t<ul class=\"condition-list\" id=\"modalConditionList\">
  2983. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  2984. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-all-conditions\" name=\"modal-condition\" checked>
  2985. \t\t\t\t\t\t\t\t\t<label for=\"modal-all-conditions\">Toutes les conditions</label>
  2986. \t\t\t\t\t\t\t\t</li>
  2987. \t\t\t\t\t\t\t\t";
  2988.         // line 2561
  2989.         $context['_parent'] = $context;
  2990.         $context['_seq'] = CoreExtension::ensureTraversable(($context["conditions"] ?? null));
  2991.         foreach ($context['_seq'] as $context["_key"] => $context["condition"]) {
  2992.             // line 2562
  2993.             yield "\t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  2994. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-condition-";
  2995.             // line 2563
  2996.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["condition"], "id", [], "any"falsefalsefalse2563), "html"nulltrue);
  2997.             yield "\" name=\"modal-condition\" value=\"";
  2998.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["condition"], "slug", [], "any"falsefalsefalse2563), "html"nulltrue);
  2999.             yield "\">
  3000. \t\t\t\t\t\t\t\t\t\t<label for=\"modal-condition-";
  3001.             // line 2564
  3002.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["condition"], "id", [], "any"falsefalsefalse2564), "html"nulltrue);
  3003.             yield "\">";
  3004.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["condition"], "name", [], "any"falsefalsefalse2564), "html"nulltrue);
  3005.             yield "<span>(";
  3006.             yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env$this->source$context["condition"], "getActiveProductsCount", [], "method"falsefalsefalse2564), "html"nulltrue);
  3007.             yield ")</span>
  3008. \t\t\t\t\t\t\t\t\t\t</label>
  3009. \t\t\t\t\t\t\t\t\t</li>
  3010. \t\t\t\t\t\t\t\t";
  3011.         }
  3012.         $_parent $context['_parent'];
  3013.         unset($context['_seq'], $context['_key'], $context['condition'], $context['_parent']);
  3014.         $context array_intersect_key($context$_parent) + $_parent;
  3015.         // line 2568
  3016.         yield "\t\t\t\t\t\t\t</ul>
  3017. \t\t\t\t\t\t</div>
  3018. \t\t\t\t\t\t<!-- Boutiques -->
  3019. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  3020. \t\t\t\t\t\t\t<div class=\"head\">Boutiques</div>
  3021. \t\t\t\t\t\t\t<div id=\"modalShopsFilter\">
  3022. \t\t\t\t\t\t\t\t<ul
  3023. \t\t\t\t\t\t\t\t\tclass=\"shop-list\" id=\"modalShopList\"><!-- Les boutiques seront chargées dynamiquement -->
  3024. \t\t\t\t\t\t\t\t</ul>
  3025. \t\t\t\t\t\t\t</div>
  3026. \t\t\t\t\t\t</div>
  3027. \t\t\t\t\t\t<!-- Produits vedettes -->
  3028. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  3029. \t\t\t\t\t\t\t<div class=\"head\">Produits vedettes</div>
  3030. \t\t\t\t\t\t\t<ul>
  3031. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3032. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-featured-all\" name=\"modal-featured\" checked>
  3033. \t\t\t\t\t\t\t\t\t<label for=\"modal-featured-all\">Tous les produits</label>
  3034. \t\t\t\t\t\t\t\t</li>
  3035. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3036. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-featured-only\" name=\"modal-featured\" value=\"true\">
  3037. \t\t\t\t\t\t\t\t\t<label for=\"modal-featured-only\">Produits vedettes uniquement</label>
  3038. \t\t\t\t\t\t\t\t</li>
  3039. \t\t\t\t\t\t\t</ul>
  3040. \t\t\t\t\t\t</div>
  3041. \t\t\t\t\t\t<!-- Type de produit -->
  3042. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  3043. \t\t\t\t\t\t\t<div class=\"head\">Type de produit</div>
  3044. \t\t\t\t\t\t\t<ul>
  3045. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3046. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-digital-all\" name=\"modal-digital\" checked>
  3047. \t\t\t\t\t\t\t\t\t<label for=\"modal-digital-all\">Tous les types</label>
  3048. \t\t\t\t\t\t\t\t</li>
  3049. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3050. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-digital-physical\" name=\"modal-digital\" value=\"false\">
  3051. \t\t\t\t\t\t\t\t\t<label for=\"modal-digital-physical\">Produits physiques</label>
  3052. \t\t\t\t\t\t\t\t</li>
  3053. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3054. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-digital-digital\" name=\"modal-digital\" value=\"true\">
  3055. \t\t\t\t\t\t\t\t\t<label for=\"modal-digital-digital\">Produits numériques</label>
  3056. \t\t\t\t\t\t\t\t</li>
  3057. \t\t\t\t\t\t\t</ul>
  3058. \t\t\t\t\t\t</div>
  3059. \t\t\t\t\t</div>
  3060. \t\t\t\t\t<!-- Colonne droite -->
  3061. \t\t\t\t\t<div
  3062. \t\t\t\t\t\tclass=\"col-md-6\">
  3063. \t\t\t\t\t\t<!-- Disponibilité -->
  3064. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  3065. \t\t\t\t\t\t\t<div class=\"head\">Disponibilité</div>
  3066. \t\t\t\t\t\t\t<ul>
  3067. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3068. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-availability-all\" name=\"modal-availability\" checked>
  3069. \t\t\t\t\t\t\t\t\t<label for=\"modal-availability-all\">Tous</label>
  3070. \t\t\t\t\t\t\t\t</li>
  3071. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3072. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-availability-in-stock\" name=\"modal-availability\" value=\"in_stock\">
  3073. \t\t\t\t\t\t\t\t\t<label for=\"modal-availability-in-stock\">En stock</label>
  3074. \t\t\t\t\t\t\t\t</li>
  3075. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3076. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-availability-low-stock\" name=\"modal-availability\" value=\"low_stock\">
  3077. \t\t\t\t\t\t\t\t\t<label for=\"modal-availability-low-stock\">Stock faible</label>
  3078. \t\t\t\t\t\t\t\t</li>
  3079. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3080. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-availability-out-of-stock\" name=\"modal-availability\" value=\"out_of_stock\">
  3081. \t\t\t\t\t\t\t\t\t<label for=\"modal-availability-out-of-stock\">Rupture de stock</label>
  3082. \t\t\t\t\t\t\t\t</li>
  3083. \t\t\t\t\t\t\t</ul>
  3084. \t\t\t\t\t\t</div>
  3085. \t\t\t\t\t\t<!-- Note minimale -->
  3086. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  3087. \t\t\t\t\t\t\t<div class=\"head\">Note minimale</div>
  3088. \t\t\t\t\t\t\t<ul>
  3089. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3090. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-rating-all\" name=\"modal-rating\" checked>
  3091. \t\t\t\t\t\t\t\t\t<label for=\"modal-rating-all\">Toutes les notes</label>
  3092. \t\t\t\t\t\t\t\t</li>
  3093. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3094. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-rating-4\" name=\"modal-rating\" value=\"4\">
  3095. \t\t\t\t\t\t\t\t\t<label for=\"modal-rating-4\">4 étoiles et plus</label>
  3096. \t\t\t\t\t\t\t\t</li>
  3097. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3098. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-rating-3\" name=\"modal-rating\" value=\"3\">
  3099. \t\t\t\t\t\t\t\t\t<label for=\"modal-rating-3\">3 étoiles et plus</label>
  3100. \t\t\t\t\t\t\t\t</li>
  3101. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3102. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-rating-2\" name=\"modal-rating\" value=\"2\">
  3103. \t\t\t\t\t\t\t\t\t<label for=\"modal-rating-2\">2 étoiles et plus</label>
  3104. \t\t\t\t\t\t\t\t</li>
  3105. \t\t\t\t\t\t\t</ul>
  3106. \t\t\t\t\t\t</div>
  3107. \t\t\t\t\t\t<!-- Prix -->
  3108. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  3109. \t\t\t\t\t\t\t<div class=\"head\">Prix (HTG)</div>
  3110. \t\t\t\t\t\t\t<div class=\"price-range-area\">
  3111. \t\t\t\t\t\t\t\t<div class=\"d-flex gap-3\">
  3112. \t\t\t\t\t\t\t\t\t<div class=\"flex-grow-1\">
  3113. \t\t\t\t\t\t\t\t\t\t<label class=\"form-label\">Min</label>
  3114. \t\t\t\t\t\t\t\t\t\t<input type=\"number\" class=\"form-control\" id=\"modal-priceMin\" placeholder=\"0\" min=\"0\" step=\"0.01\">
  3115. \t\t\t\t\t\t\t\t\t</div>
  3116. \t\t\t\t\t\t\t\t\t<div class=\"flex-grow-1\">
  3117. \t\t\t\t\t\t\t\t\t\t<label class=\"form-label\">Max</label>
  3118. \t\t\t\t\t\t\t\t\t\t<input type=\"number\" class=\"form-control\" id=\"modal-priceMax\" placeholder=\"999999\" min=\"0\" step=\"0.01\">
  3119. \t\t\t\t\t\t\t\t\t</div>
  3120. \t\t\t\t\t\t\t\t</div>
  3121. \t\t\t\t\t\t\t</div>
  3122. \t\t\t\t\t\t</div>
  3123. \t\t\t\t\t\t<!-- Poids -->
  3124. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  3125. \t\t\t\t\t\t\t<div class=\"head\">Poids (kg)</div>
  3126. \t\t\t\t\t\t\t<div class=\"weight-range-area\">
  3127. \t\t\t\t\t\t\t\t<div class=\"d-flex gap-3\">
  3128. \t\t\t\t\t\t\t\t\t<div class=\"flex-grow-1\">
  3129. \t\t\t\t\t\t\t\t\t\t<label class=\"form-label\">Min</label>
  3130. \t\t\t\t\t\t\t\t\t\t<input type=\"number\" class=\"form-control\" id=\"modal-weightMin\" placeholder=\"0\" step=\"0.1\" min=\"0\">
  3131. \t\t\t\t\t\t\t\t\t</div>
  3132. \t\t\t\t\t\t\t\t\t<div class=\"flex-grow-1\">
  3133. \t\t\t\t\t\t\t\t\t\t<label class=\"form-label\">Max</label>
  3134. \t\t\t\t\t\t\t\t\t\t<input type=\"number\" class=\"form-control\" id=\"modal-weightMax\" placeholder=\"100\" step=\"0.1\" min=\"0\">
  3135. \t\t\t\t\t\t\t\t\t</div>
  3136. \t\t\t\t\t\t\t\t</div>
  3137. \t\t\t\t\t\t\t</div>
  3138. \t\t\t\t\t\t</div>
  3139. \t\t\t\t\t\t<!-- Couleur -->
  3140. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  3141. \t\t\t\t\t\t\t<div class=\"head\">Couleur</div>
  3142. \t\t\t\t\t\t\t<div id=\"modalColorsFilter\">
  3143. \t\t\t\t\t\t\t\t<ul
  3144. \t\t\t\t\t\t\t\t\tclass=\"color-list\" id=\"modalColorList\"><!-- Les couleurs seront chargées dynamiquement -->
  3145. \t\t\t\t\t\t\t\t</ul>
  3146. \t\t\t\t\t\t\t</div>
  3147. \t\t\t\t\t\t</div>
  3148. \t\t\t\t\t\t<!-- Taille -->
  3149. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  3150. \t\t\t\t\t\t\t<div class=\"head\">Taille</div>
  3151. \t\t\t\t\t\t\t<div id=\"modalSizesFilter\">
  3152. \t\t\t\t\t\t\t\t<ul
  3153. \t\t\t\t\t\t\t\t\tclass=\"size-list\" id=\"modalSizeList\"><!-- Les tailles seront chargées dynamiquement -->
  3154. \t\t\t\t\t\t\t\t</ul>
  3155. \t\t\t\t\t\t\t</div>
  3156. \t\t\t\t\t\t</div>
  3157. \t\t\t\t\t\t<!-- Matériau -->
  3158. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  3159. \t\t\t\t\t\t\t<div class=\"head\">Matériau</div>
  3160. \t\t\t\t\t\t\t<div id=\"modalMaterialsFilter\">
  3161. \t\t\t\t\t\t\t\t<ul
  3162. \t\t\t\t\t\t\t\t\tclass=\"material-list\" id=\"modalMaterialList\"><!-- Les matériaux seront chargés dynamiquement -->
  3163. \t\t\t\t\t\t\t\t</ul>
  3164. \t\t\t\t\t\t\t</div>
  3165. \t\t\t\t\t\t</div>
  3166. \t\t\t\t\t</div>
  3167. \t\t\t\t</div>
  3168. \t\t\t</div>
  3169. \t\t\t<div class=\"modal-footer\" style=\"flex-shrink: 0; border-top: 1px solid #dee2e6; padding: 1rem 1.5rem; background: #f8f9fa;\">
  3170. \t\t\t\t<button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">
  3171. \t\t\t\t\t<i class=\"lnr lnr-cross-circle me-1\"></i>Annuler
  3172. \t\t\t\t</button>
  3173. \t\t\t\t<button type=\"button\" class=\"btn btn-outline-danger\" id=\"modalResetFiltersBtn\">
  3174. \t\t\t\t\t<i class=\"lnr lnr-refresh me-1\"></i>Réinitialiser
  3175. \t\t\t\t</button>
  3176. \t\t\t\t<button type=\"button\" class=\"btn btn-primary\" id=\"modalApplyFiltersBtn\">
  3177. \t\t\t\t\t<i class=\"lnr lnr-checkmark-circle me-1\"></i>Appliquer les filtres
  3178. \t\t\t\t</button>
  3179. \t\t\t</div>
  3180. \t\t</div>
  3181. \t</div>
  3182. </div>
  3183. <script>
  3184. \t// Synchroniser les filtres du modal avec currentFilters au chargement
  3185. document.addEventListener('DOMContentLoaded', function () {
  3186. console.log('[Modal] Initializing filter modal...');
  3187. // Fonction pour synchroniser les valeurs du modal avec currentFilters
  3188. function syncModalFilters() {
  3189. console.log('[Modal] Syncing filters with currentFilters:', currentFilters);
  3190. // Réinitialiser tous les radios \"Tous\" d'abord
  3191. document.querySelectorAll('#filtersModal input[type=\"radio\"][id*=\"-all\"]').forEach(radio => {
  3192. radio.checked = true;
  3193. });
  3194. // Marques
  3195. if (currentFilters.brand) {
  3196. const brandRadio = document.querySelector (`#modalBrandList input[value=\"\${
  3197. currentFilters.brand
  3198. }\"]`);
  3199. if (brandRadio) {
  3200. brandRadio.checked = true;
  3201. console.log('[Modal] Brand synced:', currentFilters.brand);
  3202. }
  3203. } else {
  3204. const allBrands = document.getElementById('modal-all-brands');
  3205. if (allBrands) 
  3206. allBrands.checked = true;
  3207. }
  3208. // Conditions
  3209. if (currentFilters.condition) {
  3210. const conditionRadio = document.querySelector (`#modalConditionList input[value=\"\${
  3211. currentFilters.condition
  3212. }\"]`);
  3213. if (conditionRadio) {
  3214. conditionRadio.checked = true;
  3215. console.log('[Modal] Condition synced:', currentFilters.condition);
  3216. }
  3217. } else {
  3218. const allConditions = document.getElementById('modal-all-conditions');
  3219. if (allConditions) 
  3220. allConditions.checked = true;
  3221. }
  3222. // Featured
  3223. if (currentFilters.featured === 'true') {
  3224. const featuredRadio = document.getElementById('modal-featured-only');
  3225. if (featuredRadio) {
  3226. featuredRadio.checked = true;
  3227. console.log('[Modal] Featured synced');
  3228. }
  3229. } else {
  3230. const allFeatured = document.getElementById('modal-featured-all');
  3231. if (allFeatured) 
  3232. allFeatured.checked = true;
  3233. }
  3234. // Digital
  3235. if (currentFilters.digital === 'true') {
  3236. const digitalRadio = document.getElementById('modal-digital-digital');
  3237. if (digitalRadio) 
  3238. digitalRadio.checked = true;
  3239. } else if (currentFilters.digital === 'false') {
  3240. const physicalRadio = document.getElementById('modal-digital-physical');
  3241. if (physicalRadio) 
  3242. physicalRadio.checked = true;
  3243. } else {
  3244. const allDigital = document.getElementById('modal-digital-all');
  3245. if (allDigital) 
  3246. allDigital.checked = true;
  3247. }
  3248. // Availability
  3249. if (currentFilters.availability) {
  3250. const availabilityId = `modal-availability-\${
  3251. currentFilters.availability.replace('_', '-')
  3252. }`;
  3253. const availabilityRadio = document.getElementById(availabilityId);
  3254. if (availabilityRadio) {
  3255. availabilityRadio.checked = true;
  3256. console.log('[Modal] Availability synced:', currentFilters.availability);
  3257. }
  3258. } else {
  3259. const allAvailability = document.getElementById('modal-availability-all');
  3260. if (allAvailability) 
  3261. allAvailability.checked = true;
  3262. }
  3263. // Rating
  3264. if (currentFilters.ratingMin || currentFilters.rating) {
  3265. const ratingValue = currentFilters.ratingMin || currentFilters.rating;
  3266. const ratingRadio = document.getElementById (`modal-rating-\${ratingValue}`);
  3267. if (ratingRadio) {
  3268. ratingRadio.checked = true;
  3269. console.log('[Modal] Rating synced:', ratingValue);
  3270. }
  3271. } else {
  3272. const allRating = document.getElementById('modal-rating-all');
  3273. if (allRating) 
  3274. allRating.checked = true;
  3275. }
  3276. // Prix
  3277. const priceMinInput = document.getElementById('modal-priceMin');
  3278. const priceMaxInput = document.getElementById('modal-priceMax');
  3279. if (priceMinInput) 
  3280. priceMinInput.value = currentFilters.priceMin || '';
  3281. if (priceMaxInput) 
  3282. priceMaxInput.value = currentFilters.priceMax || '';
  3283. // Poids
  3284. const weightMinInput = document.getElementById('modal-weightMin');
  3285. const weightMaxInput = document.getElementById('modal-weightMax');
  3286. if (weightMinInput) 
  3287. weightMinInput.value = currentFilters.weightMin || '';
  3288. if (weightMaxInput) 
  3289. weightMaxInput.value = currentFilters.weightMax || '';
  3290. console.log('[Modal] Filters synced successfully');
  3291. }
  3292. // Fonction pour charger les filtres dynamiques dans le modal
  3293. function loadDynamicFiltersToModal() {
  3294. console.log('[Modal] Loading dynamic filters...');
  3295. // Boutiques
  3296. const sidebarShopList = document.getElementById('shopList');
  3297. const modalShopList = document.getElementById('modalShopList');
  3298. if (modalShopList) { // Si la sidebar a déjà les boutiques, les copier
  3299. if (sidebarShopList && sidebarShopList.innerHTML.trim() && ! sidebarShopList.innerHTML.includes('Aucune boutique') && ! sidebarShopList.innerHTML.includes('seront chargées')) {
  3300. 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-');
  3301. modalShopList.innerHTML = shopHtml;
  3302. // Synchroniser la sélection
  3303. if (currentFilters.shop) {
  3304. const shopRadio = document.querySelector (`#modalShopList input[value=\"\${
  3305. currentFilters.shop
  3306. }\"]`);
  3307. if (shopRadio) 
  3308. shopRadio.checked = true;
  3309. } else {
  3310. const allShops = document.getElementById('modal-all-shops');
  3311. if (allShops) 
  3312. allShops.checked = true;
  3313. }
  3314. console.log('[Modal] Shops loaded from sidebar');
  3315. } else { // Si pas encore chargés, afficher un message ou laisser vide
  3316. if (! modalShopList.innerHTML.trim() || modalShopList.innerHTML.includes('seront chargées')) {
  3317. modalShopList.innerHTML = '<li class=\"filter-list\"><span class=\"text-muted\">Les boutiques seront chargées lors de l\\'application des filtres</span></li>';
  3318. }
  3319. }
  3320. }
  3321. // Couleurs
  3322. const sidebarColorList = document.getElementById('colorList');
  3323. const modalColorList = document.getElementById('modalColorList');
  3324. if (sidebarColorList && modalColorList) {
  3325. if (sidebarColorList.innerHTML.trim() && ! sidebarColorList.innerHTML.includes('Aucun')) {
  3326. 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-');
  3327. modalColorList.innerHTML = colorHtml;
  3328. // Synchroniser la sélection
  3329. if (currentFilters.color) {
  3330. const colorRadio = document.querySelector (`#modalColorList input[value=\"\${
  3331. currentFilters.color
  3332. }\"]`);
  3333. if (colorRadio) 
  3334. colorRadio.checked = true;
  3335. } else {
  3336. const allColor = document.getElementById('modal-all-color');
  3337. if (allColor) 
  3338. allColor.checked = true;
  3339. }
  3340. console.log('[Modal] Colors loaded');
  3341. }
  3342. }
  3343. // Tailles
  3344. const sidebarSizeList = document.getElementById('sizeList');
  3345. const modalSizeList = document.getElementById('modalSizeList');
  3346. if (sidebarSizeList && modalSizeList) {
  3347. if (sidebarSizeList.innerHTML.trim() && ! sidebarSizeList.innerHTML.includes('Aucun')) {
  3348. 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-');
  3349. modalSizeList.innerHTML = sizeHtml;
  3350. // Synchroniser la sélection
  3351. if (currentFilters.size) {
  3352. const sizeRadio = document.querySelector (`#modalSizeList input[value=\"\${
  3353. currentFilters.size
  3354. }\"]`);
  3355. if (sizeRadio) 
  3356. sizeRadio.checked = true;
  3357. } else {
  3358. const allSize = document.getElementById('modal-all-size');
  3359. if (allSize) 
  3360. allSize.checked = true;
  3361. }
  3362. console.log('[Modal] Sizes loaded');
  3363. }
  3364. }
  3365. // Matériaux
  3366. const sidebarMaterialList = document.getElementById('materialList');
  3367. const modalMaterialList = document.getElementById('modalMaterialList');
  3368. if (sidebarMaterialList && modalMaterialList) {
  3369. if (sidebarMaterialList.innerHTML.trim() && ! sidebarMaterialList.innerHTML.includes('Aucun')) {
  3370. 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-');
  3371. modalMaterialList.innerHTML = materialHtml;
  3372. // Synchroniser la sélection
  3373. if (currentFilters.material) {
  3374. const materialRadio = document.querySelector (`#modalMaterialList input[value=\"\${
  3375. currentFilters.material
  3376. }\"]`);
  3377. if (materialRadio) 
  3378. materialRadio.checked = true;
  3379. } else {
  3380. const allMaterial = document.getElementById('modal-all-material');
  3381. if (allMaterial) 
  3382. allMaterial.checked = true;
  3383. }
  3384. console.log('[Modal] Materials loaded');
  3385. }
  3386. }
  3387. }
  3388. // Synchroniser à l'ouverture du modal
  3389. const filtersModal = document.getElementById('filtersModal');
  3390. if (filtersModal) {
  3391. filtersModal.addEventListener('show.bs.modal', function () {
  3392. console.log('[Modal] Modal opening, syncing filters...');
  3393. // Charger les filtres dynamiques d'abord si pas encore chargés
  3394. loadDynamicFiltersToModal();
  3395. // Puis synchroniser les valeurs
  3396. setTimeout(() => {
  3397. syncModalFilters();
  3398. }, 100);
  3399. });
  3400. }
  3401. // Charger les filtres dynamiques au chargement initial si la sidebar les a déjà
  3402. loadDynamicFiltersToModal();
  3403. // Bouton Appliquer les filtres
  3404. const applyBtn = document.getElementById('modalApplyFiltersBtn');
  3405. if (applyBtn) {
  3406. applyBtn.addEventListener('click', function (e) {
  3407. e.preventDefault();
  3408. console.log('[Modal] Apply button clicked, collecting filter values...');
  3409. // Collecter les valeurs du modal
  3410. const brandRadio = document.querySelector('#modalBrandList input[name=\"modal-brand\"]:checked');
  3411. if (brandRadio) {
  3412. currentFilters.brand = brandRadio.id !== 'modal-all-brands' ? brandRadio.value : '';
  3413. console.log('[Modal] Brand:', currentFilters.brand);
  3414. } else {
  3415. currentFilters.brand = '';
  3416. console.log('[Modal] Brand: No selection found');
  3417. }
  3418. // Conditions
  3419. const conditionRadio = document.querySelector('#modalConditionList input[name=\"modal-condition\"]:checked');
  3420. if (conditionRadio) {
  3421. currentFilters.condition = conditionRadio.id !== 'modal-all-conditions' ? conditionRadio.value : '';
  3422. console.log('[Modal] Condition:', currentFilters.condition);
  3423. } else {
  3424. currentFilters.condition = '';
  3425. }
  3426. // Featured
  3427. const featuredRadio = document.querySelector('#filtersModal input[name=\"modal-featured\"]:checked');
  3428. if (featuredRadio) {
  3429. currentFilters.featured = featuredRadio.id !== 'modal-featured-all' ? featuredRadio.value : '';
  3430. console.log('[Modal] Featured:', currentFilters.featured);
  3431. } else {
  3432. currentFilters.featured = '';
  3433. }
  3434. // Digital
  3435. const digitalRadio = document.querySelector('#filtersModal input[name=\"modal-digital\"]:checked');
  3436. if (digitalRadio) {
  3437. currentFilters.digital = digitalRadio.id !== 'modal-digital-all' ? digitalRadio.value : '';
  3438. console.log('[Modal] Digital:', currentFilters.digital);
  3439. } else {
  3440. currentFilters.digital = '';
  3441. }
  3442. // Availability
  3443. const availabilityRadio = document.querySelector('#filtersModal input[name=\"modal-availability\"]:checked');
  3444. if (availabilityRadio) {
  3445. currentFilters.availability = availabilityRadio.id !== 'modal-availability-all' ? availabilityRadio.value : '';
  3446. console.log('[Modal] Availability:', currentFilters.availability);
  3447. } else {
  3448. currentFilters.availability = '';
  3449. }
  3450. // Rating
  3451. const ratingRadio = document.querySelector('#filtersModal input[name=\"modal-rating\"]:checked');
  3452. if (ratingRadio && ratingRadio.id !== 'modal-rating-all') {
  3453. currentFilters.ratingMin = ratingRadio.value;
  3454. currentFilters.rating = ratingRadio.value;
  3455. console.log('[Modal] Rating:', currentFilters.ratingMin);
  3456. } else {
  3457. currentFilters.ratingMin = '';
  3458. currentFilters.rating = '';
  3459. }
  3460. // Prix
  3461. const priceMinInput = document.getElementById('modal-priceMin');
  3462. const priceMaxInput = document.getElementById('modal-priceMax');
  3463. currentFilters.priceMin = priceMinInput ? priceMinInput.value.trim() : '';
  3464. currentFilters.priceMax = priceMaxInput ? priceMaxInput.value.trim() : '';
  3465. console.log('[Modal] Price:', currentFilters.priceMin, '-', currentFilters.priceMax);
  3466. // Poids
  3467. const weightMinInput = document.getElementById('modal-weightMin');
  3468. const weightMaxInput = document.getElementById('modal-weightMax');
  3469. currentFilters.weightMin = weightMinInput ? weightMinInput.value.trim() : '';
  3470. currentFilters.weightMax = weightMaxInput ? weightMaxInput.value.trim() : '';
  3471. console.log('[Modal] Weight:', currentFilters.weightMin, '-', currentFilters.weightMax);
  3472. // Couleur
  3473. const colorRadio = document.querySelector('#modalColorList input[name=\"modal-color\"]:checked');
  3474. if (colorRadio) {
  3475. currentFilters.color = colorRadio.id !== 'modal-all-color' ? colorRadio.value : '';
  3476. console.log('[Modal] Color:', currentFilters.color);
  3477. } else {
  3478. currentFilters.color = '';
  3479. }
  3480. // Taille
  3481. const sizeRadio = document.querySelector('#modalSizeList input[name=\"modal-size\"]:checked');
  3482. if (sizeRadio) {
  3483. currentFilters.size = sizeRadio.id !== 'modal-all-size' ? sizeRadio.value : '';
  3484. console.log('[Modal] Size:', currentFilters.size);
  3485. } else {
  3486. currentFilters.size = '';
  3487. }
  3488. // Matériau
  3489. const materialRadio = document.querySelector('#modalMaterialList input[name=\"modal-material\"]:checked');
  3490. if (materialRadio) {
  3491. currentFilters.material = materialRadio.id !== 'modal-all-material' ? materialRadio.value : '';
  3492. console.log('[Modal] Material:', currentFilters.material);
  3493. } else {
  3494. currentFilters.material = '';
  3495. }
  3496. // Boutiques
  3497. const shopRadio = document.querySelector('#modalShopList input[name=\"modal-shop\"]:checked');
  3498. if (shopRadio) {
  3499. currentFilters.shop = shopRadio.id !== 'modal-all-shops' ? shopRadio.value : '';
  3500. console.log('[Modal] Shop:', currentFilters.shop);
  3501. } else {
  3502. currentFilters.shop = '';
  3503. }
  3504. currentFilters.page = 1;
  3505. console.log('[Modal] All filters collected:', currentFilters);
  3506. // Fermer le modal d'abord
  3507. const modalElement = document.getElementById('filtersModal');
  3508. const modal = bootstrap.Modal.getInstance(modalElement);
  3509. if (modal) {
  3510. modal.hide();
  3511. console.log('[Modal] Modal closed');
  3512. }
  3513. // Appliquer les filtres après un court délai pour laisser le modal se fermer
  3514. setTimeout(() => { // Vérifier que applyFilters existe
  3515. if (typeof window.applyFilters === 'function') {
  3516. console.log('[Modal] Calling applyFilters()...');
  3517. window.applyFilters();
  3518. // Mettre à jour le compteur de filtres actifs
  3519. setTimeout(() => {
  3520. if (typeof updateActiveFiltersCount === 'function') {
  3521. updateActiveFiltersCount();
  3522. }
  3523. }, 100);
  3524. } else {
  3525. console.error('[Modal] applyFilters is not available!', typeof window.applyFilters);
  3526. // Essayer de recharger la page avec les nouveaux paramètres
  3527. const url = new URL(window.location.href);
  3528. Object.keys(currentFilters).forEach(key => {
  3529. if (currentFilters[key] && currentFilters[key] !== '') {
  3530. let apiKey = key;
  3531. if (key === 'priceMin') {
  3532. apiKey = 'price_min';
  3533. } else if (key === 'priceMax') {
  3534. apiKey = 'price_max';
  3535. } else if (key === 'ratingMin') {
  3536. apiKey = 'rating_min';
  3537. } else if (key === 'weightMin') {
  3538. apiKey = 'weight_min';
  3539. } else if (key === 'weightMax') {
  3540. apiKey = 'weight_max';
  3541. } else if (key === 'stockStatus') {
  3542. apiKey = 'stock_status';
  3543. }
  3544. url.searchParams.set(apiKey, currentFilters[key]);
  3545. } else {
  3546. let apiKey = key;
  3547. if (key === 'priceMin') {
  3548. apiKey = 'price_min';
  3549. } else if (key === 'priceMax') {
  3550. apiKey = 'price_max';
  3551. } else if (key === 'ratingMin') {
  3552. apiKey = 'rating_min';
  3553. } else if (key === 'weightMin') {
  3554. apiKey = 'weight_min';
  3555. } else if (key === 'weightMax') {
  3556. apiKey = 'weight_max';
  3557. } else if (key === 'stockStatus') {
  3558. apiKey = 'stock_status';
  3559. }
  3560. url.searchParams.delete(apiKey);
  3561. }
  3562. });
  3563. window.location.href = url.toString();
  3564. }
  3565. }, 300);
  3566. });
  3567. } else {
  3568. console.error('[Modal] Apply button not found!');
  3569. }
  3570. // Bouton Réinitialiser
  3571. const resetBtn = document.getElementById('modalResetFiltersBtn');
  3572. if (resetBtn) {
  3573. resetBtn.addEventListener('click', function () {
  3574. console.log('[Modal] Reset button clicked');
  3575. // Réinitialiser tous les filtres du modal
  3576. document.querySelectorAll('#filtersModal input[type=\"radio\"]').forEach(radio => {
  3577. if (radio.id && radio.id.includes('-all')) {
  3578. radio.checked = true;
  3579. } else {
  3580. radio.checked = false;
  3581. }
  3582. });
  3583. const priceMinInput = document.getElementById('modal-priceMin');
  3584. const priceMaxInput = document.getElementById('modal-priceMax');
  3585. const weightMinInput = document.getElementById('modal-weightMin');
  3586. const weightMaxInput = document.getElementById('modal-weightMax');
  3587. if (priceMinInput) 
  3588. priceMinInput.value = '';
  3589. if (priceMaxInput) 
  3590. priceMaxInput.value = '';
  3591. if (weightMinInput) 
  3592. weightMinInput.value = '';
  3593. if (weightMaxInput) 
  3594. weightMaxInput.value = '';
  3595. // Réinitialiser currentFilters (garder category et sort)
  3596. const category = currentFilters.category || '';
  3597. const sort = currentFilters.sort || 'newest';
  3598. currentFilters = {
  3599. category: category,
  3600. sort: sort,
  3601. page: 1,
  3602. brand: '',
  3603. condition: '',
  3604. featured: '',
  3605. digital: '',
  3606. availability: '',
  3607. ratingMin: '',
  3608. rating: '',
  3609. priceMin: '',
  3610. priceMax: '',
  3611. weightMin: '',
  3612. weightMax: '',
  3613. color: '',
  3614. size: '',
  3615. material: '',
  3616. shop: ''
  3617. };
  3618. console.log('[Modal] Filters reset, currentFilters:', currentFilters);
  3619. // Fermer le modal d'abord
  3620. const modalElement = document.getElementById('filtersModal');
  3621. const modal = bootstrap.Modal.getInstance(modalElement);
  3622. if (modal) {
  3623. modal.hide();
  3624. console.log('[Modal] Modal closed after reset');
  3625. }
  3626. // Appliquer les filtres après un court délai
  3627. setTimeout(() => {
  3628. if (typeof window.applyFilters === 'function') {
  3629. window.applyFilters();
  3630. } else {
  3631. console.error('[Modal] applyFilters not available, reloading page...');
  3632. // Recharger la page avec les filtres réinitialisés
  3633. const url = new URL(window.location.href);
  3634. url.searchParams.delete('price_min');
  3635. url.searchParams.delete('price_max');
  3636. url.searchParams.delete('rating_min');
  3637. url.searchParams.delete('weight_min');
  3638. url.searchParams.delete('weight_max');
  3639. url.searchParams.delete('brand');
  3640. url.searchParams.delete('condition');
  3641. url.searchParams.delete('featured');
  3642. url.searchParams.delete('digital');
  3643. url.searchParams.delete('availability');
  3644. url.searchParams.delete('color');
  3645. url.searchParams.delete('size');
  3646. url.searchParams.delete('material');
  3647. url.searchParams.delete('shop');
  3648. window.location.href = url.toString();
  3649. }
  3650. }, 300);
  3651. });
  3652. } else {
  3653. console.error('[Modal] Reset button not found!');
  3654. }
  3655. // S'assurer que les filtres dynamiques sont chargés au premier chargement si disponibles
  3656. setTimeout(() => {
  3657. loadDynamicFiltersToModal();
  3658. }, 500);
  3659. });
  3660. </script>";
  3661.         
  3662.         $__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
  3663.         
  3664.         $__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
  3665.         yield from [];
  3666.     }
  3667.     /**
  3668.      * @codeCoverageIgnore
  3669.      */
  3670.     public function getTemplateName(): string
  3671.     {
  3672.         return "home/listing.html.twig";
  3673.     }
  3674.     /**
  3675.      * @codeCoverageIgnore
  3676.      */
  3677.     public function isTraitable(): bool
  3678.     {
  3679.         return false;
  3680.     }
  3681.     /**
  3682.      * @codeCoverageIgnore
  3683.      */
  3684.     public function getDebugInfo(): array
  3685.     {
  3686.         return array (  3341 => 2568,  3327 => 2564,  3321 => 2563,  3318 => 2562,  3314 => 2561,  3301 => 2550,  3287 => 2546,  3281 => 2545,  3278 => 2544,  3274 => 2543,  2934 => 2206,  2013 => 1288,  2004 => 1282,  2000 => 1281,  1996 => 1280,  1992 => 1279,  1988 => 1278,  1984 => 1277,  1980 => 1276,  1976 => 1275,  1972 => 1274,  1968 => 1273,  1964 => 1272,  1960 => 1271,  1956 => 1270,  1951 => 1268,  1947 => 1267,  1943 => 1266,  1939 => 1265,  1935 => 1264,  1931 => 1263,  1927 => 1262,  1923 => 1261,  1916 => 1257,  1910 => 1254,  1904 => 1251,  1898 => 1248,  1892 => 1245,  1886 => 1242,  1880 => 1239,  1874 => 1236,  1868 => 1233,  1862 => 1230,  1856 => 1227,  1850 => 1224,  1844 => 1221,  1838 => 1218,  1834 => 1217,  1830 => 1216,  1826 => 1215,  1822 => 1214,  1818 => 1213,  1814 => 1212,  1810 => 1211,  1614 => 1018,  1604 => 1010,  1594 => 1009,  1590 => 1008,  1549 => 1007,  1542 => 1005,  1539 => 1004,  1533 => 1003,  1530 => 1002,  1525 => 1001,  1522 => 1000,  1517 => 999,  1515 => 998,  1513 => 997,  1509 => 996,  1429 => 919,  1425 => 918,  1421 => 917,  1417 => 916,  1413 => 915,  1338 => 843,  1334 => 842,  1330 => 840,  1317 => 839,  882 => 415,  847 => 414,  825 => 403,  817 => 397,  815 => 396,  810 => 393,  801 => 389,  799 => 388,  786 => 380,  771 => 376,  756 => 372,  749 => 368,  745 => 366,  738 => 363,  736 => 362,  731 => 360,  725 => 356,  721 => 354,  715 => 351,  710 => 350,  708 => 349,  697 => 341,  693 => 340,  688 => 337,  684 => 335,  661 => 333,  644 => 332,  640 => 331,  630 => 326,  621 => 323,  618 => 322,  615 => 321,  609 => 320,  606 => 319,  600 => 318,  597 => 317,  592 => 316,  589 => 315,  584 => 314,  581 => 313,  579 => 312,  574 => 309,  564 => 307,  554 => 305,  551 => 304,  548 => 303,  542 => 302,  539 => 301,  533 => 300,  530 => 299,  525 => 298,  522 => 297,  517 => 296,  514 => 295,  512 => 294,  508 => 293,  502 => 292,  498 => 290,  493 => 289,  480 => 281,  474 => 280,  468 => 279,  462 => 278,  456 => 277,  450 => 276,  277 => 105,  268 => 101,  266 => 100,  254 => 97,  248 => 96,  245 => 95,  240 => 94,  231 => 88,  214 => 73,  200 => 69,  194 => 68,  191 => 67,  187 => 66,  174 => 55,  165 => 51,  163 => 50,  155 => 47,  151 => 46,  140 => 44,  137 => 43,  132 => 42,  126 => 39,  117 => 37,  89 => 12,  79 => 4,  66 => 3,  43 => 1,);
  3687.     }
  3688.     public function getSourceContext(): Source
  3689.     {
  3690.         return new Source("{% extends 'base_home.html.twig' %}
  3691. {% block body %}
  3692. \t<!-- Start Banner Area -->
  3693. \t<section class=\"banner-area organic-breadcrumb\">
  3694. \t\t<div class=\"container\">
  3695. \t\t\t<div class=\"breadcrumb-banner d-flex flex-wrap align-items-center justify-content-end\">
  3696. \t\t\t\t<div class=\"col-first\">
  3697. \t\t\t\t\t<h1>Page de listage de produit</h1>
  3698. \t\t\t\t\t<nav class=\"d-flex align-items-center\">
  3699. \t\t\t\t\t\t<a href=\"{{ path('ui_home') }}\">Accueil<span class=\"lnr lnr-arrow-right\"></span>
  3700. \t\t\t\t\t\t</a>
  3701. \t\t\t\t\t\t<a href=\"javascript:void(0);\">Liste des produits</a>
  3702. \t\t\t\t\t</nav>
  3703. \t\t\t\t</div>
  3704. \t\t\t</div>
  3705. \t\t</div>
  3706. \t</section>
  3707. \t<!-- End Banner Area -->
  3708. \t<div class=\"container\">
  3709. \t\t<div class=\"row\">
  3710. \t\t\t<div class=\"col-xl-3 col-lg-4 col-md-5\">
  3711. \t\t\t\t<!-- Bouton toggle sidebar mobile -->
  3712. \t\t\t\t<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\">
  3713. \t\t\t\t\t<i class=\"lnr lnr-menu me-2\"></i>Filtres et catégories
  3714. \t\t\t\t\t<i class=\"lnr lnr-chevron-down ms-2 toggle-icon\"></i>
  3715. \t\t\t\t</button>
  3716. \t\t\t\t<!-- Sidebar avec collapse -->
  3717. \t\t\t\t<div class=\"collapse d-md-block\" id=\"listingSidebarCollapse\">
  3718. \t\t\t\t\t<div class=\"sidebar-categories\">
  3719. \t\t\t\t\t\t<div class=\"head\">Catégories</div>
  3720. \t\t\t\t\t\t<ul class=\"main-categories\">
  3721. \t\t\t\t\t\t\t<li class=\"main-nav-list\">
  3722. \t\t\t\t\t\t\t\t<a href=\"{{ path('ui_listing') }}\" class=\"category-link {% if not currentCategory %}active{% endif %}\">
  3723. \t\t\t\t\t\t\t\t\t<span class=\"lnr lnr-tag\"></span>Toutes les catégories
  3724. \t\t\t\t\t\t\t\t\t<span class=\"number\">({{ totalProducts }})</span>
  3725. \t\t\t\t\t\t\t\t</a>
  3726. \t\t\t\t\t\t\t</li>
  3727. \t\t\t\t\t\t\t{% for category in categories %}
  3728. \t\t\t\t\t\t\t\t<li class=\"main-nav-list\">
  3729. \t\t\t\t\t\t\t\t\t<a href=\"{{ path('ui_listing', {'category': category.slug}) }}\" class=\"category-link {% if currentCategory == category.slug %}active{% endif %}\" data-category=\"{{ category.slug }}\">
  3730. \t\t\t\t\t\t\t\t\t\t<span class=\"lnr lnr-tag\"></span>
  3731. \t\t\t\t\t\t\t\t\t\t{{ category.name }}
  3732. \t\t\t\t\t\t\t\t\t\t<span class=\"number\">({{ category.products|length }})</span>
  3733. \t\t\t\t\t\t\t\t\t</a>
  3734. \t\t\t\t\t\t\t\t</li>
  3735. \t\t\t\t\t\t\t{% else %}
  3736. \t\t\t\t\t\t\t\t<li class=\"main-nav-list\">
  3737. \t\t\t\t\t\t\t\t\t<span>Aucune catégorie</span>
  3738. \t\t\t\t\t\t\t\t</li>
  3739. \t\t\t\t\t\t\t{% endfor %}
  3740. \t\t\t\t\t\t</ul>
  3741. \t\t\t\t\t</div>
  3742. \t\t\t\t\t<div class=\"sidebar-filter mt-50\" id=\"brandsFilter\">
  3743. \t\t\t\t\t\t<div class=\"top-filter-head\">Filtres</div>
  3744. \t\t\t\t\t\t<div class=\"common-filter\">
  3745. \t\t\t\t\t\t\t<div class=\"head\">Marques</div>
  3746. \t\t\t\t\t\t\t<ul class=\"brand-list\" id=\"brandList\">
  3747. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3748. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"all-brands\" name=\"brand\" checked>
  3749. \t\t\t\t\t\t\t\t\t<label for=\"all-brands\">Toutes les marques</label>
  3750. \t\t\t\t\t\t\t\t</li>
  3751. \t\t\t\t\t\t\t\t{% for brand in brands %}
  3752. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3753. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"brand-{{ brand.id }}\" name=\"brand\" value=\"{{ brand.slug }}\">
  3754. \t\t\t\t\t\t\t\t\t\t<label for=\"brand-{{ brand.id }}\">{{ brand.name }}<span>({{ brand.getActiveProductsCount() }})</span>
  3755. \t\t\t\t\t\t\t\t\t\t</label>
  3756. \t\t\t\t\t\t\t\t\t</li>
  3757. \t\t\t\t\t\t\t\t{% endfor %}
  3758. \t\t\t\t\t\t\t</ul>
  3759. \t\t\t\t\t\t</div>
  3760. \t\t\t\t\t\t<!-- Filtres avancés -->
  3761. \t\t\t\t\t\t<div class=\"common-filter\">
  3762. \t\t\t\t\t\t\t<div class=\"head\">Boutiques</div>
  3763. \t\t\t\t\t\t\t<div id=\"shopsFilter\">
  3764. \t\t\t\t\t\t\t\t<form action=\"#\">
  3765. \t\t\t\t\t\t\t\t\t<ul
  3766. \t\t\t\t\t\t\t\t\t\tclass=\"shop-list\" id=\"shopList\"><!-- Les boutiques seront chargées dynamiquement -->
  3767. \t\t\t\t\t\t\t\t\t</ul>
  3768. \t\t\t\t\t\t\t\t</form>
  3769. \t\t\t\t\t\t\t</div>
  3770. \t\t\t\t\t\t</div>
  3771. \t\t\t\t\t\t<div class=\"common-filter\">
  3772. \t\t\t\t\t\t\t<div class=\"head\">Condition ({{ conditions|length }})</div>
  3773. \t\t\t\t\t\t\t<ul class=\"condition-list\" id=\"conditionList\">
  3774. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3775. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"all-conditions\" name=\"condition\" checked>
  3776. \t\t\t\t\t\t\t\t\t<label for=\"all-conditions\">Toutes les conditions</label>
  3777. \t\t\t\t\t\t\t\t</li>
  3778. \t\t\t\t\t\t\t\t{% for condition in conditions %}
  3779. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3780. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"condition-{{ condition.id }}\" name=\"condition\" value=\"{{ condition.slug }}\">
  3781. \t\t\t\t\t\t\t\t\t\t<label for=\"condition-{{ condition.id }}\">{{ condition.name }}<span>({{ condition.getActiveProductsCount() }})</span>
  3782. \t\t\t\t\t\t\t\t\t\t</label>
  3783. \t\t\t\t\t\t\t\t\t</li>
  3784. \t\t\t\t\t\t\t\t{% else %}
  3785. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3786. \t\t\t\t\t\t\t\t\t\t<span class=\"text-muted\">Aucune condition disponible</span>
  3787. \t\t\t\t\t\t\t\t\t</li>
  3788. \t\t\t\t\t\t\t\t{% endfor %}
  3789. \t\t\t\t\t\t\t</ul>
  3790. \t\t\t\t\t\t</div>
  3791. \t\t\t\t\t\t<div class=\"common-filter\">
  3792. \t\t\t\t\t\t\t<div class=\"head\">Produits vedettes</div>
  3793. \t\t\t\t\t\t\t<form action=\"#\">
  3794. \t\t\t\t\t\t\t\t<ul>
  3795. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3796. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"featured-all\" name=\"featured\" checked>
  3797. \t\t\t\t\t\t\t\t\t\t<label for=\"featured-all\">Tous les produits</label>
  3798. \t\t\t\t\t\t\t\t\t</li>
  3799. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3800. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"featured-only\" name=\"featured\" value=\"true\">
  3801. \t\t\t\t\t\t\t\t\t\t<label for=\"featured-only\">Produits vedettes uniquement</label>
  3802. \t\t\t\t\t\t\t\t\t</li>
  3803. \t\t\t\t\t\t\t\t</ul>
  3804. \t\t\t\t\t\t\t</form>
  3805. \t\t\t\t\t\t</div>
  3806. \t\t\t\t\t\t<div class=\"common-filter\">
  3807. \t\t\t\t\t\t\t<div class=\"head\">Type de produit</div>
  3808. \t\t\t\t\t\t\t<form action=\"#\">
  3809. \t\t\t\t\t\t\t\t<ul>
  3810. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3811. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"digital-all\" name=\"digital\" checked>
  3812. \t\t\t\t\t\t\t\t\t\t<label for=\"digital-all\">Tous les types</label>
  3813. \t\t\t\t\t\t\t\t\t</li>
  3814. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3815. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"digital-physical\" name=\"digital\" value=\"false\">
  3816. \t\t\t\t\t\t\t\t\t\t<label for=\"digital-physical\">Produits physiques</label>
  3817. \t\t\t\t\t\t\t\t\t</li>
  3818. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3819. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"digital-digital\" name=\"digital\" value=\"true\">
  3820. \t\t\t\t\t\t\t\t\t\t<label for=\"digital-digital\">Produits numériques</label>
  3821. \t\t\t\t\t\t\t\t\t</li>
  3822. \t\t\t\t\t\t\t\t</ul>
  3823. \t\t\t\t\t\t\t</form>
  3824. \t\t\t\t\t\t</div>
  3825. \t\t\t\t\t\t<div class=\"common-filter\">
  3826. \t\t\t\t\t\t\t<div class=\"head\">Disponibilité</div>
  3827. \t\t\t\t\t\t\t<form action=\"#\">
  3828. \t\t\t\t\t\t\t\t<ul>
  3829. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3830. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"availability-all\" name=\"availability\" checked>
  3831. \t\t\t\t\t\t\t\t\t\t<label for=\"availability-all\">Tous</label>
  3832. \t\t\t\t\t\t\t\t\t</li>
  3833. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3834. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"availability-in-stock\" name=\"availability\" value=\"in_stock\">
  3835. \t\t\t\t\t\t\t\t\t\t<label for=\"availability-in-stock\">En stock</label>
  3836. \t\t\t\t\t\t\t\t\t</li>
  3837. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3838. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"availability-low-stock\" name=\"availability\" value=\"low_stock\">
  3839. \t\t\t\t\t\t\t\t\t\t<label for=\"availability-low-stock\">Stock faible</label>
  3840. \t\t\t\t\t\t\t\t\t</li>
  3841. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3842. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"availability-out-of-stock\" name=\"availability\" value=\"out_of_stock\">
  3843. \t\t\t\t\t\t\t\t\t\t<label for=\"availability-out-of-stock\">Rupture de stock</label>
  3844. \t\t\t\t\t\t\t\t\t</li>
  3845. \t\t\t\t\t\t\t\t</ul>
  3846. \t\t\t\t\t\t\t</form>
  3847. \t\t\t\t\t\t</div>
  3848. \t\t\t\t\t\t<div class=\"common-filter\">
  3849. \t\t\t\t\t\t\t<div class=\"head\">Note minimale</div>
  3850. \t\t\t\t\t\t\t<form action=\"#\">
  3851. \t\t\t\t\t\t\t\t<ul>
  3852. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3853. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"rating-all\" name=\"rating\" checked>
  3854. \t\t\t\t\t\t\t\t\t\t<label for=\"rating-all\">Toutes les notes</label>
  3855. \t\t\t\t\t\t\t\t\t</li>
  3856. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3857. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"rating-4\" name=\"rating\" value=\"4\">
  3858. \t\t\t\t\t\t\t\t\t\t<label for=\"rating-4\">4 étoiles et plus</label>
  3859. \t\t\t\t\t\t\t\t\t</li>
  3860. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3861. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"rating-3\" name=\"rating\" value=\"3\">
  3862. \t\t\t\t\t\t\t\t\t\t<label for=\"rating-3\">3 étoiles et plus</label>
  3863. \t\t\t\t\t\t\t\t\t</li>
  3864. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  3865. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"rating-2\" name=\"rating\" value=\"2\">
  3866. \t\t\t\t\t\t\t\t\t\t<label for=\"rating-2\">2 étoiles et plus</label>
  3867. \t\t\t\t\t\t\t\t\t</li>
  3868. \t\t\t\t\t\t\t\t</ul>
  3869. \t\t\t\t\t\t\t</form>
  3870. \t\t\t\t\t\t</div>
  3871. \t\t\t\t\t\t<div class=\"common-filter\">
  3872. \t\t\t\t\t\t\t<div class=\"head\">Poids</div>
  3873. \t\t\t\t\t\t\t<div class=\"weight-range-area\">
  3874. \t\t\t\t\t\t\t\t<div class=\"weight-inputs d-flex\">
  3875. \t\t\t\t\t\t\t\t\t<div class=\"weight-input\">
  3876. \t\t\t\t\t\t\t\t\t\t<label>Min (kg):</label>
  3877. \t\t\t\t\t\t\t\t\t\t<input type=\"number\" id=\"weightMin\" placeholder=\"0\" step=\"0.1\" min=\"0\">
  3878. \t\t\t\t\t\t\t\t\t</div>
  3879. \t\t\t\t\t\t\t\t\t<div class=\"weight-input\">
  3880. \t\t\t\t\t\t\t\t\t\t<label>Max (kg):</label>
  3881. \t\t\t\t\t\t\t\t\t\t<input type=\"number\" id=\"weightMax\" placeholder=\"100\" step=\"0.1\" min=\"0\">
  3882. \t\t\t\t\t\t\t\t\t</div>
  3883. \t\t\t\t\t\t\t\t</div>
  3884. \t\t\t\t\t\t\t</div>
  3885. \t\t\t\t\t\t</div>
  3886. \t\t\t\t\t\t<div class=\"common-filter\">
  3887. \t\t\t\t\t\t\t<div class=\"head\">Couleur</div>
  3888. \t\t\t\t\t\t\t<div id=\"colorsFilter\">
  3889. \t\t\t\t\t\t\t\t<form action=\"#\">
  3890. \t\t\t\t\t\t\t\t\t<ul
  3891. \t\t\t\t\t\t\t\t\t\tclass=\"color-list\" id=\"colorList\"><!-- Les couleurs seront chargées dynamiquement -->
  3892. \t\t\t\t\t\t\t\t\t</ul>
  3893. \t\t\t\t\t\t\t\t</form>
  3894. \t\t\t\t\t\t\t</div>
  3895. \t\t\t\t\t\t</div>
  3896. \t\t\t\t\t\t<div class=\"common-filter\">
  3897. \t\t\t\t\t\t\t<div class=\"head\">Taille</div>
  3898. \t\t\t\t\t\t\t<div id=\"sizesFilter\">
  3899. \t\t\t\t\t\t\t\t<form action=\"#\">
  3900. \t\t\t\t\t\t\t\t\t<ul
  3901. \t\t\t\t\t\t\t\t\t\tclass=\"size-list\" id=\"sizeList\"><!-- Les tailles seront chargées dynamiquement -->
  3902. \t\t\t\t\t\t\t\t\t</ul>
  3903. \t\t\t\t\t\t\t\t</form>
  3904. \t\t\t\t\t\t\t</div>
  3905. \t\t\t\t\t\t</div>
  3906. \t\t\t\t\t\t<div class=\"common-filter\">
  3907. \t\t\t\t\t\t\t<div class=\"head\">Matériau</div>
  3908. \t\t\t\t\t\t\t<div id=\"materialsFilter\">
  3909. \t\t\t\t\t\t\t\t<form action=\"#\">
  3910. \t\t\t\t\t\t\t\t\t<ul
  3911. \t\t\t\t\t\t\t\t\t\tclass=\"material-list\" id=\"materialList\"><!-- Les matériaux seront chargés dynamiquement -->
  3912. \t\t\t\t\t\t\t\t\t</ul>
  3913. \t\t\t\t\t\t\t\t</form>
  3914. \t\t\t\t\t\t\t</div>
  3915. \t\t\t\t\t\t</div>
  3916. \t\t\t\t\t\t<div class=\"common-filter\">
  3917. \t\t\t\t\t\t\t<div class=\"head\">Prix</div>
  3918. \t\t\t\t\t\t\t<div class=\"price-range-area\">
  3919. \t\t\t\t\t\t\t\t<div id=\"price-range\"></div>
  3920. \t\t\t\t\t\t\t\t<div class=\"value-wrapper d-flex\">
  3921. \t\t\t\t\t\t\t\t\t<div class=\"price\">Prix:</div>
  3922. \t\t\t\t\t\t\t\t\t<span>\$</span>
  3923. \t\t\t\t\t\t\t\t\t<div id=\"lower-value\"></div>
  3924. \t\t\t\t\t\t\t\t\t<div class=\"to\">to</div>
  3925. \t\t\t\t\t\t\t\t\t<span>\$</span>
  3926. \t\t\t\t\t\t\t\t\t<div id=\"upper-value\"></div>
  3927. \t\t\t\t\t\t\t\t</div>
  3928. \t\t\t\t\t\t\t</div>
  3929. \t\t\t\t\t\t</div>
  3930. \t\t\t\t\t</div>
  3931. \t\t\t\t</div>
  3932. \t\t\t\t<!-- Fin du collapse sidebar -->
  3933. \t\t\t</div>
  3934. \t\t\t<div class=\"col-xl-9 col-lg-8 col-md-7\">
  3935. \t\t\t<!-- Barre de tri et bouton de filtres -->
  3936. \t\t\t<div class=\"row mb-4\">
  3937. \t\t\t\t<div
  3938. \t\t\t\t\tclass=\"col-md-6 mb-3 mb-md-0\">
  3939. \t\t\t\t\t<!-- Bouton pour ouvrir le modal de filtres -->
  3940. \t\t\t\t\t<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;\">
  3941. \t\t\t\t\t\t<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);\">
  3942. \t\t\t\t\t\t\t<i class=\"lnr lnr-filter\" style=\"font-size: 1.3rem; color: white; font-weight: bold;\"></i>
  3943. \t\t\t\t\t\t</div>
  3944. \t\t\t\t\t\t<span style=\"font-size: 1.1rem; font-weight: 600; color: #333;\">Filtres</span>
  3945. \t\t\t\t\t\t<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>
  3946. \t\t\t\t\t</button>
  3947. \t\t\t\t</div>
  3948. \t\t\t\t<div class=\"col-md-6\">
  3949. \t\t\t\t\t<select class=\"form-select\" id=\"sortSelect\" onchange=\"applySorting()\">
  3950. \t\t\t\t\t\t<option value=\"newest\" {% if currentSort == 'newest' %} selected {% endif %}>Plus récents</option>
  3951. \t\t\t\t\t\t<option value=\"price_asc\" {% if currentSort == 'price_asc' %} selected {% endif %}>Prix croissant</option>
  3952. \t\t\t\t\t\t<option value=\"price_desc\" {% if currentSort == 'price_desc' %} selected {% endif %}>Prix décroissant</option>
  3953. \t\t\t\t\t\t<option value=\"name_asc\" {% if currentSort == 'name_asc' %} selected {% endif %}>Nom A-Z</option>
  3954. \t\t\t\t\t\t<option value=\"name_desc\" {% if currentSort == 'name_desc' %} selected {% endif %}>Nom Z-A</option>
  3955. \t\t\t\t\t\t<option value=\"popular\" {% if currentSort == 'popular' %} selected {% endif %}>Plus populaires</option>
  3956. \t\t\t\t\t</select>
  3957. \t\t\t\t</div>
  3958. \t\t\t</div>
  3959. \t\t\t<!-- Start Best Seller -->
  3960. \t\t\t<section class=\"lattest-product-area pb-40 category-list\">
  3961. \t\t\t\t<div class=\"row\" id=\"productsContainer\">
  3962. \t\t\t\t\t{% for product in products %}
  3963. \t\t\t\t\t\t<div class=\"col-lg-4 col-md-6\">
  3964. \t\t\t\t\t\t\t<div class=\"single-product\">
  3965. \t\t\t\t\t\t\t\t<div class=\"product-image-container mb-2\" onmouseenter=\"showImageNav({{ product.id }})\" onmouseleave=\"hideImageNav({{ product.id }})\">
  3966. \t\t\t\t\t\t\t\t\t<a href=\"{{ path('ui_product_show', { slug: product.slug }) }}\">
  3967. \t\t\t\t\t\t\t\t\t\t{% set allImages = product.images|default([]) %}
  3968. \t\t\t\t\t\t\t\t\t\t{% if product.variants is defined %}
  3969. \t\t\t\t\t\t\t\t\t\t\t{% for variant in product.variants %}
  3970. \t\t\t\t\t\t\t\t\t\t\t\t{% if variant.isActive and variant.images is defined %}
  3971. \t\t\t\t\t\t\t\t\t\t\t\t\t{% for variantImg in variant.images %}
  3972. \t\t\t\t\t\t\t\t\t\t\t\t\t\t{% set allImages = allImages|merge([variantImg]) %}
  3973. \t\t\t\t\t\t\t\t\t\t\t\t\t{% endfor %}
  3974. \t\t\t\t\t\t\t\t\t\t\t\t{% endif %}
  3975. \t\t\t\t\t\t\t\t\t\t\t{% endfor %}
  3976. \t\t\t\t\t\t\t\t\t\t{% endif %}
  3977. \t\t\t\t\t\t\t\t\t\t{% if allImages|length > 0 %}
  3978. \t\t\t\t\t\t\t\t\t\t\t<img class=\"img-fluid main-product-img\" id=\"main-img-{{ product.id }}\" src=\"{{ asset(allImages[0]) }}\" alt=\"{{ product.name }}\">
  3979. \t\t\t\t\t\t\t\t\t\t{% else %}
  3980. \t\t\t\t\t\t\t\t\t\t\t<img class=\"img-fluid main-product-img\" id=\"main-img-{{ product.id }}\" src=\"{{ asset('ui/img/product/p1.jpg') }}\" alt=\"{{ product.name }}\">
  3981. \t\t\t\t\t\t\t\t\t\t{% endif %}
  3982. \t\t\t\t\t\t\t\t\t</a>
  3983. \t\t\t\t\t\t\t\t\t<!-- Boutons de navigation (visibles au survol) -->
  3984. \t\t\t\t\t\t\t\t\t{% set allImages = product.images|default([]) %}
  3985. \t\t\t\t\t\t\t\t\t{% if product.variants is defined %}
  3986. \t\t\t\t\t\t\t\t\t\t{% for variant in product.variants %}
  3987. \t\t\t\t\t\t\t\t\t\t\t{% if variant.isActive and variant.images is defined %}
  3988. \t\t\t\t\t\t\t\t\t\t\t\t{% for variantImg in variant.images %}
  3989. \t\t\t\t\t\t\t\t\t\t\t\t\t{% set allImages = allImages|merge([variantImg]) %}
  3990. \t\t\t\t\t\t\t\t\t\t\t\t{% endfor %}
  3991. \t\t\t\t\t\t\t\t\t\t\t{% endif %}
  3992. \t\t\t\t\t\t\t\t\t\t{% endfor %}
  3993. \t\t\t\t\t\t\t\t\t{% endif %}
  3994. \t\t\t\t\t\t\t\t\t{% if allImages|length > 1 %}
  3995. \t\t\t\t\t\t\t\t\t\t<button class=\"img-nav-btn img-nav-left\" id=\"img-left-{{ product.id }}\" onclick=\"prevImage({{ product.id }})\" style=\"display: none;\">
  3996. \t\t\t\t\t\t\t\t\t\t\t<span class=\"lnr lnr-chevron-left\"></span>
  3997. \t\t\t\t\t\t\t\t\t\t</button>
  3998. \t\t\t\t\t\t\t\t\t\t<button class=\"img-nav-btn img-nav-right\" id=\"img-right-{{ product.id }}\" onclick=\"nextImage({{ product.id }})\" style=\"display: none;\">
  3999. \t\t\t\t\t\t\t\t\t\t\t<span class=\"lnr lnr-chevron-right\"></span>
  4000. \t\t\t\t\t\t\t\t\t\t</button>
  4001. \t\t\t\t\t\t\t\t\t\t<!-- Indicateurs de position -->
  4002. \t\t\t\t\t\t\t\t\t\t<div class=\"img-indicators\" id=\"img-indicators-{{ product.id }}\" style=\"display: none;\">
  4003. \t\t\t\t\t\t\t\t\t\t\t{% for img in allImages %}
  4004. \t\t\t\t\t\t\t\t\t\t\t\t<span class=\"indicator\" id=\"indicator-{{ product.id }}-{{ loop.index0 }}\" onclick=\"showImage({{ product.id }}, {{ loop.index0 }})\"></span>
  4005. \t\t\t\t\t\t\t\t\t\t\t{% endfor %}
  4006. \t\t\t\t\t\t\t\t\t\t</div>
  4007. \t\t\t\t\t\t\t\t\t{% endif %}
  4008. \t\t\t\t\t\t\t\t</div>
  4009. \t\t\t\t\t\t\t\t<div class=\"product-details\">
  4010. \t\t\t\t\t\t\t\t\t<a href=\"{{ path('ui_product_show', { slug: product.slug }) }}\">
  4011. \t\t\t\t\t\t\t\t\t\t<h6>{{ product.name }}</h6>
  4012. \t\t\t\t\t\t\t\t\t</a>
  4013. \t\t\t\t\t\t\t\t\t<!-- Affichage de la boutique -->
  4014. \t\t\t\t\t\t\t\t\t<div class=\"shop-info\">
  4015. \t\t\t\t\t\t\t\t\t\t<small class=\"text-muted\">
  4016. \t\t\t\t\t\t\t\t\t\t\t<i class=\"lnr lnr-store\"></i>
  4017. \t\t\t\t\t\t\t\t\t\t\tVendu par :
  4018. \t\t\t\t\t\t\t\t\t\t\t{% if product.shop is defined and product.shop %}
  4019. \t\t\t\t\t\t\t\t\t\t\t\t<a href=\"{{ path('ui_shop_show', {'slug': product.shop.slug}) }}\" class=\"shop-link\">
  4020. \t\t\t\t\t\t\t\t\t\t\t\t\t{{ product.shop.name }}
  4021. \t\t\t\t\t\t\t\t\t\t\t\t</a>
  4022. \t\t\t\t\t\t\t\t\t\t\t{% else %}
  4023. \t\t\t\t\t\t\t\t\t\t\t\t<span class=\"text-muted\">Boutique inconnue</span>
  4024. \t\t\t\t\t\t\t\t\t\t\t{% endif %}
  4025. \t\t\t\t\t\t\t\t\t\t</small>
  4026. \t\t\t\t\t\t\t\t\t</div>
  4027. \t\t\t\t\t\t\t\t\t<div class=\"price\">
  4028. \t\t\t\t\t\t\t\t\t\t<h6>{{ product.price|number_format(2, '.', ' ') }}
  4029. \t\t\t\t\t\t\t\t\t\t\tHTG</h6>
  4030. \t\t\t\t\t\t\t\t\t\t{% if product.compareAtPrice %}
  4031. \t\t\t\t\t\t\t\t\t\t\t<h6 class=\"l-through\">{{ product.compareAtPrice|number_format(2, '.', ' ') }}
  4032. \t\t\t\t\t\t\t\t\t\t\t\tHTG</h6>
  4033. \t\t\t\t\t\t\t\t\t\t{% endif %}
  4034. \t\t\t\t\t\t\t\t\t</div>
  4035. \t\t\t\t\t\t\t\t\t<div class=\"prd-bottom\">
  4036. \t\t\t\t\t\t\t\t\t\t<a href=\"javascript:void(0)\" class=\"social-info add-to-cart\" data-product-id=\"{{ product.id }}\" data-qty=\"1\">
  4037. \t\t\t\t\t\t\t\t\t\t\t<span class=\"ti-bag\"></span>
  4038. \t\t\t\t\t\t\t\t\t\t\t<p class=\"hover-text\">+ panier</p>
  4039. \t\t\t\t\t\t\t\t\t\t</a>
  4040. \t\t\t\t\t\t\t\t\t\t<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 %}>
  4041. \t\t\t\t\t\t\t\t\t\t\t<span class=\"lnr lnr-heart\"></span>
  4042. \t\t\t\t\t\t\t\t\t\t\t<p class=\"hover-text\">Favoris</p>
  4043. \t\t\t\t\t\t\t\t\t\t</a>
  4044. \t\t\t\t\t\t\t\t\t\t<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 %}>
  4045. \t\t\t\t\t\t\t\t\t\t\t<span class=\"lnr lnr-sync\"></span>
  4046. \t\t\t\t\t\t\t\t\t\t\t<p class=\"hover-text\">Comparer</p>
  4047. \t\t\t\t\t\t\t\t\t\t</a>
  4048. \t\t\t\t\t\t\t\t\t\t<a href=\"{{ path('ui_product_show', { slug: product.slug }) }}\" class=\"social-info\">
  4049. \t\t\t\t\t\t\t\t\t\t\t<span class=\"lnr lnr-move\"></span>
  4050. \t\t\t\t\t\t\t\t\t\t\t<p class=\"hover-text\">Voir plus</p>
  4051. \t\t\t\t\t\t\t\t\t\t</a>
  4052. \t\t\t\t\t\t\t\t\t</div>
  4053. \t\t\t\t\t\t\t\t</div>
  4054. \t\t\t\t\t\t\t</div>
  4055. \t\t\t\t\t\t</div>
  4056. \t\t\t\t\t{% else %}
  4057. \t\t\t\t\t\t<div class=\"col-12\">
  4058. \t\t\t\t\t\t\t<p>Aucun produit disponible.</p>
  4059. \t\t\t\t\t\t</div>
  4060. \t\t\t\t\t{% endfor %}
  4061. \t\t\t\t</div>
  4062. \t\t\t\t<!-- Bouton Charger plus de produits -->
  4063. \t\t\t\t{% if totalPages > currentPage %}
  4064. \t\t\t\t\t<div class=\"text-center mt-4 mb-4\" id=\"loadMoreContainer\">
  4065. \t\t\t\t\t\t<button type=\"button\" class=\"btn btn-primary btn-lg\" id=\"loadMoreBtn\" onclick=\"loadMoreProducts()\">
  4066. \t\t\t\t\t\t\t<i class=\"lnr lnr-plus-circle me-2\"></i>Charger plus de produits
  4067. \t\t\t\t\t\t</button>
  4068. \t\t\t\t\t</div>
  4069. \t\t\t\t{% endif %}
  4070. \t\t\t\t<!-- Message de fin -->
  4071. \t\t\t\t<div id=\"noMoreProducts\" class=\"text-center py-4\" style=\"display: none;\">
  4072. \t\t\t\t\t<p class=\"text-muted\">
  4073. \t\t\t\t\t\t<i class=\"lnr lnr-checkmark-circle\"></i>
  4074. \t\t\t\t\t\tTous les produits ont été chargés</p>
  4075. \t\t\t\t</div>
  4076. \t\t\t</section>
  4077. \t\t\t<!-- End Best Seller -->
  4078. \t\t</div>
  4079. \t</div>
  4080. </div>{% endblock %}{% block title %}Liste des produits | MaketOu{% endblock %}{% block stylesheets %}
  4081. <style>
  4082. \t/* Styles pour la navigation des images */
  4083. \t.product-image-container {
  4084. \t\tposition: relative;
  4085. \t\toverflow: hidden;
  4086. \t\tborder-radius: 8px;
  4087. \t}
  4088. \t.img-nav-btn {
  4089. \t\tposition: absolute;
  4090. \t\ttop: 50%;
  4091. \t\ttransform: translateY(-50%);
  4092. \t\tbackground: rgba(0, 0, 0, 0.6);
  4093. \t\tcolor: white;
  4094. \t\tborder: none;
  4095. \t\twidth: 35px;
  4096. \t\theight: 35px;
  4097. \t\tborder-radius: 50%;
  4098. \t\tdisplay: flex;
  4099. \t\talign-items: center;
  4100. \t\tjustify-content: center;
  4101. \t\tcursor: pointer;
  4102. \t\ttransition: all 0.3s ease;
  4103. \t\tz-index: 10;
  4104. \t}
  4105. \t.img-nav-btn:hover {
  4106. \t\tbackground: #095ad3;
  4107. \t\ttransform: translateY(-50%) scale(1.1);
  4108. \t}
  4109. \t.img-nav-left {
  4110. \t\tleft: 10px;
  4111. \t}
  4112. \t.img-nav-right {
  4113. \t\tright: 10px;
  4114. \t}
  4115. \t.img-indicators {
  4116. \t\tposition: absolute;
  4117. \t\tbottom: 10px;
  4118. \t\tleft: 50%;
  4119. \t\ttransform: translateX(-50%);
  4120. \t\tdisplay: flex;
  4121. \t\tgap: 5px;
  4122. \t\tz-index: 10;
  4123. \t}
  4124. \t.indicator {
  4125. \t\twidth: 8px;
  4126. \t\theight: 8px;
  4127. \t\tborder-radius: 50%;
  4128. \t\tbackground: rgba(255, 255, 255, 0.5);
  4129. \t\tcursor: pointer;
  4130. \t\tmargin-bottom: 10px;
  4131. \t\ttransition: all 0.3s ease;
  4132. \t\tborder: 1px solid rgba(255, 255, 255, 0.3);
  4133. \t}
  4134. \t.indicator.active {
  4135. \t\tbackground: #ffa200;
  4136. \t\tborder-color: #ffa200;
  4137. \t\ttransform: scale(1.2);
  4138. \t}
  4139. \t.indicator:hover {
  4140. \t\tbackground: rgba(255, 255, 255, 0.8);
  4141. \t}
  4142. \t/* Styles pour l'affichage de la boutique */
  4143. \t.shop-info {
  4144. \t\tmargin: 8px 0;
  4145. \t\tpadding: 5px 0;
  4146. \t\tborder-bottom: 1px solid #f0f0f0;
  4147. \t}
  4148. \t.shop-link {
  4149. \t\tcolor: #007bff;
  4150. \t\ttext-decoration: none;
  4151. \t\tfont-weight: 500;
  4152. \t}
  4153. \t.shop-link:hover {
  4154. \t\tcolor: #ffa200;
  4155. \t\ttext-decoration: underline;
  4156. \t}
  4157. \t/* Animation pour le survol */
  4158. \t.product-image-container:hover .main-product-img {
  4159. \t\ttransform: scale(1.05);
  4160. \t\ttransition: transform 0.3s ease;
  4161. \t}
  4162. \t.main-product-img {
  4163. \t\ttransition: transform 0.3s ease;
  4164. \t\twidth: 100%;
  4165. \t\theight: 100%;
  4166. \t\tobject-fit: cover; /* Recadre l'image pour remplir le conteneur */
  4167. \t\tobject-position: center; /* Centre l'image */
  4168. \t\tdisplay: block;
  4169. \t}
  4170. \t/* Styles pour les images des produits associés (Deals de la semaine) */
  4171. \t.related-product-img-container {
  4172. \t\tposition: relative;
  4173. \t\toverflow: hidden;
  4174. \t\tborder-radius: 8px;
  4175. \t\twidth: 100%;
  4176. \t\theight: 200px; /* Hauteur fixe pour les produits associés */
  4177. \t\tbackground: #f8f9fa; /* Fond au cas où l'image ne charge pas */
  4178. \t}
  4179. \t.related-product-img {
  4180. \t\twidth: 100%;
  4181. \t\theight: 100%;
  4182. \t\tobject-fit: cover; /* Recadre l'image pour remplir le conteneur */
  4183. \t\tobject-position: center; /* Centre l'image */
  4184. \t\tdisplay: block;
  4185. \t\ttransition: transform 0.3s ease;
  4186. \t}
  4187. \t.related-product-img-container:hover .related-product-img {
  4188. \t\ttransform: scale(1.05);
  4189. \t}
  4190. \t/* Styles pour l'image de catégorie dans les deals */
  4191. \t.category-banner-container {
  4192. \t\tdisplay: block;
  4193. \t\tposition: relative;
  4194. \t\toverflow: hidden;
  4195. \t\tborder-radius: 8px;
  4196. \t\twidth: 100%;
  4197. \t\theight: 400px; /* Hauteur fixe pour la bannière de catégorie */
  4198. \t\tbackground: #f8f9fa; /* Fond au cas où l'image ne charge pas */
  4199. \t}
  4200. \t.category-banner-img {
  4201. \t\twidth: 100%;
  4202. \t\theight: 100%;
  4203. \t\tobject-fit: cover; /* Recadre l'image pour remplir le conteneur */
  4204. \t\tobject-position: center; /* Centre l'image */
  4205. \t\tdisplay: block;
  4206. \t\ttransition: transform 0.3s ease;
  4207. \t}
  4208. \t.category-banner-container:hover .category-banner-img {
  4209. \t\ttransform: scale(1.05);
  4210. \t}
  4211. \t/* Styles pour les images de la modal Quick Product View */
  4212. \t.quick-view-carousel .item {
  4213. \t\twidth: 100%;
  4214. \t\theight: 400px; /* Hauteur fixe pour les images du carousel */
  4215. \t\tbackground-size: cover;
  4216. \t\tbackground-position: center;
  4217. \t\tbackground-repeat: no-repeat;
  4218. \t\tborder-radius: 8px;
  4219. \t}
  4220. \t/* Contraindre le conteneur d'image pour maintenir les proportions */
  4221. \t.product-image-container {
  4222. \t\tposition: relative;
  4223. \t\toverflow: hidden;
  4224. \t\tborder-radius: 8px;
  4225. \t\twidth: 200px;
  4226. \t\taspect-ratio: 1 / 1; /* Conteneur carré */
  4227. \t\tbackground: #f8f9fa; /* Fond au cas où l'image ne charge pas */
  4228. \t}
  4229. \t/* Styles pour le bouton de filtre amélioré */
  4230. \t.filter-icon-circle {
  4231. \t\ttransition: all 0.3s ease;
  4232. \t}
  4233. \tbutton[data-bs-target=\"#filtersModal\"]:hover .filter-icon-circle {
  4234. \t\tbackground: linear-gradient(135deg, #ff8c00 0%, #ff7700 100%) !important;
  4235. \t\ttransform: rotate(15deg) scale(1.1);
  4236. \t\tbox-shadow: 0 4px 15px rgba(255, 162, 0, 0.5);
  4237. \t}
  4238. \tbutton[data-bs-target=\"#filtersModal\"]:hover {
  4239. \t\ttransform: translateY(-2px);
  4240. \t}
  4241. \tbutton[data-bs-target=\"#filtersModal\"]:active {
  4242. \t\ttransform: translateY(0);
  4243. \t}
  4244. \tbutton[data-bs-target=\"#filtersModal\"]:hover span {
  4245. \t\tcolor: #ffa200 !important;
  4246. \t}
  4247. \t/* Styles pour le modal de filtres */
  4248. \t#filtersModal .modal-body {
  4249. \t\tpadding: 1.5rem;
  4250. \t}
  4251. \t#filtersModal .modal-body::-webkit-scrollbar {
  4252. \t\twidth: 8px;
  4253. \t}
  4254. \t#filtersModal .modal-body::-webkit-scrollbar-track {
  4255. \t\tbackground: #f1f1f1;
  4256. \t\tborder-radius: 4px;
  4257. \t}
  4258. \t#filtersModal .modal-body::-webkit-scrollbar-thumb {
  4259. \t\tbackground: #ffa200;
  4260. \t\tborder-radius: 4px;
  4261. \t}
  4262. \t#filtersModal .modal-body::-webkit-scrollbar-thumb:hover {
  4263. \t\tbackground: #e69100;
  4264. \t}
  4265. \t#filtersModal .modal-footer {
  4266. \t\tbox-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
  4267. \t}
  4268. \t/* Styles pour les filtres avancés */
  4269. \t.weight-range-area {
  4270. \t\tpadding: 15px 0;
  4271. \t}
  4272. \t.weight-inputs {
  4273. \t\tgap: 15px;
  4274. \t\talign-items: center;
  4275. \t}
  4276. \t.weight-input {
  4277. \t\tflex: 1;
  4278. \t}
  4279. \t.weight-input label {
  4280. \t\tdisplay: block;
  4281. \t\tmargin-bottom: 5px;
  4282. \t\tfont-weight: 500;
  4283. \t\tcolor: #333;
  4284. \t}
  4285. \t.weight-input input {
  4286. \t\twidth: 100%;
  4287. \t\tpadding: 8px 12px;
  4288. \t\tborder: 1px solid #ddd;
  4289. \t\tborder-radius: 4px;
  4290. \t\tfont-size: 14px;
  4291. \t}
  4292. \t.weight-input input:focus {
  4293. \t\toutline: none;
  4294. \t\tborder-color: #ffa200;
  4295. \t\tbox-shadow: 0 0 0 2px rgba(255, 162, 0, 0.2);
  4296. \t}
  4297. \t/* Styles pour les filtres dynamiques */
  4298. \t.shop-list,
  4299. \t.color-list,
  4300. \t.size-list,
  4301. \t.material-list,
  4302. \t.condition-list {
  4303. \t\tmax-height: 200px;
  4304. \t\toverflow-y: auto;
  4305. \t\tpadding-right: 5px;
  4306. \t}
  4307. \t.shop-list::-webkit-scrollbar,
  4308. \t.color-list::-webkit-scrollbar,
  4309. \t.size-list::-webkit-scrollbar,
  4310. \t.material-list::-webkit-scrollbar,
  4311. \t.condition-list::-webkit-scrollbar {
  4312. \t\twidth: 6px;
  4313. \t}
  4314. \t.shop-list::-webkit-scrollbar-track,
  4315. \t.color-list::-webkit-scrollbar-track,
  4316. \t.size-list::-webkit-scrollbar-track,
  4317. \t.material-list::-webkit-scrollbar-track,
  4318. \t.condition-list::-webkit-scrollbar-track {
  4319. \t\tbackground: #f1f1f1;
  4320. \t\tborder-radius: 3px;
  4321. \t}
  4322. \t.shop-list::-webkit-scrollbar-thumb,
  4323. \t.color-list::-webkit-scrollbar-thumb,
  4324. \t.size-list::-webkit-scrollbar-thumb,
  4325. \t.material-list::-webkit-scrollbar-thumb,
  4326. \t.condition-list::-webkit-scrollbar-thumb {
  4327. \t\tbackground: #ffa200;
  4328. \t\tborder-radius: 3px;
  4329. \t}
  4330. \t.shop-list::-webkit-scrollbar-thumb:hover,
  4331. \t.color-list::-webkit-scrollbar-thumb:hover,
  4332. \t.size-list::-webkit-scrollbar-thumb:hover,
  4333. \t.material-list::-webkit-scrollbar-thumb:hover,
  4334. \t.condition-list::-webkit-scrollbar-thumb:hover {
  4335. \t\tbackground: #e69100;
  4336. \t}
  4337. \t/* Animation pour les filtres qui apparaissent */
  4338. \t.common-filter {
  4339. \t\ttransition: all 0.3s ease;
  4340. \t}
  4341. \t.common-filter[style*=\"display: none\"] {
  4342. \t\topacity: 0;
  4343. \t\ttransform: translateY(-10px);
  4344. \t}
  4345. \t.common-filter[style*=\"display: block\"] {
  4346. \t\topacity: 1;
  4347. \t\ttransform: translateY(0);
  4348. \t}
  4349. \t/* Styles pour les indicateurs de chargement */
  4350. \t.loading-indicator {
  4351. \t\tdisplay: inline-block;
  4352. \t\twidth: 20px;
  4353. \t\theight: 20px;
  4354. \t\tborder: 3px solid #f3f3f3;
  4355. \t\tborder-top: 3px solid #ffa200;
  4356. \t\tborder-radius: 50%;
  4357. \t\tanimation: spin 1s linear infinite;
  4358. \t}
  4359. \t@keyframes spin {
  4360. \t\t0% {
  4361. \t\t\ttransform: rotate(0deg);
  4362. \t\t}
  4363. \t\t100% {
  4364. \t\t\ttransform: rotate(360deg);
  4365. \t\t}
  4366. \t}
  4367. \t/* Styles pour le loader de chargement infini */
  4368. \t.infinite-loader {
  4369. \t\tdisplay: flex;
  4370. \t\tflex-direction: column;
  4371. \t\talign-items: center;
  4372. \t\tjustify-content: center;
  4373. \t\tpadding: 20px;
  4374. \t}
  4375. \t.loader-spinner {
  4376. \t\twidth: 50px;
  4377. \t\theight: 50px;
  4378. \t\tborder: 4px solid #f3f3f3;
  4379. \t\tborder-top: 4px solid #ffa200;
  4380. \t\tborder-radius: 50%;
  4381. \t\tanimation: spin 1s linear infinite;
  4382. \t}
  4383. \t#infiniteScrollLoader {
  4384. \t\tmin-height: 100px;
  4385. \t}
  4386. \t#noMoreProducts {
  4387. \t\tmin-height: 60px;
  4388. \t\tcolor: #6c757d;
  4389. \t}
  4390. \t#noMoreProducts i {
  4391. \t\tfont-size: 24px;
  4392. \t\tcolor: #28a745;
  4393. \t\tmargin-right: 8px;
  4394. \t}
  4395. \t/* Responsive pour les filtres */
  4396. \t@media(max-width: 768px) {
  4397. \t\t.weight-inputs {
  4398. \t\t\tflex-direction: column;
  4399. \t\t\tgap: 10px;
  4400. \t\t}
  4401. \t\t.weight-input {
  4402. \t\t\twidth: 100%;
  4403. \t\t}
  4404. \t\t.common-filter {
  4405. \t\t\tmargin-bottom: 20px;
  4406. \t\t}
  4407. \t\t.shop-list,
  4408. \t\t.color-list,
  4409. \t\t.size-list,
  4410. \t\t.material-list,
  4411. \t\t.condition-list {
  4412. \t\t\tmax-height: 150px;
  4413. \t\t}
  4414. \t}
  4415. \t/* Bouton toggle sidebar mobile */
  4416. \t.listing-sidebar-toggle {
  4417. \t\tborder-radius: 0.5rem;
  4418. \t\tpadding: 0.75rem;
  4419. \t\tfont-weight: 500;
  4420. \t\tdisplay: flex;
  4421. \t\talign-items: center;
  4422. \t\tjustify-content: center;
  4423. \t}
  4424. \t.listing-sidebar-toggle .toggle-icon {
  4425. \t\ttransition: transform 0.3s ease;
  4426. \t\tdisplay: inline-block;
  4427. \t}
  4428. \t/* État fermé (par défaut) */
  4429. \t.listing-sidebar-toggle[aria-expanded=\"false\"] .toggle-icon,
  4430. \t.listing-sidebar-toggle.collapsed .toggle-icon {
  4431. \t\ttransform: rotate(0deg);
  4432. \t}
  4433. \t/* État ouvert */
  4434. \t.listing-sidebar-toggle[aria-expanded=\"true\"] .toggle-icon,
  4435. \t.listing-sidebar-toggle:not(.collapsed) .toggle-icon {
  4436. \t\ttransform: rotate(180deg);
  4437. \t}
  4438. \t/* Sidebar mobile */
  4439. \t@media(max-width: 767.98px) {
  4440. \t\t#listingSidebarCollapse {
  4441. \t\t\tmargin-bottom: 1rem;
  4442. \t\t}
  4443. \t}
  4444. </style>{% endblock %}{% block javascripts %}
  4445. <script>
  4446. // === Script isolé : chargement de plus de produits (comme les boutiques) ===
  4447. var currentPage = {{ currentPage }};
  4448. var totalPages = {{ totalPages }};
  4449. var isLoading = false;
  4450. function loadMoreProducts() {
  4451.     if (isLoading || currentPage >= totalPages) return;
  4452.     isLoading = true;
  4453.     var btn = document.getElementById('loadMoreBtn');
  4454.     var productsContainer = document.getElementById('productsContainer');
  4455.     if (!btn || !productsContainer) {
  4456.         isLoading = false;
  4457.         return;
  4458.     }
  4459.     var originalText = btn.innerHTML;
  4460.     btn.disabled = true;
  4461.     btn.innerHTML = '<span class=\"spinner-border spinner-border-sm me-2\"></span>Chargement...';
  4462.     currentPage++;
  4463.     var url = new URL(window.location.href);
  4464.     url.searchParams.set('page', currentPage);
  4465.     fetch(url.toString(), {
  4466.         headers: { 'X-Requested-With': 'XMLHttpRequest' }
  4467.     })
  4468.     .then(function(response) { return response.json(); })
  4469.     .then(function(data) {
  4470.         if (data.success && data.products) {
  4471.             var tempDiv = document.createElement('div');
  4472.             tempDiv.innerHTML = data.products;
  4473.             var newProducts = tempDiv.querySelectorAll('.col-lg-4, .col-lg-3, [class*=\"col-lg\"]');
  4474.             newProducts.forEach(function(product) {
  4475.                 productsContainer.appendChild(product);
  4476.             });
  4477.             currentPage = data.pagination.currentPage;
  4478.             totalPages = data.pagination.totalPages;
  4479.             // Initialiser les images et events pour les nouveaux produits
  4480.             if (typeof initializeProductImages === 'function') initializeProductImages(tempDiv);
  4481.             if (typeof initializeProductEventListeners === 'function') initializeProductEventListeners(tempDiv);
  4482.             if (currentPage >= totalPages) {
  4483.                 document.getElementById('loadMoreContainer').style.display = 'none';
  4484.             } else {
  4485.                 btn.disabled = false;
  4486.                 btn.innerHTML = originalText;
  4487.             }
  4488.         } else {
  4489.             document.getElementById('loadMoreContainer').style.display = 'none';
  4490.         }
  4491.         isLoading = false;
  4492.     })
  4493.     .catch(function(error) {
  4494.         btn.disabled = false;
  4495.         btn.innerHTML = originalText;
  4496.         isLoading = false;
  4497.     });
  4498. }
  4499. </script>
  4500. <script>
  4501. \t// Variables globales pour la navigation des images
  4502. const productImages = {};
  4503. const currentImageIndex = {};
  4504. // Variables pour le chargement infini (utilise les var globales définies au-dessus)
  4505. let hasMoreProducts = totalPages > currentPage;
  4506. let currentFilters = {
  4507. category: '{{ currentCategory ?? '' }}',
  4508. brand: '{{ currentBrand ?? '' }}',
  4509. sort: '{{ currentSort ?? 'newest' }}',
  4510. priceMin: '{{ priceMin ?? '' }}',
  4511. priceMax: '{{ priceMax ?? '' }}'
  4512. };
  4513. // Modal style eBay pour remplacer les alertes - Définir AVANT son utilisation
  4514. function showEbayModal(message, type = 'info') {
  4515. const modalId = 'ebayAlertModal';
  4516. let existingModal = document.getElementById(modalId);
  4517. if (existingModal) {
  4518. existingModal.remove();
  4519. }
  4520. const modal = document.createElement('div');
  4521. modal.id = modalId;
  4522. modal.className = 'modal fade';
  4523. modal.setAttribute('tabindex', '-1');
  4524. modal.setAttribute('aria-labelledby', 'ebayAlertModalLabel');
  4525. modal.setAttribute('aria-hidden', 'true');
  4526. // Définir les couleurs et icônes selon le type
  4527. let iconClass = 'ti-info-alt';
  4528. let iconColor = '#0064D2';
  4529. let title = 'Information';
  4530. switch (type) {
  4531. case 'success': iconClass = 'ti-check-box';
  4532. iconColor = '#0A7C42';
  4533. title = 'Succès';
  4534. break;
  4535. case 'error':
  4536. case 'danger': iconClass = 'ti-alert';
  4537. iconColor = '#D32F2F';
  4538. title = 'Erreur';
  4539. break;
  4540. case 'warning': iconClass = 'ti-alert-triangle';
  4541. iconColor = '#F57C00';
  4542. title = 'Attention';
  4543. break;
  4544. default: iconClass = 'ti-info-alt';
  4545. iconColor = '#0064D2';
  4546. title = 'Information';
  4547. }
  4548. modal.innerHTML = `
  4549. <div class=\"modal-dialog modal-dialog-centered\">
  4550. <div class=\"modal-content\" style=\"border-radius: 8px; border: none; box-shadow: 0 4px 20px rgba(0,0,0,0.15);\">
  4551. <div class=\"modal-header\" style=\"border-bottom: 1px solid #e0e0e0; padding: 20px 24px;\">
  4552. <h5 class=\"modal-title\" id=\"ebayAlertModalLabel\" style=\"font-weight: 600; font-size: 18px; color: #333;\">
  4553. <i class=\"\${iconClass}\" style=\"color: \${iconColor}; margin-right: 8px;\"></i>\${title}
  4554. </h5>
  4555. <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\" style=\"margin: 0;\"></button>
  4556. </div>
  4557. <div class=\"modal-body\" style=\"padding: 24px; text-align: center;\">
  4558. <p style=\"margin: 0; font-size: 16px; color: #333; line-height: 1.5;\">\${message}</p>
  4559. </div>
  4560. <div class=\"modal-footer\" style=\"border-top: 1px solid #e0e0e0; padding: 16px 24px; justify-content: center;\">
  4561. <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;\">
  4562. OK
  4563. </button>
  4564. </div>
  4565. </div>
  4566. </div>
  4567. `;
  4568. document.body.appendChild(modal);
  4569. const bsModal = new bootstrap.Modal(modal);
  4570. bsModal.show();
  4571. // Supprimer le modal du DOM après fermeture
  4572. modal.addEventListener('hidden.bs.modal', function () {
  4573. modal.remove();
  4574. });
  4575. }
  4576. // Rendre la fonction globale
  4577. window.showEbayModal = showEbayModal;
  4578. document.addEventListener('DOMContentLoaded', function () { // Initialisation des données d'images pour chaque produit
  4579. {% for product in products %}
  4580. {% set allImages = product.images|default([]) %}
  4581. {% if product.variants is defined %}
  4582. \t{% for variant in product.variants %}
  4583. \t\t{% if variant.isActive and variant.images is defined %}
  4584. \t\t\t{% for variantImg in variant.images %}
  4585. \t\t\t\t{% set allImages = allImages|merge([variantImg]) %}
  4586. \t\t\t{% endfor %}
  4587. \t\t{% endif %}
  4588. \t{% endfor %}
  4589. {% endif %}
  4590. {% if allImages|length > 0 %}productImages[{{ product.id }}] = [{% for img in allImages %}'{{ asset(img) }}'{% if not loop.last %},{% endif %}{% endfor %}];
  4591. currentImageIndex[{{ product.id }}] = 0;
  4592. console.log('Initialized product {{ product.id }} with images:', productImages[{{ product.id }}]);{% endif %}{% endfor %}
  4593. // Gestion du panier
  4594. const cartButtons = document.querySelectorAll('.add-to-cart');
  4595. cartButtons.forEach(button => {
  4596. button.addEventListener('click', function () {
  4597. const productId = this.getAttribute('data-product-id');
  4598. const qty = this.getAttribute('data-qty');
  4599. fetch('{{ path(\"ui_cart_add\") }}', {
  4600. method: 'POST',
  4601. headers: {
  4602. 'Content-Type': 'application/x-www-form-urlencoded'
  4603. },
  4604. body: 'productId=' + productId + '&qty=' + qty
  4605. }).then(response => response.json()).then(data => {
  4606. if (data.ok) {
  4607. showEbayModal('Produit ajouté au panier !', 'success');
  4608. // Mettre à jour le compteur du panier si présent
  4609. const cartBadge = document.querySelector('.cart-badge');
  4610. if (cartBadge) {
  4611. cartBadge.textContent = data.totalQty;
  4612. }
  4613. } else {
  4614. showEbayModal(data.message || 'Erreur lors de l\\'ajout au panier', 'error');
  4615. }
  4616. }).catch(error => {
  4617. console.error('Erreur:', error);
  4618. showEbayModal('Erreur lors de l\\'ajout au panier', 'error');
  4619. });
  4620. });
  4621. });
  4622. });
  4623. // Fonctions pour la navigation des images
  4624. window.showImageNav = function(productId) {
  4625. const leftBtn = document.getElementById('img-left-' + productId);
  4626. const rightBtn = document.getElementById('img-right-' + productId);
  4627. const indicators = document.getElementById('img-indicators-' + productId);
  4628. if (leftBtn) {
  4629. leftBtn.style.display = 'flex';
  4630. }
  4631. if (rightBtn) {
  4632. rightBtn.style.display = 'flex';
  4633. }
  4634. if (indicators) {
  4635. indicators.style.display = 'flex';
  4636. }
  4637. };
  4638. window.hideImageNav = function(productId) {
  4639. const leftBtn = document.getElementById('img-left-' + productId);
  4640. const rightBtn = document.getElementById('img-right-' + productId);
  4641. const indicators = document.getElementById('img-indicators-' + productId);
  4642. if (leftBtn) {
  4643. leftBtn.style.display = 'none';
  4644. }
  4645. if (rightBtn) {
  4646. rightBtn.style.display = 'none';
  4647. }
  4648. if (indicators) {
  4649. indicators.style.display = 'none';
  4650. }
  4651. };
  4652. window.showImage = function(productId, imageIndex) {
  4653. if (! productImages[productId] || ! productImages[productId][imageIndex]) {
  4654. console.log('Image not found for product', productId, 'index', imageIndex);
  4655. return;
  4656. }
  4657. const img = document.getElementById('main-img-' + productId);
  4658. if (img) {
  4659. console.log('Changing image to:', productImages[productId][imageIndex]);
  4660. // Force le rechargement de l'image
  4661. img.style.opacity = '0.5';
  4662. setTimeout(() => {
  4663. img.src = productImages[productId][imageIndex];
  4664. img.style.opacity = '1';
  4665. currentImageIndex[productId] = imageIndex;
  4666. updateIndicators(productId);
  4667. }, 100);
  4668. } else {
  4669. console.log('Image element not found:', 'main-img-' + productId);
  4670. }
  4671. };
  4672. window.nextImage = function(productId) {
  4673. if (! productImages[productId]) {
  4674. console.log('No images for product', productId);
  4675. return;
  4676. }
  4677. const nextIndex = (currentImageIndex[productId] + 1) % productImages[productId].length;
  4678. showImage(productId, nextIndex);
  4679. };
  4680. window.prevImage = function(productId) {
  4681. if (! productImages[productId]) {
  4682. console.log('No images for product', productId);
  4683. return;
  4684. }
  4685. const prevIndex = currentImageIndex[productId] === 0 ? productImages[productId].length - 1 : currentImageIndex[productId] - 1;
  4686. showImage(productId, prevIndex);
  4687. };
  4688. function updateIndicators(productId) {
  4689. const indicators = document.querySelectorAll('#img-indicators-' + productId + ' .indicator');
  4690. indicators.forEach((indicator, index) => {
  4691. indicator.classList.toggle('active', index === currentImageIndex[productId]);
  4692. });
  4693. }
  4694. // Fonction pour tracker la vue d'un produit
  4695. function trackProductView(productId) {
  4696. fetch (`/api/track-product-view/\${productId}`, {
  4697. method: 'POST',
  4698. headers: {
  4699. 'Content-Type': 'application/json',
  4700. 'X-Requested-With': 'XMLHttpRequest'
  4701. }
  4702. }).then(response => response.json()).then(data => {
  4703. if (data.success) {
  4704. console.log (`Vue enregistrée pour le produit \${productId}:`, data.viewCount);
  4705. }
  4706. }).catch(error => {
  4707. console.error('Erreur lors du tracking:', error);
  4708. });
  4709. }
  4710. // Fonction pour tracker la vue d'une boutique
  4711. function trackShopView(shopId) {
  4712. fetch (`/api/track-shop-view/\${shopId}`, {
  4713. method: 'POST',
  4714. headers: {
  4715. 'Content-Type': 'application/json',
  4716. 'X-Requested-With': 'XMLHttpRequest'
  4717. }
  4718. }).then(response => response.json()).then(data => {
  4719. if (data.success) {
  4720. console.log (`Vue enregistrée pour la boutique \${shopId}:`, data.viewCount);
  4721. }
  4722. }).catch(error => {
  4723. console.error('Erreur lors du tracking:', error);
  4724. });
  4725. }
  4726. // Ajouter le tracking aux événements existants
  4727. document.addEventListener('DOMContentLoaded', function () { // Tracking au survol des produits (avec délai)
  4728. const productCards = document.querySelectorAll('.single_product_item');
  4729. productCards.forEach(card => {
  4730. const productId = card.dataset.productId;
  4731. const shopId = card.dataset.shopId;
  4732. if (productId) {
  4733. let hoverTimeout;
  4734. card.addEventListener('mouseenter', () => { // Tracker la vue du produit après 2 secondes de survol
  4735. hoverTimeout = setTimeout(() => {
  4736. trackProductView(productId);
  4737. }, 2000);
  4738. });
  4739. card.addEventListener('mouseleave', () => {
  4740. clearTimeout(hoverTimeout);
  4741. });
  4742. // Tracking au clic sur le lien du produit
  4743. const productLink = card.querySelector('.product_img a');
  4744. if (productLink) {
  4745. productLink.addEventListener('click', () => {
  4746. trackProductView(productId);
  4747. });
  4748. }
  4749. }
  4750. // Tracking au clic sur le lien de la boutique
  4751. if (shopId) {
  4752. const shopLink = card.querySelector('.shop-link');
  4753. if (shopLink) {
  4754. shopLink.addEventListener('click', () => {
  4755. trackShopView(shopId);
  4756. });
  4757. }
  4758. }
  4759. });
  4760. });
  4761. </script>
  4762. <script>
  4763. \t// Système de filtrage AJAX avancé
  4764. // currentFilters est déjà déclaré plus haut, on met juste à jour les valeurs
  4765. if (typeof currentFilters !== 'undefined') { // Mettre à jour les valeurs existantes
  4766. currentFilters.category = '{{ currentCategory }}';
  4767. currentFilters.brand = '{{ currentBrand }}';
  4768. currentFilters.condition = '{{ currentCondition }}';
  4769. currentFilters.sort = '{{ currentSort }}';
  4770. currentFilters.priceMin = '{{ priceMin }}';
  4771. currentFilters.priceMax = '{{ priceMax }}';
  4772. currentFilters.page = {{ currentPage }};
  4773. currentFilters.q = currentFilters.q || '{{ app.request.query.get('q', '') }}';
  4774. // Ajouter les nouveaux filtres depuis l'URL s'ils n'existent pas
  4775. if (! currentFilters.shop) 
  4776. currentFilters.shop = '{{ app.request.query.get('shop', '') }}';
  4777. if (! currentFilters.featured) 
  4778. currentFilters.featured = '{{ app.request.query.get('featured', '') }}';
  4779. if (! currentFilters.digital) 
  4780. currentFilters.digital = '{{ app.request.query.get('digital', '') }}';
  4781. if (! currentFilters.stockStatus) 
  4782. currentFilters.stockStatus = '{{ app.request.query.get('stock_status', '') }}';
  4783. if (! currentFilters.ratingMin) 
  4784. currentFilters.ratingMin = '{{ app.request.query.get('rating_min', '') }}';
  4785. if (! currentFilters.rating) 
  4786. currentFilters.rating = '{{ app.request.query.get('rating_min', '') }}';
  4787. if (! currentFilters.weightMin) 
  4788. currentFilters.weightMin = '{{ app.request.query.get('weight_min', '') }}';
  4789. if (! currentFilters.weightMax) 
  4790. currentFilters.weightMax = '{{ app.request.query.get('weight_max', '') }}';
  4791. if (! currentFilters.color) 
  4792. currentFilters.color = '{{ app.request.query.get('color', '') }}';
  4793. if (! currentFilters.size) 
  4794. currentFilters.size = '{{ app.request.query.get('size', '') }}';
  4795. if (! currentFilters.material) 
  4796. currentFilters.material = '{{ app.request.query.get('material', '') }}';
  4797. if (! currentFilters.condition) 
  4798. currentFilters.condition = '{{ app.request.query.get('condition', '') }}';
  4799. if (! currentFilters.availability) 
  4800. currentFilters.availability = '{{ app.request.query.get('availability', '') }}';
  4801. } else { // Si currentFilters n'existe pas, le créer avec les valeurs de l'URL
  4802. let currentFilters = {
  4803. category: '{{ currentCategory ?? '' }}',
  4804. brand: '{{ currentBrand ?? '' }}',
  4805. condition: '{{ currentCondition ?? '' }}',
  4806. sort: '{{ currentSort ?? 'newest' }}',
  4807. priceMin: '{{ app.request.query.get('price_min', '') }}',
  4808. priceMax: '{{ app.request.query.get('price_max', '') }}',
  4809. page: {{ currentPage }},
  4810. q: '{{ app.request.query.get('q', '') }}',
  4811. // Nouveaux filtres depuis l'URL
  4812. shop: '{{ app.request.query.get('shop', '') }}',
  4813. featured: '{{ app.request.query.get('featured', '') }}',
  4814. digital: '{{ app.request.query.get('digital', '') }}',
  4815. stockStatus: '{{ app.request.query.get('stock_status', '') }}',
  4816. ratingMin: '{{ app.request.query.get('rating_min', '') }}',
  4817. rating: '{{ app.request.query.get('rating_min', '') }}',
  4818. weightMin: '{{ app.request.query.get('weight_min', '') }}',
  4819. weightMax: '{{ app.request.query.get('weight_max', '') }}',
  4820. color: '{{ app.request.query.get('color', '') }}',
  4821. size: '{{ app.request.query.get('size', '') }}',
  4822. material: '{{ app.request.query.get('material', '') }}',
  4823. condition: '{{ app.request.query.get('condition', '') }}',
  4824. availability: '{{ app.request.query.get('availability', '') }}'
  4825. };
  4826. }
  4827. // Fonction pour appliquer les filtres
  4828. function applyFilters() {
  4829. const url = new URL('{{ path('ui_api_products_filter') }}', window.location.origin);
  4830. // Ajouter les paramètres
  4831. Object.keys(currentFilters).forEach(key => {
  4832. if (currentFilters[key] && currentFilters[key] !== '') { // Mapper les clés pour l'API
  4833. let apiKey = key;
  4834. if (key === 'priceMin') {
  4835. apiKey = 'price_min';
  4836. } else if (key === 'priceMax') {
  4837. apiKey = 'price_max';
  4838. } else if (key === 'ratingMin') {
  4839. apiKey = 'rating_min';
  4840. } else if (key === 'weightMin') {
  4841. apiKey = 'weight_min';
  4842. } else if (key === 'weightMax') {
  4843. apiKey = 'weight_max';
  4844. } else if (key === 'stockStatus') {
  4845. apiKey = 'stock_status';
  4846. }
  4847. url.searchParams.set(apiKey, currentFilters[key]);
  4848. }
  4849. });
  4850. // Afficher le loader
  4851. document.getElementById('productsContainer').innerHTML = '<div class=\"col-12 text-center\"><i class=\"fa fa-spinner fa-spin\"></i> Chargement...</div>';
  4852. // Faire la requête AJAX
  4853. fetch(url).then(response => response.json()).then(data => {
  4854. if (data.success) { // Mettre à jour les produits
  4855. document.getElementById('productsContainer').innerHTML = data.products;
  4856. // Mettre à jour la pagination
  4857. currentPage = data.pagination.currentPage;
  4858. totalPages = data.pagination.totalPages;
  4859. hasMoreProducts = currentPage < totalPages;
  4860. // Préserver l'état des filtres statiques avant la mise à jour
  4861. const preservedFilters = {
  4862. featured: currentFilters.featured,
  4863. digital: currentFilters.digital,
  4864. availability: currentFilters.availability,
  4865. rating: currentFilters.rating || currentFilters.ratingMin
  4866. };
  4867. // Mettre à jour les marques disponibles (toujours afficher toutes)
  4868. updateAvailableBrands(data.availableBrands || []);
  4869. // Mettre à jour les conditions disponibles (toujours afficher toutes)
  4870. updateAvailableConditions(data.availableConditions || []);
  4871. // Mettre à jour les boutiques disponibles (toujours afficher toutes)
  4872. updateAvailableShops(data.availableShops || []);
  4873. // Mettre à jour les attributs disponibles (toujours afficher tous)
  4874. updateAvailableAttributes(data.availableAttributes || {});
  4875. // Restaurer l'état des filtres statiques après la mise à jour
  4876. restoreStaticFiltersState(preservedFilters);
  4877. // Réinitialiser les filtres depuis currentFilters pour s'assurer qu'ils sont tous cochés
  4878. if (typeof initializeFiltersFromURL === 'function') {
  4879. initializeFiltersFromURL();
  4880. }
  4881. // Mettre à jour l'URL
  4882. updateURL();
  4883. // Mettre à jour l'affichage du bouton
  4884. updateLoadMoreButton();
  4885. // Réinitialiser les event listeners pour les nouveaux produits
  4886. const tempDiv = document.createElement('div');
  4887. tempDiv.innerHTML = data.products;
  4888. initializeProductEventListeners(tempDiv);
  4889. initializeProductImages(tempDiv);
  4890. // Mettre à jour les filtres actifs
  4891. if (typeof updateActiveFilters === 'function') {
  4892. updateActiveFilters();
  4893. }
  4894. }
  4895. }).catch(error => {
  4896. console.error('Erreur lors du filtrage:', error);
  4897. document.getElementById('productsContainer').innerHTML = '<div class=\"col-12 text-center text-danger\">Erreur lors du chargement des produits</div>';
  4898. });
  4899. }
  4900. // Exposer la fonction globalement
  4901. window.applyFilters = applyFilters;
  4902. // Fonction pour appliquer le tri
  4903. function applySorting() {
  4904. currentFilters.sort = document.getElementById('sortSelect').value;
  4905. currentFilters.page = 1;
  4906. applyFilters();
  4907. }
  4908. // Fonction pour appliquer le filtre de prix
  4909. function applyPriceFilter() {
  4910. currentFilters.priceMin = document.getElementById('priceMin').value;
  4911. currentFilters.priceMax = document.getElementById('priceMax').value;
  4912. currentFilters.page = 1;
  4913. applyFilters();
  4914. }
  4915. // Fonction pour appliquer le filtre de poids
  4916. function applyWeightFilter() {
  4917. currentFilters.weightMin = document.getElementById('weightMin').value;
  4918. currentFilters.weightMax = document.getElementById('weightMax').value;
  4919. currentFilters.page = 1;
  4920. applyFilters();
  4921. }
  4922. // Fonction pour changer de page
  4923. function changePage(page) {
  4924. currentFilters.page = page;
  4925. applyFilters();
  4926. }
  4927. // Fonction pour mettre à jour les marques disponibles
  4928. function updateAvailableBrands(brands) {
  4929. const brandsFilter = document.getElementById('brandsFilter');
  4930. const brandList = document.getElementById('brandList');
  4931. const currentBrandValue = currentFilters.brand || '';
  4932. // Toujours afficher le filtre des marques
  4933. brandsFilter.style.display = 'block';
  4934. // Si pas de marques disponibles, garder les marques existantes ou afficher un message
  4935. if (brands.length === 0) { // Ne pas réinitialiser si la liste existe déjà et a du contenu
  4936. if (brandList && brandList.children.length > 0) { // Juste mettre à jour les états cochés
  4937. const existingRadios = brandList.querySelectorAll('input[name=\"brand\"]');
  4938. existingRadios.forEach(radio => {
  4939. if (currentBrandValue && radio.value === currentBrandValue) {
  4940. radio.checked = true;
  4941. } else if (! currentBrandValue && radio.id === 'all-brands') {
  4942. radio.checked = true;
  4943. } else {
  4944. radio.checked = false;
  4945. }
  4946. });
  4947. return; // Garder les marques existantes
  4948. }
  4949. // Sinon, afficher un message
  4950. brandList.innerHTML = '<li class=\"filter-list\"><span class=\"text-muted\">Aucune marque disponible</span></li>';
  4951. return;
  4952. }
  4953. let html = '';
  4954. const isAllSelected = ! currentBrandValue || currentBrandValue === '';
  4955. html += `<li class=\"filter-list\"><input class=\"pixel-radio\" type=\"radio\" id=\"all-brands\" name=\"brand\" \${
  4956. isAllSelected ? 'checked' : ''
  4957. }><label for=\"all-brands\">Toutes les marques</label></li>`;
  4958. brands.forEach(brand => {
  4959. const isSelected = currentBrandValue === brand.slug;
  4960. html += `<li class=\"filter-list\">
  4961.                 <input class=\"pixel-radio\" type=\"radio\" id=\"brand-\${
  4962. brand.id
  4963. }\" name=\"brand\" value=\"\${
  4964. brand.slug
  4965. }\" \${
  4966. isSelected ? 'checked' : ''
  4967. }>
  4968.                 <label for=\"brand-\${
  4969. brand.id
  4970. }\">\${
  4971. brand.name
  4972. }<span>(\${
  4973. brand.productCount
  4974. })</span></label>
  4975.             </li>`;
  4976. });
  4977. brandList.innerHTML = html;
  4978. // Ajouter les event listeners
  4979. brandList.querySelectorAll('input[name=\"brand\"]').forEach(radio => {
  4980. radio.addEventListener('change', function () {
  4981. if (this.id === 'all-brands') {
  4982. currentFilters.brand = '';
  4983. } else {
  4984. currentFilters.brand = this.value;
  4985. } currentFilters.page = 1;
  4986. applyFilters();
  4987. });
  4988. });
  4989. }
  4990. // Fonction pour mettre à jour les conditions disponibles
  4991. function updateAvailableConditions(conditions) {
  4992. const conditionList = document.getElementById('conditionList');
  4993. const currentConditionValue = currentFilters.condition || '';
  4994. // Toujours garder le contenu statique et juste mettre à jour les états cochés
  4995. if (conditionList && conditionList.children.length > 0) {
  4996. const existingRadios = conditionList.querySelectorAll('input[name=\"condition\"]');
  4997. existingRadios.forEach(radio => {
  4998. if (currentConditionValue && radio.value === currentConditionValue) {
  4999. radio.checked = true;
  5000. } else if (! currentConditionValue && radio.id === 'all-conditions') {
  5001. radio.checked = true;
  5002. } else {
  5003. radio.checked = false;
  5004. }
  5005. });
  5006. return; // Garder les conditions existantes
  5007. }
  5008. // Si pas de contenu statique, générer dynamiquement
  5009. if (conditions.length === 0) {
  5010. conditionList.innerHTML = '<li class=\"filter-list\"><span class=\"text-muted\">Aucune condition disponible</span></li>';
  5011. return;
  5012. }
  5013. let html = '';
  5014. const isAllSelected = ! currentConditionValue || currentConditionValue === '';
  5015. html += `<li class=\"filter-list\"><input class=\"pixel-radio\" type=\"radio\" id=\"all-conditions\" name=\"condition\" \${
  5016. isAllSelected ? 'checked' : ''
  5017. }><label for=\"all-conditions\">Toutes les conditions</label></li>`;
  5018. conditions.forEach(condition => {
  5019. const isSelected = currentConditionValue === condition.slug;
  5020. html += `<li class=\"filter-list\">
  5021.                 <input class=\"pixel-radio\" type=\"radio\" id=\"condition-\${
  5022. condition.id
  5023. }\" name=\"condition\" value=\"\${
  5024. condition.slug
  5025. }\" \${
  5026. isSelected ? 'checked' : ''
  5027. }>
  5028.                 <label for=\"condition-\${
  5029. condition.id
  5030. }\">\${
  5031. condition.name
  5032. }<span>(\${
  5033. condition.productCount
  5034. })</span></label>
  5035.             </li>`;
  5036. });
  5037. conditionList.innerHTML = html;
  5038. // Ajouter les event listeners
  5039. conditionList.querySelectorAll('input[name=\"condition\"]').forEach(radio => {
  5040. radio.addEventListener('change', function () {
  5041. if (this.id === 'all-conditions') {
  5042. currentFilters.condition = '';
  5043. } else {
  5044. currentFilters.condition = this.value;
  5045. } currentFilters.page = 1;
  5046. applyFilters();
  5047. });
  5048. });
  5049. }
  5050. // Fonction pour mettre à jour les boutiques disponibles
  5051. function updateAvailableShops(shops) {
  5052. const shopsFilter = document.getElementById('shopsFilter');
  5053. const shopList = document.getElementById('shopList');
  5054. const currentShopValue = currentFilters.shop || '';
  5055. // Toujours afficher le filtre des boutiques
  5056. shopsFilter.style.display = 'block';
  5057. // Si pas de boutiques disponibles, afficher un message ou garder les boutiques existantes
  5058. if (shops.length === 0) { // Ne pas réinitialiser si la liste existe déjà et a du contenu
  5059. if (shopList && shopList.children.length > 0) {
  5060. return; // Garder les boutiques existantes
  5061. }
  5062. // Sinon, afficher un message
  5063. shopList.innerHTML = '<li class=\"filter-list\"><span class=\"text-muted\">Aucune boutique disponible</span></li>';
  5064. return;
  5065. }
  5066. let html = '';
  5067. const isAllSelected = ! currentShopValue || currentShopValue === '';
  5068. html += `<li class=\"filter-list\"><input class=\"pixel-radio\" type=\"radio\" id=\"all-shops\" name=\"shop\" \${
  5069. isAllSelected ? 'checked' : ''
  5070. }><label for=\"all-shops\">Toutes les boutiques</label></li>`;
  5071. shops.forEach(shop => {
  5072. const isSelected = currentShopValue === shop.slug;
  5073. html += `<li class=\"filter-list\">
  5074.                 <input class=\"pixel-radio\" type=\"radio\" id=\"shop-\${
  5075. shop.id
  5076. }\" name=\"shop\" value=\"\${
  5077. shop.slug
  5078. }\" \${
  5079. isSelected ? 'checked' : ''
  5080. }>
  5081.                 <label for=\"shop-\${
  5082. shop.id
  5083. }\">\${
  5084. shop.name
  5085. }<span>(\${
  5086. shop.productCount
  5087. })</span></label>
  5088.             </li>`;
  5089. });
  5090. shopList.innerHTML = html;
  5091. // Mettre à jour aussi le modal
  5092. const modalShopList = document.getElementById('modalShopList');
  5093. if (modalShopList) {
  5094. 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-');
  5095. }
  5096. // Ajouter les event listeners (seulement pour la sidebar, le modal gère ses propres listeners)
  5097. shopList.querySelectorAll('input[name=\"shop\"]').forEach(radio => {
  5098. radio.addEventListener('change', function () {
  5099. if (this.id === 'all-shops') {
  5100. currentFilters.shop = '';
  5101. } else {
  5102. currentFilters.shop = this.value;
  5103. } currentFilters.page = 1;
  5104. applyFilters();
  5105. });
  5106. });
  5107. }
  5108. // Fonction pour mettre à jour les attributs disponibles
  5109. function updateAvailableAttributes(attributes) {
  5110. // Toujours afficher tous les filtres d'attributs, même s'ils ne sont pas pertinents
  5111. // Couleurs
  5112. updateAttributeFilter('colors', attributes.colors, 'colorList', 'colorsFilter');
  5113. // Tailles
  5114. updateAttributeFilter('sizes', attributes.sizes, 'sizeList', 'sizesFilter');
  5115. // Matériaux
  5116. updateAttributeFilter('materials', attributes.materials, 'materialList', 'materialsFilter');
  5117. // Conditions
  5118. updateAttributeFilter('conditions', attributes.conditions, 'conditionList', 'conditionsFilter');
  5119. }
  5120. // Fonction générique pour mettre à jour les filtres d'attributs
  5121. function updateAttributeFilter(attributeType, attributes, listId, filterId) {
  5122. const filter = document.getElementById(filterId);
  5123. const list = document.getElementById(listId);
  5124. const currentValue = currentFilters[attributeType] || '';
  5125. // Toujours afficher les filtres d'attributs
  5126. filter.style.display = 'block';
  5127. // Si pas d'attributs disponibles, afficher un message ou garder les attributs existants
  5128. if (! attributes || attributes.length === 0) { // Ne pas réinitialiser si la liste existe déjà et a du contenu
  5129. if (list && list.children.length > 0) {
  5130. return; // Garder les attributs existants
  5131. }
  5132. // Sinon, afficher un message
  5133. list.innerHTML = '<li class=\"filter-list\"><span class=\"text-muted\">Aucun attribut disponible</span></li>';
  5134. return;
  5135. }
  5136. let html = '';
  5137. const isAllSelected = ! currentValue || currentValue === '';
  5138. html += `<li class=\"filter-list\"><input class=\"pixel-radio\" type=\"radio\" id=\"all-\${attributeType}\" name=\"\${attributeType}\" \${
  5139. isAllSelected ? 'checked' : ''
  5140. }><label for=\"all-\${attributeType}\">Tous</label></li>`;
  5141. attributes.forEach(attr => {
  5142. const isSelected = currentValue === attr.value;
  5143. html += `<li class=\"filter-list\">
  5144.                 <input class=\"pixel-radio\" type=\"radio\" id=\"\${attributeType}-\${
  5145. attr.value
  5146. }\" name=\"\${attributeType}\" value=\"\${
  5147. attr.value
  5148. }\" \${
  5149. isSelected ? 'checked' : ''
  5150. }>
  5151.                 <label for=\"\${attributeType}-\${
  5152. attr.value
  5153. }\">\${
  5154. attr.value
  5155. }<span>(\${
  5156. attr.count
  5157. })</span></label>
  5158.             </li>`;
  5159. });
  5160. list.innerHTML = html;
  5161. // Mettre à jour aussi le modal (si l'élément existe)
  5162. const modalListId = 'modal' + listId.charAt(0).toUpperCase() + listId.slice(1); // modalColorList, modalSizeList, etc.
  5163. const modalList = document.getElementById(modalListId);
  5164. if (modalList) { // Adapter le HTML pour le modal (changer les IDs et names)
  5165. 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}-`);
  5166. modalList.innerHTML = modalHtml;
  5167. }
  5168. // Ajouter les event listeners (seulement pour la sidebar, le modal gère ses propres listeners)
  5169. list.querySelectorAll (`input[name=\"\${attributeType}\"]`).forEach(radio => {
  5170. radio.addEventListener('change', function () {
  5171. if (this.id === `all-\${attributeType}`) {
  5172. currentFilters[attributeType] = '';
  5173. } else {
  5174. currentFilters[attributeType] = this.value;
  5175. } currentFilters.page = 1;
  5176. applyFilters();
  5177. });
  5178. });
  5179. } else {
  5180. filter.style.display = 'none';
  5181. }
  5182. }
  5183. // Fonction pour mettre à jour l'URL
  5184. function updateURL () {
  5185. const url = new URL(window.location);
  5186. // Supprimer les anciens paramètres
  5187. const paramsToRemove = [
  5188. 'category',
  5189. 'brand',
  5190. 'sort',
  5191. 'price_min',
  5192. 'price_max',
  5193. 'page',
  5194. 'shop',
  5195. 'featured',
  5196. 'digital',
  5197. 'stock_status',
  5198. 'rating_min',
  5199. 'weight_min',
  5200. 'weight_max',
  5201. 'color',
  5202. 'size',
  5203. 'material',
  5204. 'condition',
  5205. 'availability'
  5206. ];
  5207. paramsToRemove.forEach(param => url.searchParams.delete(param));
  5208. // Ajouter les nouveaux paramètres
  5209. Object.keys(currentFilters).forEach(key => {
  5210. if (currentFilters[key]) {
  5211. url.searchParams.set(key, currentFilters[key]);
  5212. }
  5213. });
  5214. // Mettre à jour l'URL sans recharger la page
  5215. window.history.pushState({}, '', url);
  5216. }
  5217. // Fonction pour restaurer l'état des filtres statiques
  5218. function restoreStaticFiltersState (preservedFilters) { // Restaurer featured
  5219. if (preservedFilters.featured) {
  5220. const featuredInput = document.querySelector (`input[name=\"featured\"][value=\"\${
  5221. preservedFilters.featured
  5222. }\"]`);
  5223. if (featuredInput) {
  5224. featuredInput.checked = true;
  5225. } else {
  5226. const featuredAll = document.getElementById('featured-all');
  5227. if (featuredAll) 
  5228. featuredAll.checked = true;
  5229. }
  5230. } else {
  5231. const featuredAll = document.getElementById('featured-all');
  5232. if (featuredAll) 
  5233. featuredAll.checked = true;
  5234. }
  5235. // Restaurer digital
  5236. if (preservedFilters.digital) {
  5237. const digitalInput = document.querySelector (`input[name=\"digital\"][value=\"\${
  5238. preservedFilters.digital
  5239. }\"]`);
  5240. if (digitalInput) {
  5241. digitalInput.checked = true;
  5242. } else {
  5243. const digitalAll = document.getElementById('digital-all');
  5244. if (digitalAll) 
  5245. digitalAll.checked = true;
  5246. }
  5247. } else {
  5248. const digitalAll = document.getElementById('digital-all');
  5249. if (digitalAll) 
  5250. digitalAll.checked = true;
  5251. }
  5252. // Restaurer availability
  5253. if (preservedFilters.availability) {
  5254. const availabilityInput = document.querySelector (`input[name=\"availability\"][value=\"\${
  5255. preservedFilters.availability
  5256. }\"]`);
  5257. if (availabilityInput) {
  5258. availabilityInput.checked = true;
  5259. } else {
  5260. const availabilityAll = document.getElementById('availability-all');
  5261. if (availabilityAll) 
  5262. availabilityAll.checked = true;
  5263. }
  5264. } else {
  5265. const availabilityAll = document.getElementById('availability-all');
  5266. if (availabilityAll) 
  5267. availabilityAll.checked = true;
  5268. }
  5269. // Restaurer rating (mapping vers ratingMin)
  5270. if (preservedFilters.rating) {
  5271. const ratingInput = document.querySelector (`input[name=\"rating\"][value=\"\${
  5272. preservedFilters.rating
  5273. }\"]`);
  5274. if (ratingInput) {
  5275. ratingInput.checked = true;
  5276. } else {
  5277. const ratingAll = document.getElementById('rating-all');
  5278. if (ratingAll) 
  5279. ratingAll.checked = true;
  5280. }
  5281. } else {
  5282. const ratingAll = document.getElementById('rating-all');
  5283. if (ratingAll) 
  5284. ratingAll.checked = true;
  5285. }
  5286. }
  5287. // Fonction pour initialiser tous les filtres avec les valeurs de l'URL
  5288. function initializeFiltersFromURL () { // Initialiser les filtres statiques
  5289. const staticFilters = {
  5290. featured: currentFilters.featured || '',
  5291. digital: currentFilters.digital || '',
  5292. availability: currentFilters.availability || '',
  5293. rating: currentFilters.rating || currentFilters.ratingMin || ''
  5294. };
  5295. // Cocher les filtres statiques
  5296. Object.keys(staticFilters).forEach(filterName => {
  5297. const value = staticFilters[filterName];
  5298. if (value) {
  5299. const input = document.querySelector(`input[name=\"\${filterName}\"][value=\"\${value}\"]`);
  5300. if (input) {
  5301. input.checked = true;
  5302. } else { // Cocher \"all\" si la valeur n'existe pas
  5303. const allInput = document.getElementById (`\${filterName}-all`);
  5304. if (allInput) 
  5305. allInput.checked = true;
  5306. }
  5307. } else {
  5308. const allInput = document.getElementById (`\${filterName}-all`);
  5309. if (allInput) 
  5310. allInput.checked = true;
  5311. }
  5312. });
  5313. // Initialiser les filtres de poids
  5314. if (currentFilters.weightMin) {
  5315. const weightMinInput = document.getElementById('weightMin');
  5316. if (weightMinInput) 
  5317. weightMinInput.value = currentFilters.weightMin;
  5318. }
  5319. if (currentFilters.weightMax) {
  5320. const weightMaxInput = document.getElementById('weightMax');
  5321. if (weightMaxInput) 
  5322. weightMaxInput.value = currentFilters.weightMax;
  5323. }
  5324. // Initialiser les filtres de marque (si déjà chargés)
  5325. if (currentFilters.brand) {
  5326. const brandInput = document.querySelector (`input[name=\"brand\"][value=\"\${
  5327. currentFilters.brand
  5328. }\"]`);
  5329. if (brandInput) {
  5330. brandInput.checked = true;
  5331. } else {
  5332. const allBrands = document.getElementById('all-brands');
  5333. if (allBrands) 
  5334. allBrands.checked = true;
  5335. }
  5336. }
  5337. // Initialiser les filtres de boutique (si déjà chargés)
  5338. if (currentFilters.shop) {
  5339. const shopInput = document.querySelector (`input[name=\"shop\"][value=\"\${
  5340. currentFilters.shop
  5341. }\"]`);
  5342. if (shopInput) {
  5343. shopInput.checked = true;
  5344. } else {
  5345. const allShops = document.getElementById('all-shops');
  5346. if (allShops) 
  5347. allShops.checked = true;
  5348. }
  5349. }
  5350. // Initialiser les filtres d'attributs (si déjà chargés)
  5351. ['color', 'size', 'material', 'condition'].forEach(attrType => {
  5352. if (currentFilters[attrType]) {
  5353. const attrInput = document.querySelector(`input[name=\"\${attrType}\"][value=\"\${
  5354. currentFilters[attrType]
  5355. }\"]`);
  5356. if (attrInput) {
  5357. attrInput.checked = true;
  5358. } else {
  5359. const allAttr = document.getElementById (`all-\${attrType}`);
  5360. if (allAttr) 
  5361. allAttr.checked = true;
  5362. }
  5363. }
  5364. });
  5365. }
  5366. // Initialiser les filtres
  5367. document.addEventListener('DOMContentLoaded', function () { // Initialiser les filtres depuis l'URL
  5368. initializeFiltersFromURL();
  5369. // Event listeners pour les filtres statiques
  5370. const staticFilters = ['featured', 'digital', 'availability'];
  5371. staticFilters.forEach(filterName => {
  5372. const inputs = document.querySelectorAll (`input[name=\"\${filterName}\"]`);
  5373. inputs.forEach(input => {
  5374. input.addEventListener('change', function () {
  5375. if (this.id.includes('-all')) {
  5376. currentFilters[filterName] = '';
  5377. } else {
  5378. currentFilters[filterName] = this.value;
  5379. }
  5380. currentFilters.page = 1;
  5381. applyFilters();
  5382. });
  5383. });
  5384. });
  5385. // Event listener spécial pour rating (mapping vers ratingMin)
  5386. const ratingInputs = document.querySelectorAll('input[name=\"rating\"]');
  5387. ratingInputs.forEach(input => {
  5388. input.addEventListener('change', function () {
  5389. if (this.id === 'rating-all') {
  5390. currentFilters.ratingMin = '';
  5391. currentFilters.rating = '';
  5392. } else {
  5393. currentFilters.ratingMin = this.value;
  5394. currentFilters.rating = this.value;
  5395. }
  5396. currentFilters.page = 1;
  5397. applyFilters();
  5398. });
  5399. });
  5400. // Event listeners pour les filtres de poids
  5401. const weightInputs = ['weightMin', 'weightMax'];
  5402. weightInputs.forEach(inputId => {
  5403. const input = document.getElementById(inputId);
  5404. if (input) {
  5405. input.addEventListener('change', applyWeightFilter);
  5406. input.addEventListener('input', debounce(applyWeightFilter, 500));
  5407. }
  5408. });
  5409. // Event listeners pour les filtres de prix
  5410. const priceInputs = ['priceMin', 'priceMax'];
  5411. priceInputs.forEach(inputId => {
  5412. const input = document.getElementById(inputId);
  5413. if (input) {
  5414. input.addEventListener('change', applyPriceFilter);
  5415. input.addEventListener('input', debounce(applyPriceFilter, 500));
  5416. }
  5417. });
  5418. // Ajouter les event listeners pour les marques existantes
  5419. const brandList = document.getElementById('brandList');
  5420. if (brandList) {
  5421. brandList.querySelectorAll('input[name=\"brand\"]').forEach(radio => {
  5422. radio.addEventListener('change', function () {
  5423. if (this.id === 'all-brands') {
  5424. currentFilters.brand = '';
  5425. } else {
  5426. currentFilters.brand = this.value;
  5427. }
  5428. currentFilters.page = 1;
  5429. applyFilters();
  5430. });
  5431. });
  5432. }
  5433. // Appliquer les filtres initiaux si une catégorie est sélectionnée
  5434. if (currentFilters.category) {
  5435. applyFilters();
  5436. } else { // Initialiser l'affichage du bouton au chargement de la page
  5437. updateLoadMoreButton();
  5438. }
  5439. });
  5440. // Fonction de debounce pour éviter trop de requêtes
  5441. function debounce (func, wait) {
  5442. let timeout;
  5443. return function executedFunction(...args) {
  5444. const later = () => {
  5445. clearTimeout(timeout);
  5446. func(...args);
  5447. };
  5448. clearTimeout(timeout);
  5449. timeout = setTimeout(later, wait);
  5450. };
  5451. }
  5452. // Les fonctions toggleComparison, toggleWishlist, et showNotification sont définies globalement dans base_home.html.twig
  5453. // Pas besoin de les redéfinir ici
  5454. // Fonctions pour le dropshipping
  5455. function generateDropshipLink (productId) {
  5456. showEbayPromptModal('URL originale (optionnel):', '').then(originalUrl => {
  5457. if (originalUrl === null) 
  5458. return;
  5459.  // Utilisateur a annulé
  5460. const formData = new FormData();
  5461. formData.append('originalUrl', originalUrl || '');
  5462. fetch (`/api/dropship/generate/\${productId}`, {
  5463. method: 'POST',
  5464. body: formData
  5465. }).then(response => response.json()).then(data => {
  5466. if (data.success) { // Afficher le lien généré dans une modal
  5467. showDropshipLinkModal(data.data);
  5468. } else {
  5469. showNotification(data.message || 'Erreur lors de la génération du lien', 'error');
  5470. }
  5471. }).catch(error => {
  5472. console.error('Erreur:', error);
  5473. showNotification('Erreur lors de la génération du lien', 'error');
  5474. });
  5475. });
  5476. }
  5477. function showDropshipLinkModal (linkData) {
  5478. const modal = document.createElement('div');
  5479. modal.className = 'modal fade show';
  5480. modal.style.display = 'block';
  5481. modal.innerHTML = `
  5482.         <div class=\"modal-dialog\">
  5483.             <div class=\"modal-content\">
  5484.                 <div class=\"modal-header\">
  5485.                     <h5 class=\"modal-title\">
  5486.                         <i class=\"fa fa-share-alt\"></i> Lien de dropshipping généré
  5487.                     </h5>
  5488.                     <button type=\"button\" class=\"btn-close\" onclick=\"closeModal(this)\"></button>
  5489.                 </div>
  5490.                 <div class=\"modal-body\">
  5491.                     <div class=\"mb-3\">
  5492.                         <label class=\"form-label\">Lien de dropshipping :</label>
  5493.                         <div class=\"input-group\">
  5494.                             <input type=\"text\" class=\"form-control\" value=\"\${
  5495. linkData.dropshipUrl
  5496. }\" readonly id=\"dropshipUrl\">
  5497.                             <button class=\"btn btn-outline-secondary\" onclick=\"copyToClipboard('\${
  5498. linkData.dropshipUrl
  5499. }')\">
  5500.                                 <i class=\"fa fa-copy\"></i>
  5501.                             </button>
  5502.                         </div>
  5503.                     </div>
  5504.                     <div class=\"row\">
  5505.                         <div class=\"col-md-6\">
  5506.                             <div class=\"card\">
  5507.                                 <div class=\"card-body text-center\">
  5508.                                     <h6 class=\"card-title\">Taux de commission</h6>
  5509.                                     <h4 class=\"text-primary\">\${
  5510. linkData.commissionRate
  5511. }%</h4>
  5512.                                 </div>
  5513.                             </div>
  5514.                         </div>
  5515.                         <div class=\"col-md-6\">
  5516.                             <div class=\"card\">
  5517.                                 <div class=\"card-body text-center\">
  5518.                                     <h6 class=\"card-title\">Commission par vente</h6>
  5519.                                     <h4 class=\"text-success\">\${
  5520. linkData.commissionAmount
  5521. } HTG</h4>
  5522.                                 </div>
  5523.                             </div>
  5524.                         </div>
  5525.                     </div>
  5526.                     <div class=\"alert alert-info\">
  5527.                         <i class=\"fa fa-info-circle\"></i>
  5528.                         <strong>Note :</strong> Ce lien expire le \${
  5529. new Date(linkData.expiresAt).toLocaleDateString('fr-FR')
  5530. }.
  5531.                     </div>
  5532.                 </div>
  5533.                 <div class=\"modal-footer\">
  5534.                     <button type=\"button\" class=\"btn btn-secondary\" onclick=\"closeModal(this)\">Fermer</button>
  5535.                     <a href=\"/dropship/dashboard\" class=\"btn btn-primary\">
  5536.                         <i class=\"fa fa-tachometer\"></i> Voir le dashboard
  5537.                     </a>
  5538.                 </div>
  5539.             </div>
  5540.         </div>
  5541.     `;
  5542. document.body.appendChild(modal);
  5543. // Ajouter le backdrop
  5544. const backdrop = document.createElement('div');
  5545. backdrop.className = 'modal-backdrop fade show';
  5546. document.body.appendChild(backdrop);
  5547. }
  5548. function closeModal (button) {
  5549. const modal = button.closest('.modal');
  5550. const backdrop = document.querySelector('.modal-backdrop');
  5551. if (modal) {
  5552. modal.remove();
  5553. }
  5554. if (backdrop) {
  5555. backdrop.remove();
  5556. }
  5557. }
  5558. function copyToClipboard (text) {
  5559. navigator.clipboard.writeText(text).then(function () {
  5560. showNotification('Lien copié dans le presse-papiers !', 'success');
  5561. }, function (err) {
  5562. console.error('Erreur lors de la copie:', err);
  5563. showNotification('Erreur lors de la copie du lien', 'error');
  5564. });
  5565. }
  5566. // Fonction pour initialiser le chargement infini
  5567. function initInfiniteScroll () {
  5568. const loader = document.getElementById('infiniteScrollLoader');
  5569. const noMoreProducts = document.getElementById('noMoreProducts');
  5570. // Afficher le loader si il y a plus de produits
  5571. if (hasMoreProducts && loader) {
  5572. loader.style.display = 'block';
  5573. } else if (!hasMoreProducts && noMoreProducts) {
  5574. noMoreProducts.style.display = 'block';
  5575. }
  5576. // Observer pour détecter quand l'utilisateur arrive en bas
  5577. const observer = new IntersectionObserver((entries) => {
  5578. entries.forEach(entry => {
  5579. if (entry.isIntersecting && !isLoading && hasMoreProducts) {
  5580. loadMoreProducts();
  5581. }
  5582. });
  5583. }, {
  5584. root: null,
  5585. rootMargin: '200px', // Démarrer le chargement 200px avant d'arriver en bas
  5586. threshold: 0.1
  5587. });
  5588. // Observer le loader
  5589. if (loader) {
  5590. observer.observe(loader);
  5591. }
  5592. }
  5593. // Fonction pour mettre à jour l'affichage du bouton
  5594. function updateLoadMoreButton () {
  5595. const loadMoreContainer = document.getElementById('loadMoreContainer');
  5596. const noMoreProducts = document.getElementById('noMoreProducts');
  5597. if (hasMoreProducts) {
  5598. if (loadMoreContainer) {
  5599. loadMoreContainer.style.display = 'block';
  5600. }
  5601. if (noMoreProducts) {
  5602. noMoreProducts.style.display = 'none';
  5603. }
  5604. } else {
  5605. if (loadMoreContainer) {
  5606. loadMoreContainer.style.display = 'none';
  5607. }
  5608. if (noMoreProducts) {
  5609. noMoreProducts.style.display = 'block';
  5610. }
  5611. }
  5612. }
  5613. // Fonction pour initialiser les images des nouveaux produits
  5614. function initializeProductImages (container) {
  5615. const productCards = container.querySelectorAll('.single-product');
  5616. productCards.forEach(card => {
  5617. const productId = card.querySelector('.add-to-cart') ?. getAttribute('data-product-id');
  5618. if (productId) { // Récupérer les images depuis les attributs data ou depuis le DOM
  5619. const imgElement = card.querySelector('.main-product-img');
  5620. if (imgElement && imgElement.dataset.images) {
  5621. const images = JSON.parse(imgElement.dataset.images);
  5622. productImages[productId] = images;
  5623. currentImageIndex[productId] = 0;
  5624. }
  5625. }
  5626. });
  5627. }
  5628. // Fonction pour initialiser les event listeners des nouveaux produits
  5629. function initializeProductEventListeners (container) { // Boutons d'ajout au panier
  5630. const cartButtons = container.querySelectorAll('.add-to-cart');
  5631. cartButtons.forEach(button => {
  5632. button.addEventListener('click', function () {
  5633. const productId = this.getAttribute('data-product-id');
  5634. const qty = this.getAttribute('data-qty');
  5635. fetch('{{ path(\"ui_cart_add\") }}', {
  5636. method: 'POST',
  5637. headers: {
  5638. 'Content-Type': 'application/x-www-form-urlencoded'
  5639. },
  5640. body: 'productId=' + productId + '&qty=' + qty
  5641. }).then(response => response.json()).then(data => {
  5642. if (data.ok) {
  5643. showEbayModal('Produit ajouté au panier !', 'success');
  5644. const cartBadge = document.querySelector('.cart-badge');
  5645. if (cartBadge) {
  5646. cartBadge.textContent = data.totalQty;
  5647. }
  5648. } else {
  5649. showEbayModal(data.message || 'Erreur lors de l\\'ajout au panier', 'error');
  5650. }
  5651. }).catch(error => {
  5652. console.error('Erreur:', error);
  5653. showEbayModal('Erreur lors de l\\'ajout au panier', 'error');
  5654. });
  5655. });
  5656. });
  5657. }
  5658. // La fonction showEbayModal est déjà définie plus haut
  5659. // Modal style eBay pour remplacer prompt()
  5660. function showEbayPromptModal (message, defaultValue = '') {
  5661. return new Promise((resolve) => {
  5662. const modalId = 'ebayPromptModal';
  5663. let existingModal = document.getElementById(modalId);
  5664. if (existingModal) {
  5665. existingModal.remove();
  5666. }
  5667. const modal = document.createElement('div');
  5668. modal.id = modalId;
  5669. modal.className = 'modal fade';
  5670. modal.setAttribute('tabindex', '-1');
  5671. modal.setAttribute('aria-labelledby', 'ebayPromptModalLabel');
  5672. modal.setAttribute('aria-hidden', 'true');
  5673. modal.innerHTML = `
  5674. <div class=\"modal-dialog modal-dialog-centered\">
  5675. <div class=\"modal-content\" style=\"border-radius: 8px; border: none; box-shadow: 0 4px 20px rgba(0,0,0,0.15);\">
  5676. <div class=\"modal-header\" style=\"border-bottom: 1px solid #e0e0e0; padding: 20px 24px;\">
  5677. <h5 class=\"modal-title\" id=\"ebayPromptModalLabel\" style=\"font-weight: 600; font-size: 18px; color: #333;\">
  5678. <i class=\"ti-pencil-alt\" style=\"color: #0064D2; margin-right: 8px;\"></i>Saisie
  5679. </h5>
  5680. <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\" style=\"margin: 0;\"></button>
  5681. </div>
  5682. <div class=\"modal-body\" style=\"padding: 24px;\">
  5683. <p style=\"margin-bottom: 16px; font-size: 16px; color: #333;\">\${message}</p>
  5684. <input type=\"text\" class=\"form-control\" id=\"ebayPromptInput\" value=\"\${defaultValue}\" style=\"border-radius: 4px; border: 1px solid #ccc; padding: 10px; font-size: 14px;\" autofocus>
  5685. </div>
  5686. <div class=\"modal-footer\" style=\"border-top: 1px solid #e0e0e0; padding: 16px 24px; justify-content: flex-end;\">
  5687. <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\" style=\"min-width: 80px; border-radius: 4px; font-weight: 500; margin-right: 8px;\">
  5688. Annuler
  5689. </button>
  5690. <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;\">
  5691. OK
  5692. </button>
  5693. </div>
  5694. </div>
  5695. </div>
  5696. `;
  5697. document.body.appendChild(modal);
  5698. const bsModal = new bootstrap.Modal(modal);
  5699. const input = modal.querySelector('#ebayPromptInput');
  5700. const confirmBtn = modal.querySelector('#ebayPromptConfirm');
  5701. // Gérer la confirmation
  5702. confirmBtn.addEventListener('click', function () {
  5703. const value = input.value;
  5704. bsModal.hide();
  5705. resolve(value);
  5706. });
  5707. // Gérer l'annulation
  5708. modal.addEventListener('hidden.bs.modal', function () {
  5709. if (input.value === undefined || input.value === '') {
  5710. resolve(null);
  5711. }
  5712. modal.remove();
  5713. });
  5714. // Gérer la touche Entrée
  5715. input.addEventListener('keypress', function (e) {
  5716. if (e.key === 'Enter') {
  5717. confirmBtn.click();
  5718. }
  5719. });
  5720. bsModal.show();
  5721. input.focus();
  5722. input.select();
  5723. });
  5724. }
  5725. // Remplacer window.alert et window.prompt
  5726. window.alert = showEbayModal;
  5727. window.prompt = showEbayPromptModal;
  5728. </script>
  5729. <script>
  5730. \t// Gestion du toggle de la sidebar sur mobile
  5731. document.addEventListener('DOMContentLoaded', function () {
  5732. const sidebarToggle = document.querySelector('.listing-sidebar-toggle');
  5733. const sidebarCollapse = document.getElementById('listingSidebarCollapse');
  5734. if (sidebarToggle && sidebarCollapse) { // Bootstrap 5 utilise des événements natifs
  5735. sidebarCollapse.addEventListener('show.bs.collapse', function () {
  5736. sidebarToggle.classList.remove('collapsed');
  5737. sidebarToggle.setAttribute('aria-expanded', 'true');
  5738. });
  5739. sidebarCollapse.addEventListener('hide.bs.collapse', function () {
  5740. sidebarToggle.classList.add('collapsed');
  5741. sidebarToggle.setAttribute('aria-expanded', 'false');
  5742. });
  5743. sidebarCollapse.addEventListener('shown.bs.collapse', function () {
  5744. sidebarToggle.classList.remove('collapsed');
  5745. });
  5746. sidebarCollapse.addEventListener('hidden.bs.collapse', function () {
  5747. sidebarToggle.classList.add('collapsed');
  5748. });
  5749. // Vérifier l'état initial
  5750. if (sidebarCollapse.classList.contains('show')) {
  5751. sidebarToggle.classList.remove('collapsed');
  5752. sidebarToggle.setAttribute('aria-expanded', 'true');
  5753. } else {
  5754. sidebarToggle.classList.add('collapsed');
  5755. sidebarToggle.setAttribute('aria-expanded', 'false');
  5756. }
  5757. }
  5758. });
  5759. // Synchroniser la recherche avec la barre de recherche du header
  5760. const headerSearchInput = document.getElementById('search_input');
  5761. if (headerSearchInput && typeof currentFilters !== 'undefined') { // Mettre à jour la valeur de la barre de recherche du header
  5762. headerSearchInput.value = currentFilters.q || '';
  5763. // Afficher le bouton clear si nécessaire
  5764. const clearSearchBtn = document.getElementById('clear_search_btn');
  5765. if (clearSearchBtn) {
  5766. clearSearchBtn.style.display = currentFilters.q ? 'block' : 'none';
  5767. }
  5768. }
  5769. // Réinitialiser tous les filtres
  5770. const resetFiltersBtn = document.getElementById('resetFiltersBtn');
  5771. if (resetFiltersBtn) {
  5772. resetFiltersBtn.addEventListener('click', function () {
  5773. resetAllFilters();
  5774. });
  5775. }
  5776. // Fonction pour réinitialiser tous les filtres
  5777. function resetAllFilters () {
  5778. currentFilters = {
  5779. category: '',
  5780. brand: '',
  5781. sort: 'newest',
  5782. priceMin: '',
  5783. priceMax: '',
  5784. page: 1,
  5785. q: '',
  5786. shop: '',
  5787. featured: '',
  5788. digital: '',
  5789. stockStatus: '',
  5790. ratingMin: '',
  5791. rating: '',
  5792. weightMin: '',
  5793. weightMax: '',
  5794. color: '',
  5795. size: '',
  5796. material: '',
  5797. condition: '',
  5798. availability: ''
  5799. };
  5800. // Réinitialiser la barre de recherche du header
  5801. const headerSearchInput = document.getElementById('search_input');
  5802. if (headerSearchInput) {
  5803. headerSearchInput.value = '';
  5804. }
  5805. const clearSearchBtn = document.getElementById('clear_search_btn');
  5806. if (clearSearchBtn) {
  5807. clearSearchBtn.style.display = 'none';
  5808. }
  5809. // Réinitialiser les inputs
  5810. document.getElementById('sortSelect').value = 'newest';
  5811. // Réinitialiser les radios
  5812. document.querySelectorAll('input[type=\"radio\"]').forEach(radio => {
  5813. 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')) {
  5814. radio.checked = true;
  5815. } else {
  5816. radio.checked = false;
  5817. }
  5818. });
  5819. // Réinitialiser les inputs de prix et poids
  5820. const weightMinInput = document.getElementById('weightMin');
  5821. const weightMaxInput = document.getElementById('weightMax');
  5822. if (weightMinInput) 
  5823. weightMinInput.value = '';
  5824. if (weightMaxInput) 
  5825. weightMaxInput.value = '';
  5826. applyFilters();
  5827. updateActiveFilters();
  5828. }
  5829. // Fonction pour mettre à jour l'affichage des filtres actifs
  5830. function updateActiveFilters () {
  5831. const activeFiltersDiv = document.getElementById('activeFilters');
  5832. const activeFiltersList = document.getElementById('activeFiltersList');
  5833. const filters = [];
  5834. if (currentFilters.q) {
  5835. filters.push (`<span class=\"badge bg-primary\">Recherche: \"\${
  5836. currentFilters.q
  5837. }\"</span>`);
  5838. }
  5839. if (currentFilters.category) {
  5840. filters.push(`<span class=\"badge bg-secondary\">Catégorie</span>`);
  5841. }
  5842. if (currentFilters.brand) {
  5843. filters.push(`<span class=\"badge bg-secondary\">Marque</span>`);
  5844. }
  5845. if (currentFilters.priceMin || currentFilters.priceMax) {
  5846. filters.push(`<span class=\"badge bg-info\">Prix</span>`);
  5847. }
  5848. if (currentFilters.featured === 'true') {
  5849. filters.push(`<span class=\"badge bg-warning\">Produits vedettes</span>`);
  5850. }
  5851. if (currentFilters.digital === 'true') {
  5852. filters.push(`<span class=\"badge bg-success\">Numériques</span>`);
  5853. }
  5854. if (currentFilters.digital === 'false') {
  5855. filters.push(`<span class=\"badge bg-success\">Physiques</span>`);
  5856. }
  5857. if (currentFilters.availability) {
  5858. filters.push(`<span class=\"badge bg-info\">Disponibilité</span>`);
  5859. }
  5860. if (currentFilters.ratingMin || currentFilters.rating) {
  5861. const ratingValue = currentFilters.ratingMin || currentFilters.rating;
  5862. filters.push (`<span class=\"badge bg-warning\">Note ≥ \${ratingValue}</span>`);
  5863. }
  5864. if (currentFilters.weightMin || currentFilters.weightMax) {
  5865. filters.push(`<span class=\"badge bg-secondary\">Poids</span>`);
  5866. }
  5867. if (filters.length > 0) {
  5868. activeFiltersList.innerHTML = filters.join(' ');
  5869. activeFiltersDiv.style.display = 'block';
  5870. } else {
  5871. activeFiltersDiv.style.display = 'none';
  5872. }
  5873. }
  5874. // Mettre à jour les filtres actifs au chargement et après chaque filtrage
  5875. document.addEventListener('DOMContentLoaded', function () {
  5876. updateActiveFilters();
  5877. });
  5878. // Mettre à jour les filtres actifs après chaque application de filtres
  5879. const originalApplyFilters = applyFilters;
  5880. applyFilters = function () {
  5881. originalApplyFilters();
  5882. setTimeout(updateActiveFilters, 100);
  5883. // Mettre à jour le compteur de filtres actifs
  5884. updateActiveFiltersCount();
  5885. };
  5886. // S'assurer que applyFilters est accessible globalement
  5887. window.applyFilters = applyFilters;
  5888. // Fonction pour mettre à jour le compteur de filtres actifs
  5889. function updateActiveFiltersCount () {
  5890. const count = Object.keys(currentFilters).filter(key => {
  5891. const value = currentFilters[key];
  5892. return value && value !== '' && key !== 'page' && key !== 'sort';
  5893. }).length;
  5894. const badge = document.getElementById('activeFiltersCount');
  5895. if (badge) {
  5896. if (count > 0) {
  5897. badge.textContent = count;
  5898. badge.style.display = 'inline-block';
  5899. } else {
  5900. badge.style.display = 'none';
  5901. }
  5902. }
  5903. }
  5904. // Initialiser le compteur au chargement
  5905. document.addEventListener('DOMContentLoaded', function () {
  5906. updateActiveFiltersCount();
  5907. });
  5908. </script>
  5909. <!-- Modal de filtres -->
  5910. <div class=\"modal fade\" id=\"filtersModal\" tabindex=\"-1\" aria-labelledby=\"filtersModalLabel\" aria-hidden=\"true\">
  5911. \t<div class=\"modal-dialog modal-lg\">
  5912. \t\t<div class=\"modal-content\" style=\"display: flex; flex-direction: column; max-height: 90vh;\">
  5913. \t\t\t<div class=\"modal-header\" style=\"flex-shrink: 0;\">
  5914. \t\t\t\t<h5 class=\"modal-title\" id=\"filtersModalLabel\">
  5915. \t\t\t\t\t<i class=\"lnr lnr-filter me-2\"></i>Filtres de recherche
  5916. \t\t\t\t</h5>
  5917. \t\t\t\t<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>
  5918. \t\t\t</div>
  5919. \t\t\t<div class=\"modal-body\" style=\"overflow-y: auto; flex: 1; min-height: 0;\">
  5920. \t\t\t\t<div
  5921. \t\t\t\t\tclass=\"row\">
  5922. \t\t\t\t\t<!-- Colonne gauche -->
  5923. \t\t\t\t\t<div
  5924. \t\t\t\t\t\tclass=\"col-md-6\">
  5925. \t\t\t\t\t\t<!-- Marques -->
  5926. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  5927. \t\t\t\t\t\t\t<div class=\"head\">Marques</div>
  5928. \t\t\t\t\t\t\t<ul class=\"brand-list\" id=\"modalBrandList\">
  5929. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  5930. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-all-brands\" name=\"modal-brand\" checked>
  5931. \t\t\t\t\t\t\t\t\t<label for=\"modal-all-brands\">Toutes les marques</label>
  5932. \t\t\t\t\t\t\t\t</li>
  5933. \t\t\t\t\t\t\t\t{% for brand in brands %}
  5934. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  5935. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-brand-{{ brand.id }}\" name=\"modal-brand\" value=\"{{ brand.slug }}\">
  5936. \t\t\t\t\t\t\t\t\t\t<label for=\"modal-brand-{{ brand.id }}\">{{ brand.name }}<span>({{ brand.getActiveProductsCount() }})</span>
  5937. \t\t\t\t\t\t\t\t\t\t</label>
  5938. \t\t\t\t\t\t\t\t\t</li>
  5939. \t\t\t\t\t\t\t\t{% endfor %}
  5940. \t\t\t\t\t\t\t</ul>
  5941. \t\t\t\t\t\t</div>
  5942. \t\t\t\t\t\t<!-- Conditions -->
  5943. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  5944. \t\t\t\t\t\t\t<div class=\"head\">Condition</div>
  5945. \t\t\t\t\t\t\t<ul class=\"condition-list\" id=\"modalConditionList\">
  5946. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  5947. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-all-conditions\" name=\"modal-condition\" checked>
  5948. \t\t\t\t\t\t\t\t\t<label for=\"modal-all-conditions\">Toutes les conditions</label>
  5949. \t\t\t\t\t\t\t\t</li>
  5950. \t\t\t\t\t\t\t\t{% for condition in conditions %}
  5951. \t\t\t\t\t\t\t\t\t<li class=\"filter-list\">
  5952. \t\t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-condition-{{ condition.id }}\" name=\"modal-condition\" value=\"{{ condition.slug }}\">
  5953. \t\t\t\t\t\t\t\t\t\t<label for=\"modal-condition-{{ condition.id }}\">{{ condition.name }}<span>({{ condition.getActiveProductsCount() }})</span>
  5954. \t\t\t\t\t\t\t\t\t\t</label>
  5955. \t\t\t\t\t\t\t\t\t</li>
  5956. \t\t\t\t\t\t\t\t{% endfor %}
  5957. \t\t\t\t\t\t\t</ul>
  5958. \t\t\t\t\t\t</div>
  5959. \t\t\t\t\t\t<!-- Boutiques -->
  5960. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  5961. \t\t\t\t\t\t\t<div class=\"head\">Boutiques</div>
  5962. \t\t\t\t\t\t\t<div id=\"modalShopsFilter\">
  5963. \t\t\t\t\t\t\t\t<ul
  5964. \t\t\t\t\t\t\t\t\tclass=\"shop-list\" id=\"modalShopList\"><!-- Les boutiques seront chargées dynamiquement -->
  5965. \t\t\t\t\t\t\t\t</ul>
  5966. \t\t\t\t\t\t\t</div>
  5967. \t\t\t\t\t\t</div>
  5968. \t\t\t\t\t\t<!-- Produits vedettes -->
  5969. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  5970. \t\t\t\t\t\t\t<div class=\"head\">Produits vedettes</div>
  5971. \t\t\t\t\t\t\t<ul>
  5972. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  5973. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-featured-all\" name=\"modal-featured\" checked>
  5974. \t\t\t\t\t\t\t\t\t<label for=\"modal-featured-all\">Tous les produits</label>
  5975. \t\t\t\t\t\t\t\t</li>
  5976. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  5977. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-featured-only\" name=\"modal-featured\" value=\"true\">
  5978. \t\t\t\t\t\t\t\t\t<label for=\"modal-featured-only\">Produits vedettes uniquement</label>
  5979. \t\t\t\t\t\t\t\t</li>
  5980. \t\t\t\t\t\t\t</ul>
  5981. \t\t\t\t\t\t</div>
  5982. \t\t\t\t\t\t<!-- Type de produit -->
  5983. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  5984. \t\t\t\t\t\t\t<div class=\"head\">Type de produit</div>
  5985. \t\t\t\t\t\t\t<ul>
  5986. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  5987. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-digital-all\" name=\"modal-digital\" checked>
  5988. \t\t\t\t\t\t\t\t\t<label for=\"modal-digital-all\">Tous les types</label>
  5989. \t\t\t\t\t\t\t\t</li>
  5990. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  5991. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-digital-physical\" name=\"modal-digital\" value=\"false\">
  5992. \t\t\t\t\t\t\t\t\t<label for=\"modal-digital-physical\">Produits physiques</label>
  5993. \t\t\t\t\t\t\t\t</li>
  5994. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  5995. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-digital-digital\" name=\"modal-digital\" value=\"true\">
  5996. \t\t\t\t\t\t\t\t\t<label for=\"modal-digital-digital\">Produits numériques</label>
  5997. \t\t\t\t\t\t\t\t</li>
  5998. \t\t\t\t\t\t\t</ul>
  5999. \t\t\t\t\t\t</div>
  6000. \t\t\t\t\t</div>
  6001. \t\t\t\t\t<!-- Colonne droite -->
  6002. \t\t\t\t\t<div
  6003. \t\t\t\t\t\tclass=\"col-md-6\">
  6004. \t\t\t\t\t\t<!-- Disponibilité -->
  6005. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  6006. \t\t\t\t\t\t\t<div class=\"head\">Disponibilité</div>
  6007. \t\t\t\t\t\t\t<ul>
  6008. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  6009. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-availability-all\" name=\"modal-availability\" checked>
  6010. \t\t\t\t\t\t\t\t\t<label for=\"modal-availability-all\">Tous</label>
  6011. \t\t\t\t\t\t\t\t</li>
  6012. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  6013. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-availability-in-stock\" name=\"modal-availability\" value=\"in_stock\">
  6014. \t\t\t\t\t\t\t\t\t<label for=\"modal-availability-in-stock\">En stock</label>
  6015. \t\t\t\t\t\t\t\t</li>
  6016. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  6017. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-availability-low-stock\" name=\"modal-availability\" value=\"low_stock\">
  6018. \t\t\t\t\t\t\t\t\t<label for=\"modal-availability-low-stock\">Stock faible</label>
  6019. \t\t\t\t\t\t\t\t</li>
  6020. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  6021. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-availability-out-of-stock\" name=\"modal-availability\" value=\"out_of_stock\">
  6022. \t\t\t\t\t\t\t\t\t<label for=\"modal-availability-out-of-stock\">Rupture de stock</label>
  6023. \t\t\t\t\t\t\t\t</li>
  6024. \t\t\t\t\t\t\t</ul>
  6025. \t\t\t\t\t\t</div>
  6026. \t\t\t\t\t\t<!-- Note minimale -->
  6027. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  6028. \t\t\t\t\t\t\t<div class=\"head\">Note minimale</div>
  6029. \t\t\t\t\t\t\t<ul>
  6030. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  6031. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-rating-all\" name=\"modal-rating\" checked>
  6032. \t\t\t\t\t\t\t\t\t<label for=\"modal-rating-all\">Toutes les notes</label>
  6033. \t\t\t\t\t\t\t\t</li>
  6034. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  6035. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-rating-4\" name=\"modal-rating\" value=\"4\">
  6036. \t\t\t\t\t\t\t\t\t<label for=\"modal-rating-4\">4 étoiles et plus</label>
  6037. \t\t\t\t\t\t\t\t</li>
  6038. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  6039. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-rating-3\" name=\"modal-rating\" value=\"3\">
  6040. \t\t\t\t\t\t\t\t\t<label for=\"modal-rating-3\">3 étoiles et plus</label>
  6041. \t\t\t\t\t\t\t\t</li>
  6042. \t\t\t\t\t\t\t\t<li class=\"filter-list\">
  6043. \t\t\t\t\t\t\t\t\t<input class=\"pixel-radio\" type=\"radio\" id=\"modal-rating-2\" name=\"modal-rating\" value=\"2\">
  6044. \t\t\t\t\t\t\t\t\t<label for=\"modal-rating-2\">2 étoiles et plus</label>
  6045. \t\t\t\t\t\t\t\t</li>
  6046. \t\t\t\t\t\t\t</ul>
  6047. \t\t\t\t\t\t</div>
  6048. \t\t\t\t\t\t<!-- Prix -->
  6049. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  6050. \t\t\t\t\t\t\t<div class=\"head\">Prix (HTG)</div>
  6051. \t\t\t\t\t\t\t<div class=\"price-range-area\">
  6052. \t\t\t\t\t\t\t\t<div class=\"d-flex gap-3\">
  6053. \t\t\t\t\t\t\t\t\t<div class=\"flex-grow-1\">
  6054. \t\t\t\t\t\t\t\t\t\t<label class=\"form-label\">Min</label>
  6055. \t\t\t\t\t\t\t\t\t\t<input type=\"number\" class=\"form-control\" id=\"modal-priceMin\" placeholder=\"0\" min=\"0\" step=\"0.01\">
  6056. \t\t\t\t\t\t\t\t\t</div>
  6057. \t\t\t\t\t\t\t\t\t<div class=\"flex-grow-1\">
  6058. \t\t\t\t\t\t\t\t\t\t<label class=\"form-label\">Max</label>
  6059. \t\t\t\t\t\t\t\t\t\t<input type=\"number\" class=\"form-control\" id=\"modal-priceMax\" placeholder=\"999999\" min=\"0\" step=\"0.01\">
  6060. \t\t\t\t\t\t\t\t\t</div>
  6061. \t\t\t\t\t\t\t\t</div>
  6062. \t\t\t\t\t\t\t</div>
  6063. \t\t\t\t\t\t</div>
  6064. \t\t\t\t\t\t<!-- Poids -->
  6065. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  6066. \t\t\t\t\t\t\t<div class=\"head\">Poids (kg)</div>
  6067. \t\t\t\t\t\t\t<div class=\"weight-range-area\">
  6068. \t\t\t\t\t\t\t\t<div class=\"d-flex gap-3\">
  6069. \t\t\t\t\t\t\t\t\t<div class=\"flex-grow-1\">
  6070. \t\t\t\t\t\t\t\t\t\t<label class=\"form-label\">Min</label>
  6071. \t\t\t\t\t\t\t\t\t\t<input type=\"number\" class=\"form-control\" id=\"modal-weightMin\" placeholder=\"0\" step=\"0.1\" min=\"0\">
  6072. \t\t\t\t\t\t\t\t\t</div>
  6073. \t\t\t\t\t\t\t\t\t<div class=\"flex-grow-1\">
  6074. \t\t\t\t\t\t\t\t\t\t<label class=\"form-label\">Max</label>
  6075. \t\t\t\t\t\t\t\t\t\t<input type=\"number\" class=\"form-control\" id=\"modal-weightMax\" placeholder=\"100\" step=\"0.1\" min=\"0\">
  6076. \t\t\t\t\t\t\t\t\t</div>
  6077. \t\t\t\t\t\t\t\t</div>
  6078. \t\t\t\t\t\t\t</div>
  6079. \t\t\t\t\t\t</div>
  6080. \t\t\t\t\t\t<!-- Couleur -->
  6081. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  6082. \t\t\t\t\t\t\t<div class=\"head\">Couleur</div>
  6083. \t\t\t\t\t\t\t<div id=\"modalColorsFilter\">
  6084. \t\t\t\t\t\t\t\t<ul
  6085. \t\t\t\t\t\t\t\t\tclass=\"color-list\" id=\"modalColorList\"><!-- Les couleurs seront chargées dynamiquement -->
  6086. \t\t\t\t\t\t\t\t</ul>
  6087. \t\t\t\t\t\t\t</div>
  6088. \t\t\t\t\t\t</div>
  6089. \t\t\t\t\t\t<!-- Taille -->
  6090. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  6091. \t\t\t\t\t\t\t<div class=\"head\">Taille</div>
  6092. \t\t\t\t\t\t\t<div id=\"modalSizesFilter\">
  6093. \t\t\t\t\t\t\t\t<ul
  6094. \t\t\t\t\t\t\t\t\tclass=\"size-list\" id=\"modalSizeList\"><!-- Les tailles seront chargées dynamiquement -->
  6095. \t\t\t\t\t\t\t\t</ul>
  6096. \t\t\t\t\t\t\t</div>
  6097. \t\t\t\t\t\t</div>
  6098. \t\t\t\t\t\t<!-- Matériau -->
  6099. \t\t\t\t\t\t<div class=\"common-filter mb-4\">
  6100. \t\t\t\t\t\t\t<div class=\"head\">Matériau</div>
  6101. \t\t\t\t\t\t\t<div id=\"modalMaterialsFilter\">
  6102. \t\t\t\t\t\t\t\t<ul
  6103. \t\t\t\t\t\t\t\t\tclass=\"material-list\" id=\"modalMaterialList\"><!-- Les matériaux seront chargés dynamiquement -->
  6104. \t\t\t\t\t\t\t\t</ul>
  6105. \t\t\t\t\t\t\t</div>
  6106. \t\t\t\t\t\t</div>
  6107. \t\t\t\t\t</div>
  6108. \t\t\t\t</div>
  6109. \t\t\t</div>
  6110. \t\t\t<div class=\"modal-footer\" style=\"flex-shrink: 0; border-top: 1px solid #dee2e6; padding: 1rem 1.5rem; background: #f8f9fa;\">
  6111. \t\t\t\t<button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">
  6112. \t\t\t\t\t<i class=\"lnr lnr-cross-circle me-1\"></i>Annuler
  6113. \t\t\t\t</button>
  6114. \t\t\t\t<button type=\"button\" class=\"btn btn-outline-danger\" id=\"modalResetFiltersBtn\">
  6115. \t\t\t\t\t<i class=\"lnr lnr-refresh me-1\"></i>Réinitialiser
  6116. \t\t\t\t</button>
  6117. \t\t\t\t<button type=\"button\" class=\"btn btn-primary\" id=\"modalApplyFiltersBtn\">
  6118. \t\t\t\t\t<i class=\"lnr lnr-checkmark-circle me-1\"></i>Appliquer les filtres
  6119. \t\t\t\t</button>
  6120. \t\t\t</div>
  6121. \t\t</div>
  6122. \t</div>
  6123. </div>
  6124. <script>
  6125. \t// Synchroniser les filtres du modal avec currentFilters au chargement
  6126. document.addEventListener('DOMContentLoaded', function () {
  6127. console.log('[Modal] Initializing filter modal...');
  6128. // Fonction pour synchroniser les valeurs du modal avec currentFilters
  6129. function syncModalFilters() {
  6130. console.log('[Modal] Syncing filters with currentFilters:', currentFilters);
  6131. // Réinitialiser tous les radios \"Tous\" d'abord
  6132. document.querySelectorAll('#filtersModal input[type=\"radio\"][id*=\"-all\"]').forEach(radio => {
  6133. radio.checked = true;
  6134. });
  6135. // Marques
  6136. if (currentFilters.brand) {
  6137. const brandRadio = document.querySelector (`#modalBrandList input[value=\"\${
  6138. currentFilters.brand
  6139. }\"]`);
  6140. if (brandRadio) {
  6141. brandRadio.checked = true;
  6142. console.log('[Modal] Brand synced:', currentFilters.brand);
  6143. }
  6144. } else {
  6145. const allBrands = document.getElementById('modal-all-brands');
  6146. if (allBrands) 
  6147. allBrands.checked = true;
  6148. }
  6149. // Conditions
  6150. if (currentFilters.condition) {
  6151. const conditionRadio = document.querySelector (`#modalConditionList input[value=\"\${
  6152. currentFilters.condition
  6153. }\"]`);
  6154. if (conditionRadio) {
  6155. conditionRadio.checked = true;
  6156. console.log('[Modal] Condition synced:', currentFilters.condition);
  6157. }
  6158. } else {
  6159. const allConditions = document.getElementById('modal-all-conditions');
  6160. if (allConditions) 
  6161. allConditions.checked = true;
  6162. }
  6163. // Featured
  6164. if (currentFilters.featured === 'true') {
  6165. const featuredRadio = document.getElementById('modal-featured-only');
  6166. if (featuredRadio) {
  6167. featuredRadio.checked = true;
  6168. console.log('[Modal] Featured synced');
  6169. }
  6170. } else {
  6171. const allFeatured = document.getElementById('modal-featured-all');
  6172. if (allFeatured) 
  6173. allFeatured.checked = true;
  6174. }
  6175. // Digital
  6176. if (currentFilters.digital === 'true') {
  6177. const digitalRadio = document.getElementById('modal-digital-digital');
  6178. if (digitalRadio) 
  6179. digitalRadio.checked = true;
  6180. } else if (currentFilters.digital === 'false') {
  6181. const physicalRadio = document.getElementById('modal-digital-physical');
  6182. if (physicalRadio) 
  6183. physicalRadio.checked = true;
  6184. } else {
  6185. const allDigital = document.getElementById('modal-digital-all');
  6186. if (allDigital) 
  6187. allDigital.checked = true;
  6188. }
  6189. // Availability
  6190. if (currentFilters.availability) {
  6191. const availabilityId = `modal-availability-\${
  6192. currentFilters.availability.replace('_', '-')
  6193. }`;
  6194. const availabilityRadio = document.getElementById(availabilityId);
  6195. if (availabilityRadio) {
  6196. availabilityRadio.checked = true;
  6197. console.log('[Modal] Availability synced:', currentFilters.availability);
  6198. }
  6199. } else {
  6200. const allAvailability = document.getElementById('modal-availability-all');
  6201. if (allAvailability) 
  6202. allAvailability.checked = true;
  6203. }
  6204. // Rating
  6205. if (currentFilters.ratingMin || currentFilters.rating) {
  6206. const ratingValue = currentFilters.ratingMin || currentFilters.rating;
  6207. const ratingRadio = document.getElementById (`modal-rating-\${ratingValue}`);
  6208. if (ratingRadio) {
  6209. ratingRadio.checked = true;
  6210. console.log('[Modal] Rating synced:', ratingValue);
  6211. }
  6212. } else {
  6213. const allRating = document.getElementById('modal-rating-all');
  6214. if (allRating) 
  6215. allRating.checked = true;
  6216. }
  6217. // Prix
  6218. const priceMinInput = document.getElementById('modal-priceMin');
  6219. const priceMaxInput = document.getElementById('modal-priceMax');
  6220. if (priceMinInput) 
  6221. priceMinInput.value = currentFilters.priceMin || '';
  6222. if (priceMaxInput) 
  6223. priceMaxInput.value = currentFilters.priceMax || '';
  6224. // Poids
  6225. const weightMinInput = document.getElementById('modal-weightMin');
  6226. const weightMaxInput = document.getElementById('modal-weightMax');
  6227. if (weightMinInput) 
  6228. weightMinInput.value = currentFilters.weightMin || '';
  6229. if (weightMaxInput) 
  6230. weightMaxInput.value = currentFilters.weightMax || '';
  6231. console.log('[Modal] Filters synced successfully');
  6232. }
  6233. // Fonction pour charger les filtres dynamiques dans le modal
  6234. function loadDynamicFiltersToModal() {
  6235. console.log('[Modal] Loading dynamic filters...');
  6236. // Boutiques
  6237. const sidebarShopList = document.getElementById('shopList');
  6238. const modalShopList = document.getElementById('modalShopList');
  6239. if (modalShopList) { // Si la sidebar a déjà les boutiques, les copier
  6240. if (sidebarShopList && sidebarShopList.innerHTML.trim() && ! sidebarShopList.innerHTML.includes('Aucune boutique') && ! sidebarShopList.innerHTML.includes('seront chargées')) {
  6241. 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-');
  6242. modalShopList.innerHTML = shopHtml;
  6243. // Synchroniser la sélection
  6244. if (currentFilters.shop) {
  6245. const shopRadio = document.querySelector (`#modalShopList input[value=\"\${
  6246. currentFilters.shop
  6247. }\"]`);
  6248. if (shopRadio) 
  6249. shopRadio.checked = true;
  6250. } else {
  6251. const allShops = document.getElementById('modal-all-shops');
  6252. if (allShops) 
  6253. allShops.checked = true;
  6254. }
  6255. console.log('[Modal] Shops loaded from sidebar');
  6256. } else { // Si pas encore chargés, afficher un message ou laisser vide
  6257. if (! modalShopList.innerHTML.trim() || modalShopList.innerHTML.includes('seront chargées')) {
  6258. modalShopList.innerHTML = '<li class=\"filter-list\"><span class=\"text-muted\">Les boutiques seront chargées lors de l\\'application des filtres</span></li>';
  6259. }
  6260. }
  6261. }
  6262. // Couleurs
  6263. const sidebarColorList = document.getElementById('colorList');
  6264. const modalColorList = document.getElementById('modalColorList');
  6265. if (sidebarColorList && modalColorList) {
  6266. if (sidebarColorList.innerHTML.trim() && ! sidebarColorList.innerHTML.includes('Aucun')) {
  6267. 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-');
  6268. modalColorList.innerHTML = colorHtml;
  6269. // Synchroniser la sélection
  6270. if (currentFilters.color) {
  6271. const colorRadio = document.querySelector (`#modalColorList input[value=\"\${
  6272. currentFilters.color
  6273. }\"]`);
  6274. if (colorRadio) 
  6275. colorRadio.checked = true;
  6276. } else {
  6277. const allColor = document.getElementById('modal-all-color');
  6278. if (allColor) 
  6279. allColor.checked = true;
  6280. }
  6281. console.log('[Modal] Colors loaded');
  6282. }
  6283. }
  6284. // Tailles
  6285. const sidebarSizeList = document.getElementById('sizeList');
  6286. const modalSizeList = document.getElementById('modalSizeList');
  6287. if (sidebarSizeList && modalSizeList) {
  6288. if (sidebarSizeList.innerHTML.trim() && ! sidebarSizeList.innerHTML.includes('Aucun')) {
  6289. 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-');
  6290. modalSizeList.innerHTML = sizeHtml;
  6291. // Synchroniser la sélection
  6292. if (currentFilters.size) {
  6293. const sizeRadio = document.querySelector (`#modalSizeList input[value=\"\${
  6294. currentFilters.size
  6295. }\"]`);
  6296. if (sizeRadio) 
  6297. sizeRadio.checked = true;
  6298. } else {
  6299. const allSize = document.getElementById('modal-all-size');
  6300. if (allSize) 
  6301. allSize.checked = true;
  6302. }
  6303. console.log('[Modal] Sizes loaded');
  6304. }
  6305. }
  6306. // Matériaux
  6307. const sidebarMaterialList = document.getElementById('materialList');
  6308. const modalMaterialList = document.getElementById('modalMaterialList');
  6309. if (sidebarMaterialList && modalMaterialList) {
  6310. if (sidebarMaterialList.innerHTML.trim() && ! sidebarMaterialList.innerHTML.includes('Aucun')) {
  6311. 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-');
  6312. modalMaterialList.innerHTML = materialHtml;
  6313. // Synchroniser la sélection
  6314. if (currentFilters.material) {
  6315. const materialRadio = document.querySelector (`#modalMaterialList input[value=\"\${
  6316. currentFilters.material
  6317. }\"]`);
  6318. if (materialRadio) 
  6319. materialRadio.checked = true;
  6320. } else {
  6321. const allMaterial = document.getElementById('modal-all-material');
  6322. if (allMaterial) 
  6323. allMaterial.checked = true;
  6324. }
  6325. console.log('[Modal] Materials loaded');
  6326. }
  6327. }
  6328. }
  6329. // Synchroniser à l'ouverture du modal
  6330. const filtersModal = document.getElementById('filtersModal');
  6331. if (filtersModal) {
  6332. filtersModal.addEventListener('show.bs.modal', function () {
  6333. console.log('[Modal] Modal opening, syncing filters...');
  6334. // Charger les filtres dynamiques d'abord si pas encore chargés
  6335. loadDynamicFiltersToModal();
  6336. // Puis synchroniser les valeurs
  6337. setTimeout(() => {
  6338. syncModalFilters();
  6339. }, 100);
  6340. });
  6341. }
  6342. // Charger les filtres dynamiques au chargement initial si la sidebar les a déjà
  6343. loadDynamicFiltersToModal();
  6344. // Bouton Appliquer les filtres
  6345. const applyBtn = document.getElementById('modalApplyFiltersBtn');
  6346. if (applyBtn) {
  6347. applyBtn.addEventListener('click', function (e) {
  6348. e.preventDefault();
  6349. console.log('[Modal] Apply button clicked, collecting filter values...');
  6350. // Collecter les valeurs du modal
  6351. const brandRadio = document.querySelector('#modalBrandList input[name=\"modal-brand\"]:checked');
  6352. if (brandRadio) {
  6353. currentFilters.brand = brandRadio.id !== 'modal-all-brands' ? brandRadio.value : '';
  6354. console.log('[Modal] Brand:', currentFilters.brand);
  6355. } else {
  6356. currentFilters.brand = '';
  6357. console.log('[Modal] Brand: No selection found');
  6358. }
  6359. // Conditions
  6360. const conditionRadio = document.querySelector('#modalConditionList input[name=\"modal-condition\"]:checked');
  6361. if (conditionRadio) {
  6362. currentFilters.condition = conditionRadio.id !== 'modal-all-conditions' ? conditionRadio.value : '';
  6363. console.log('[Modal] Condition:', currentFilters.condition);
  6364. } else {
  6365. currentFilters.condition = '';
  6366. }
  6367. // Featured
  6368. const featuredRadio = document.querySelector('#filtersModal input[name=\"modal-featured\"]:checked');
  6369. if (featuredRadio) {
  6370. currentFilters.featured = featuredRadio.id !== 'modal-featured-all' ? featuredRadio.value : '';
  6371. console.log('[Modal] Featured:', currentFilters.featured);
  6372. } else {
  6373. currentFilters.featured = '';
  6374. }
  6375. // Digital
  6376. const digitalRadio = document.querySelector('#filtersModal input[name=\"modal-digital\"]:checked');
  6377. if (digitalRadio) {
  6378. currentFilters.digital = digitalRadio.id !== 'modal-digital-all' ? digitalRadio.value : '';
  6379. console.log('[Modal] Digital:', currentFilters.digital);
  6380. } else {
  6381. currentFilters.digital = '';
  6382. }
  6383. // Availability
  6384. const availabilityRadio = document.querySelector('#filtersModal input[name=\"modal-availability\"]:checked');
  6385. if (availabilityRadio) {
  6386. currentFilters.availability = availabilityRadio.id !== 'modal-availability-all' ? availabilityRadio.value : '';
  6387. console.log('[Modal] Availability:', currentFilters.availability);
  6388. } else {
  6389. currentFilters.availability = '';
  6390. }
  6391. // Rating
  6392. const ratingRadio = document.querySelector('#filtersModal input[name=\"modal-rating\"]:checked');
  6393. if (ratingRadio && ratingRadio.id !== 'modal-rating-all') {
  6394. currentFilters.ratingMin = ratingRadio.value;
  6395. currentFilters.rating = ratingRadio.value;
  6396. console.log('[Modal] Rating:', currentFilters.ratingMin);
  6397. } else {
  6398. currentFilters.ratingMin = '';
  6399. currentFilters.rating = '';
  6400. }
  6401. // Prix
  6402. const priceMinInput = document.getElementById('modal-priceMin');
  6403. const priceMaxInput = document.getElementById('modal-priceMax');
  6404. currentFilters.priceMin = priceMinInput ? priceMinInput.value.trim() : '';
  6405. currentFilters.priceMax = priceMaxInput ? priceMaxInput.value.trim() : '';
  6406. console.log('[Modal] Price:', currentFilters.priceMin, '-', currentFilters.priceMax);
  6407. // Poids
  6408. const weightMinInput = document.getElementById('modal-weightMin');
  6409. const weightMaxInput = document.getElementById('modal-weightMax');
  6410. currentFilters.weightMin = weightMinInput ? weightMinInput.value.trim() : '';
  6411. currentFilters.weightMax = weightMaxInput ? weightMaxInput.value.trim() : '';
  6412. console.log('[Modal] Weight:', currentFilters.weightMin, '-', currentFilters.weightMax);
  6413. // Couleur
  6414. const colorRadio = document.querySelector('#modalColorList input[name=\"modal-color\"]:checked');
  6415. if (colorRadio) {
  6416. currentFilters.color = colorRadio.id !== 'modal-all-color' ? colorRadio.value : '';
  6417. console.log('[Modal] Color:', currentFilters.color);
  6418. } else {
  6419. currentFilters.color = '';
  6420. }
  6421. // Taille
  6422. const sizeRadio = document.querySelector('#modalSizeList input[name=\"modal-size\"]:checked');
  6423. if (sizeRadio) {
  6424. currentFilters.size = sizeRadio.id !== 'modal-all-size' ? sizeRadio.value : '';
  6425. console.log('[Modal] Size:', currentFilters.size);
  6426. } else {
  6427. currentFilters.size = '';
  6428. }
  6429. // Matériau
  6430. const materialRadio = document.querySelector('#modalMaterialList input[name=\"modal-material\"]:checked');
  6431. if (materialRadio) {
  6432. currentFilters.material = materialRadio.id !== 'modal-all-material' ? materialRadio.value : '';
  6433. console.log('[Modal] Material:', currentFilters.material);
  6434. } else {
  6435. currentFilters.material = '';
  6436. }
  6437. // Boutiques
  6438. const shopRadio = document.querySelector('#modalShopList input[name=\"modal-shop\"]:checked');
  6439. if (shopRadio) {
  6440. currentFilters.shop = shopRadio.id !== 'modal-all-shops' ? shopRadio.value : '';
  6441. console.log('[Modal] Shop:', currentFilters.shop);
  6442. } else {
  6443. currentFilters.shop = '';
  6444. }
  6445. currentFilters.page = 1;
  6446. console.log('[Modal] All filters collected:', currentFilters);
  6447. // Fermer le modal d'abord
  6448. const modalElement = document.getElementById('filtersModal');
  6449. const modal = bootstrap.Modal.getInstance(modalElement);
  6450. if (modal) {
  6451. modal.hide();
  6452. console.log('[Modal] Modal closed');
  6453. }
  6454. // Appliquer les filtres après un court délai pour laisser le modal se fermer
  6455. setTimeout(() => { // Vérifier que applyFilters existe
  6456. if (typeof window.applyFilters === 'function') {
  6457. console.log('[Modal] Calling applyFilters()...');
  6458. window.applyFilters();
  6459. // Mettre à jour le compteur de filtres actifs
  6460. setTimeout(() => {
  6461. if (typeof updateActiveFiltersCount === 'function') {
  6462. updateActiveFiltersCount();
  6463. }
  6464. }, 100);
  6465. } else {
  6466. console.error('[Modal] applyFilters is not available!', typeof window.applyFilters);
  6467. // Essayer de recharger la page avec les nouveaux paramètres
  6468. const url = new URL(window.location.href);
  6469. Object.keys(currentFilters).forEach(key => {
  6470. if (currentFilters[key] && currentFilters[key] !== '') {
  6471. let apiKey = key;
  6472. if (key === 'priceMin') {
  6473. apiKey = 'price_min';
  6474. } else if (key === 'priceMax') {
  6475. apiKey = 'price_max';
  6476. } else if (key === 'ratingMin') {
  6477. apiKey = 'rating_min';
  6478. } else if (key === 'weightMin') {
  6479. apiKey = 'weight_min';
  6480. } else if (key === 'weightMax') {
  6481. apiKey = 'weight_max';
  6482. } else if (key === 'stockStatus') {
  6483. apiKey = 'stock_status';
  6484. }
  6485. url.searchParams.set(apiKey, currentFilters[key]);
  6486. } else {
  6487. let apiKey = key;
  6488. if (key === 'priceMin') {
  6489. apiKey = 'price_min';
  6490. } else if (key === 'priceMax') {
  6491. apiKey = 'price_max';
  6492. } else if (key === 'ratingMin') {
  6493. apiKey = 'rating_min';
  6494. } else if (key === 'weightMin') {
  6495. apiKey = 'weight_min';
  6496. } else if (key === 'weightMax') {
  6497. apiKey = 'weight_max';
  6498. } else if (key === 'stockStatus') {
  6499. apiKey = 'stock_status';
  6500. }
  6501. url.searchParams.delete(apiKey);
  6502. }
  6503. });
  6504. window.location.href = url.toString();
  6505. }
  6506. }, 300);
  6507. });
  6508. } else {
  6509. console.error('[Modal] Apply button not found!');
  6510. }
  6511. // Bouton Réinitialiser
  6512. const resetBtn = document.getElementById('modalResetFiltersBtn');
  6513. if (resetBtn) {
  6514. resetBtn.addEventListener('click', function () {
  6515. console.log('[Modal] Reset button clicked');
  6516. // Réinitialiser tous les filtres du modal
  6517. document.querySelectorAll('#filtersModal input[type=\"radio\"]').forEach(radio => {
  6518. if (radio.id && radio.id.includes('-all')) {
  6519. radio.checked = true;
  6520. } else {
  6521. radio.checked = false;
  6522. }
  6523. });
  6524. const priceMinInput = document.getElementById('modal-priceMin');
  6525. const priceMaxInput = document.getElementById('modal-priceMax');
  6526. const weightMinInput = document.getElementById('modal-weightMin');
  6527. const weightMaxInput = document.getElementById('modal-weightMax');
  6528. if (priceMinInput) 
  6529. priceMinInput.value = '';
  6530. if (priceMaxInput) 
  6531. priceMaxInput.value = '';
  6532. if (weightMinInput) 
  6533. weightMinInput.value = '';
  6534. if (weightMaxInput) 
  6535. weightMaxInput.value = '';
  6536. // Réinitialiser currentFilters (garder category et sort)
  6537. const category = currentFilters.category || '';
  6538. const sort = currentFilters.sort || 'newest';
  6539. currentFilters = {
  6540. category: category,
  6541. sort: sort,
  6542. page: 1,
  6543. brand: '',
  6544. condition: '',
  6545. featured: '',
  6546. digital: '',
  6547. availability: '',
  6548. ratingMin: '',
  6549. rating: '',
  6550. priceMin: '',
  6551. priceMax: '',
  6552. weightMin: '',
  6553. weightMax: '',
  6554. color: '',
  6555. size: '',
  6556. material: '',
  6557. shop: ''
  6558. };
  6559. console.log('[Modal] Filters reset, currentFilters:', currentFilters);
  6560. // Fermer le modal d'abord
  6561. const modalElement = document.getElementById('filtersModal');
  6562. const modal = bootstrap.Modal.getInstance(modalElement);
  6563. if (modal) {
  6564. modal.hide();
  6565. console.log('[Modal] Modal closed after reset');
  6566. }
  6567. // Appliquer les filtres après un court délai
  6568. setTimeout(() => {
  6569. if (typeof window.applyFilters === 'function') {
  6570. window.applyFilters();
  6571. } else {
  6572. console.error('[Modal] applyFilters not available, reloading page...');
  6573. // Recharger la page avec les filtres réinitialisés
  6574. const url = new URL(window.location.href);
  6575. url.searchParams.delete('price_min');
  6576. url.searchParams.delete('price_max');
  6577. url.searchParams.delete('rating_min');
  6578. url.searchParams.delete('weight_min');
  6579. url.searchParams.delete('weight_max');
  6580. url.searchParams.delete('brand');
  6581. url.searchParams.delete('condition');
  6582. url.searchParams.delete('featured');
  6583. url.searchParams.delete('digital');
  6584. url.searchParams.delete('availability');
  6585. url.searchParams.delete('color');
  6586. url.searchParams.delete('size');
  6587. url.searchParams.delete('material');
  6588. url.searchParams.delete('shop');
  6589. window.location.href = url.toString();
  6590. }
  6591. }, 300);
  6592. });
  6593. } else {
  6594. console.error('[Modal] Reset button not found!');
  6595. }
  6596. // S'assurer que les filtres dynamiques sont chargés au premier chargement si disponibles
  6597. setTimeout(() => {
  6598. loadDynamicFiltersToModal();
  6599. }, 500);
  6600. });
  6601. </script>{% endblock %}
  6602. ""home/listing.html.twig""/home/u540977899/domains/maketou-ht.com/public_html/templates/home/listing.html.twig");
  6603.     }
  6604. }