{"id":283103,"date":"2025-02-04T14:08:03","date_gmt":"2025-02-04T13:08:03","guid":{"rendered":"https:\/\/www.wescona.com\/?page_id=283103"},"modified":"2025-03-25T11:28:14","modified_gmt":"2025-03-25T10:28:14","slug":"demowescobs","status":"publish","type":"page","link":"https:\/\/www.wescona.com\/fr\/demowescobs\/","title":{"rendered":"Produits \/ Demo"},"content":{"rendered":"<p>[et_pb_section fb_built=\u00a0\u00bb1&Prime; admin_label=\u00a0\u00bbsection\u00a0\u00bb _builder_version=\u00a0\u00bb4.16&Prime; global_colors_info=\u00a0\u00bb{}\u00a0\u00bb][et_pb_row admin_label=\u00a0\u00bbrow\u00a0\u00bb _builder_version=\u00a0\u00bb4.19.1&Prime; background_size=\u00a0\u00bbinitial\u00a0\u00bb background_position=\u00a0\u00bbtop_left\u00a0\u00bb background_repeat=\u00a0\u00bbrepeat\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb][et_pb_column type=\u00a0\u00bb4_4&Prime; _builder_version=\u00a0\u00bb4.16&Prime; custom_padding=\u00a0\u00bb|||\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb custom_padding__hover=\u00a0\u00bb|||\u00a0\u00bb][\/et_pb_column][\/et_pb_row][\/et_pb_section][et_pb_section fb_built=\u00a0\u00bb1&Prime; _builder_version=\u00a0\u00bb4.19.1&Prime; _module_preset=\u00a0\u00bbdefault\u00a0\u00bb width=\u00a0\u00bb100%\u00a0\u00bb max_width=\u00a0\u00bb100%\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb][et_pb_row _builder_version=\u00a0\u00bb4.19.1&Prime; _module_preset=\u00a0\u00bbdefault\u00a0\u00bb width=\u00a0\u00bb100%\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb][et_pb_column type=\u00a0\u00bb4_4&Prime; _builder_version=\u00a0\u00bb4.19.1&Prime; _module_preset=\u00a0\u00bbdefault\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb][et_pb_code _builder_version=\u00a0\u00bb4.19.1&Prime; _module_preset=\u00a0\u00bbdefault\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb]<div class=\"shop-widget\">\r\n  <!-- Sidebar -->\r\n  <aside class=\"shop-widget-sidebar\">\r\n    <div class=\"filter-widget\">\r\n      <div class=\"filter-title\">\r\n        <p>Filtrer par<\/p>\r\n      <\/div>\r\n\r\n      <!-- Section Gammes (g\u00e9n\u00e9r\u00e9e dynamiquement) -->\r\n      <div class=\"filter-section\">\r\n        <input type=\"checkbox\" id=\"toggle-gammes-section\" class=\"toggle-sections\" \/>\r\n        <label class=\"filter-section-title\" for=\"toggle-gammes-section\">\r\n          Gammes\r\n        <\/label>\r\n        <ul class=\"filter-options\" id=\"gamme-filter-options\">\r\n          <!-- Contenu g\u00e9n\u00e9r\u00e9 dynamiquement -->\r\n        <\/ul>\r\n      <\/div>\r\n\r\n      <!-- Section Prix avec slider double -->\r\n      <div class=\"filter-section\">\r\n        <input type=\"checkbox\" id=\"toggle-price-section\" class=\"toggle-sections\" \/>\r\n        <label class=\"filter-section-title\" for=\"toggle-price-section\">\r\n          Prix\r\n        <\/label>\r\n        <div class=\"filter-options\" id=\"price-filter-options\">\r\n          <div class=\"price-slider-container\">\r\n            <div class=\"price-values\">\r\n              <span id=\"price-min-value\">- \u20ac<\/span>\r\n              <span id=\"price-max-value\">- \u20ac<\/span>\r\n            <\/div>\r\n            <div class=\"slider-track\" id=\"slider-track\"><\/div>\r\n            <input type=\"range\" id=\"price-min-slider\" min=\"0\" max=\"1000\" step=\"1\" value=\"0\" \/>\r\n            <input type=\"range\" id=\"price-max-slider\" min=\"0\" max=\"1000\" step=\"1\" value=\"1000\" \/>\r\n          <\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <!-- Bouton pour appliquer les filtres -->\r\n      <div class=\"filter-section\">\r\n        <button id=\"shop-apply-filters\">Appliquer les filtres<\/button>\r\n      <\/div>\r\n    <\/div>\r\n  <\/aside>\r\n\r\n  <!-- Zone d'affichage des produits -->\r\n  <main class=\"product-grid\">\r\n    <div class=\"shop-cards-container\" id=\"shop-cards-container\"><\/div>\r\n    <div id=\"pagination-container\" class=\"pagination\"><\/div>\r\n  <\/main>\r\n<\/div>\r\n\r\n<!-- Inclusion de PapaParse -->\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/PapaParse\/5.3.2\/papaparse.min.js\"><\/script>\r\n\r\n<script>\r\ndocument.addEventListener(\"DOMContentLoaded\", async () => {\r\n  const csvUrl = \"https:\/\/docs.google.com\/spreadsheets\/d\/e\/2PACX-1vSBM-mBXPy2Kkq7klhOm9G3GNd-h6fiiadyZXXnsu724H_ozjvMysiQgeBXfUuBTEOSrnOe5Zb5Uw9b\/pub?gid=0&single=true&output=csv\";\r\n\r\n  \/\/ Variables pour la pagination\r\n  let currentPage;\r\n  const productsPerPage = 12;\r\n  let filteredProducts = [];\r\n\r\n  \/\/ R\u00e9cup\u00e9ration du param\u00e8tre \"prodpage\" dans l'URL\r\n  const urlParams = new URLSearchParams(window.location.search);\r\n  const pageParam = parseInt(urlParams.get(\"prodpage\"), 10);\r\n  currentPage = (!isNaN(pageParam) && pageParam > 0) ? pageParam : 1;\r\n\r\n  let globalHeaders = [];\r\n  let globalProducts = [];\r\n\r\n  \/\/ Stocke l'\u00e9tat de r\u00e9f\u00e9rence des filtres afin de d\u00e9sactiver le bouton s'il n'y a pas de changement\r\n  let baselineFilters = {};\r\n\r\n  \/\/ --- Fonctions utilitaires pour le bouton \"Appliquer les filtres\" ---\r\n  function getCurrentFilters() {\r\n    return {\r\n      priceMin: document.getElementById(\"price-min-slider\").value,\r\n      priceMax: document.getElementById(\"price-max-slider\").value,\r\n      gammes: Array.from(document.querySelectorAll('input[name=\"gamme\"]:checked'))\r\n                  .map(cb => cb.value)\r\n                  .sort()\r\n                  .join(',')\r\n    };\r\n  }\r\n\r\n  function setBaselineFilters() {\r\n    baselineFilters = getCurrentFilters();\r\n  }\r\n\r\n  function updateApplyButtonState() {\r\n    const current = getCurrentFilters();\r\n    const button = document.getElementById(\"shop-apply-filters\");\r\n    if (JSON.stringify(current) === JSON.stringify(baselineFilters)) {\r\n      button.disabled = true;\r\n    } else {\r\n      button.disabled = false;\r\n    }\r\n  }\r\n  \/\/ ---------------------------------------------------------------\r\n\r\n  \/\/ Met \u00e0 jour l'URL avec les filtres et la page en cours\r\n  function updateURL(page) {\r\n    const url = new URL(window.location);\r\n    url.searchParams.set(\"prodpage\", page);\r\n\r\n    const priceMinSlider = document.getElementById(\"price-min-slider\");\r\n    const priceMaxSlider = document.getElementById(\"price-max-slider\");\r\n    url.searchParams.set(\"priceMin\", priceMinSlider.value);\r\n    url.searchParams.set(\"priceMax\", priceMaxSlider.value);\r\n\r\n    const selectedGammes = Array.from(document.querySelectorAll('input[name=\"gamme\"]:checked')).map(cb => cb.value);\r\n    if (selectedGammes.length > 0) {\r\n      url.searchParams.set(\"gammes\", selectedGammes.join(\",\"));\r\n    } else {\r\n      url.searchParams.delete(\"gammes\");\r\n    }\r\n    window.history.replaceState({}, \"\", url);\r\n  }\r\n\r\n  async function fetchCSV(url) {\r\n    try {\r\n      const response = await fetch(url);\r\n      const text = await response.text();\r\n      const separator = text.includes(\";\") ? \";\" : \",\";\r\n      const results = Papa.parse(text, {\r\n        delimiter: separator,\r\n        skipEmptyLines: true,\r\n      });\r\n      return results.data;\r\n    } catch (error) {\r\n      console.error(\"Erreur de r\u00e9cup\u00e9ration du CSV:\", error);\r\n      return [];\r\n    }\r\n  }\r\n\r\n  \/\/ Applique les filtres (prix et gamme) et retourne les produits filtr\u00e9s\r\n  function applyFilters() {\r\n    const minPrice = parseFloat(document.getElementById(\"price-min-slider\").value);\r\n    const maxPrice = parseFloat(document.getElementById(\"price-max-slider\").value);\r\n    const selectedGammes = Array.from(\r\n      document.querySelectorAll('input[name=\"gamme\"]:checked')\r\n    ).map(cb => cb.value);\r\n\r\n    const headerNames = globalHeaders.map(h => h.trim().toLowerCase());\r\n    const priceIndex = headerNames.indexOf(\"price\");\r\n    const gammeIndex = headerNames.indexOf(\"gamme\");\r\n\r\n    return globalProducts.filter(product => {\r\n      if (product.length < globalHeaders.length) return false;\r\n      let price = 0;\r\n      if (priceIndex !== -1) {\r\n        price = parseFloat(product[priceIndex].replace(\",\", \".\"));\r\n        if (isNaN(price)) return false;\r\n      }\r\n      let gamme = \"\";\r\n      if (gammeIndex !== -1) {\r\n        gamme = product[gammeIndex].trim().toLowerCase();\r\n      }\r\n      let valid = true;\r\n      if (price < minPrice || price > maxPrice) valid = false;\r\n      if (selectedGammes.length > 0 && !selectedGammes.includes(gamme))\r\n        valid = false;\r\n      return valid;\r\n    });\r\n  }\r\n\r\n  \/\/ G\u00e9n\u00e8re les cartes produits pour la page donn\u00e9e\r\n  function renderProductsPage(page) {\r\n    const container = document.getElementById(\"shop-cards-container\");\r\n    container.innerHTML = \"\";\r\n    const headerNames = globalHeaders.map(h => h.trim().toLowerCase());\r\n    const productNameIndex = headerNames.indexOf(\"productname\");\r\n    const imgUrlIndex = headerNames.indexOf(\"imgurl\");\r\n    const pdfUrlIndex = headerNames.indexOf(\"pdfurl\");\r\n    const priceIndex = headerNames.indexOf(\"price\");\r\n    const gammeIndex = headerNames.indexOf(\"gamme\");\r\n\r\n    const start = (page - 1) * productsPerPage;\r\n    const end = start + productsPerPage;\r\n    const productsToShow = filteredProducts.slice(start, end);\r\n\r\n    productsToShow.forEach(product => {\r\n      const productName = product[productNameIndex].trim();\r\n      const imgUrl = product[imgUrlIndex].trim();\r\n      const pdfUrl = product[pdfUrlIndex].trim();\r\n      const price = (priceIndex !== -1)\r\n        ? parseFloat(product[priceIndex].replace(\",\", \".\"))\r\n        : null;\r\n      const gamme = (gammeIndex !== -1) ? product[gammeIndex].trim() : \"\";\r\n\r\n      const card = document.createElement(\"div\");\r\n      card.className = \"shop-card\";\r\n      card.setAttribute(\"data-price\", price);\r\n      card.setAttribute(\"data-gamme\", gamme.toLowerCase());\r\n\r\n      card.innerHTML = `\r\n        <img decoding=\"async\" src=\"${imgUrl}\" alt=\"${productName}\">\r\n        <p class=\"shop-card-productname\">${productName}<\/p>\r\n        ${price !== null ? `<p class=\"shop-card-price\">${price.toFixed(2)} \u20ac<\/p>` : \"\"}\r\n        <a class=\"shop-card-btn\" href=\"${pdfUrl}\" target=\"_blank\">Voir la fiche produit<\/a>\r\n      `;\r\n      container.appendChild(card);\r\n    });\r\n\r\n    renderPaginationControls();\r\n  }\r\n\r\n  \/\/ G\u00e9n\u00e8re les contr\u00f4les de pagination avec maximum 5 boutons num\u00e9riques\r\n  function renderPaginationControls() {\r\n    const paginationContainer = document.getElementById(\"pagination-container\");\r\n    paginationContainer.innerHTML = \"\";\r\n    const totalPages = Math.ceil(filteredProducts.length \/ productsPerPage);\r\n    const maxButtons = 4;\r\n\r\n    let startPage = currentPage - 2;\r\n    let endPage = currentPage + 2;\r\n    if (startPage < 1) {\r\n      startPage = 1;\r\n      endPage = Math.min(maxButtons, totalPages);\r\n    }\r\n    if (endPage > totalPages) {\r\n      endPage = totalPages;\r\n      startPage = Math.max(1, totalPages - maxButtons + 1);\r\n    }\r\n\r\n    \/\/ Bouton \"Pr\u00e9c\u00e9dent\"\r\n    const prevButton = document.createElement(\"button\");\r\n    prevButton.textContent = \"\u25c0\ufe0e\";\r\n    prevButton.disabled = currentPage === 1;\r\n    prevButton.addEventListener(\"click\", () => {\r\n      if (currentPage > 1) {\r\n        currentPage--;\r\n        updateURL(currentPage);\r\n        renderProductsPage(currentPage);\r\n      }\r\n    });\r\n    paginationContainer.appendChild(prevButton);\r\n\r\n    \/\/ Ellipse \u00e0 gauche si n\u00e9cessaire\r\n    if (startPage > 1) {\r\n      const leftEllipsis = document.createElement(\"span\");\r\n      leftEllipsis.textContent = \"...\";\r\n      leftEllipsis.style.padding = \"0 8px\";\r\n      paginationContainer.appendChild(leftEllipsis);\r\n    }\r\n\r\n    \/\/ Boutons num\u00e9riques\r\n    for (let i = startPage; i <= endPage; i++) {\r\n      const pageButton = document.createElement(\"button\");\r\n      pageButton.textContent = i;\r\n      if (i === currentPage) pageButton.classList.add(\"active\");\r\n      pageButton.addEventListener(\"click\", () => {\r\n        currentPage = i;\r\n        updateURL(currentPage);\r\n        renderProductsPage(currentPage);\r\n      });\r\n      paginationContainer.appendChild(pageButton);\r\n    }\r\n\r\n    \/\/ Ellipse \u00e0 droite si n\u00e9cessaire\r\n    if (endPage < totalPages) {\r\n      const rightEllipsis = document.createElement(\"span\");\r\n      rightEllipsis.textContent = \"...\";\r\n      rightEllipsis.style.padding = \"0 8px\";\r\n      paginationContainer.appendChild(rightEllipsis);\r\n    }\r\n\r\n    \/\/ Bouton \"Suivant\"\r\n    const nextButton = document.createElement(\"button\");\r\n    nextButton.textContent = \"\u25b6\ufe0e\";\r\n    nextButton.disabled = currentPage === totalPages;\r\n    nextButton.addEventListener(\"click\", () => {\r\n      if (currentPage < totalPages) {\r\n        currentPage++;\r\n        updateURL(currentPage);\r\n        renderProductsPage(currentPage);\r\n      }\r\n    });\r\n    paginationContainer.appendChild(nextButton);\r\n  }\r\n\r\n  \/\/ Met \u00e0 jour l'affichage des produits en appliquant les filtres\r\n  \/\/ resetPage est vrai lors d'un changement de filtre (r\u00e9initialise \u00e0 1)\r\n  function updateProductsDisplay(resetPage = true) {\r\n    filteredProducts = applyFilters();\r\n    if (resetPage) {\r\n      currentPage = 1;\r\n    }\r\n    updateURL(currentPage);\r\n    renderProductsPage(currentPage);\r\n  }\r\n\r\n  \/\/ G\u00e9n\u00e8re la liste des gammes disponibles avec leur compte\r\n  function generateGammeFilterOptions() {\r\n    const headerNames = globalHeaders.map(h => h.trim().toLowerCase());\r\n    const gammeIndex = headerNames.indexOf(\"gamme\");\r\n    if (gammeIndex === -1) {\r\n      console.warn(\"La colonne 'gamme' est absente du CSV.\");\r\n      return;\r\n    }\r\n    const gammeCounts = {};\r\n    globalProducts.forEach(product => {\r\n      if (product.length < globalHeaders.length) return;\r\n      const gamme = product[gammeIndex].trim();\r\n      if (!gamme) return;\r\n      const key = gamme.toLowerCase();\r\n      gammeCounts[key] = (gammeCounts[key] || 0) + 1;\r\n    });\r\n    const container = document.getElementById(\"gamme-filter-options\");\r\n    container.innerHTML = \"\";\r\n    Object.keys(gammeCounts).sort().forEach(gammeValue => {\r\n      const count = gammeCounts[gammeValue];\r\n      const li = document.createElement(\"li\");\r\n      li.className = \"filter-option\";\r\n      li.innerHTML = `\r\n        <label>\r\n          <input type=\"checkbox\" name=\"gamme\" value=\"${gammeValue}\" \/>\r\n          ${gammeValue.charAt(0).toUpperCase() + gammeValue.slice(1)}\r\n          <span class=\"count\">(${count})<\/span>\r\n        <\/label>\r\n      `;\r\n      container.appendChild(li);\r\n\r\n      \/\/ Ajoute un \u00e9couteur pour mettre \u00e0 jour l'\u00e9tat du bouton\r\n      const checkbox = li.querySelector('input[name=\"gamme\"]');\r\n      checkbox.addEventListener(\"change\", updateApplyButtonState);\r\n    });\r\n  }\r\n\r\n  \/\/ Met \u00e0 jour le slider de prix selon les valeurs du CSV\r\n  function updatePriceSlider() {\r\n    const headerNames = globalHeaders.map(h => h.trim().toLowerCase());\r\n    const priceIndex = headerNames.indexOf(\"price\");\r\n    if (priceIndex === -1) return;\r\n    let computedMinPrice = Infinity;\r\n    let computedMaxPrice = -Infinity;\r\n    globalProducts.forEach(product => {\r\n      if (product.length < globalHeaders.length) return;\r\n      const price = parseFloat(product[priceIndex].replace(\",\", \".\"));\r\n      if (isNaN(price)) return;\r\n      if (price < computedMinPrice) computedMinPrice = price;\r\n      if (price > computedMaxPrice) computedMaxPrice = price;\r\n    });\r\n    const priceMinSlider = document.getElementById(\"price-min-slider\");\r\n    const priceMaxSlider = document.getElementById(\"price-max-slider\");\r\n    priceMinSlider.min = computedMinPrice;\r\n    priceMinSlider.max = computedMaxPrice;\r\n    priceMinSlider.value = computedMinPrice;\r\n    priceMaxSlider.min = computedMinPrice;\r\n    priceMaxSlider.max = computedMaxPrice;\r\n    priceMaxSlider.value = computedMaxPrice;\r\n    document.getElementById(\"price-min-value\").textContent = computedMinPrice + \" \u20ac\";\r\n    document.getElementById(\"price-max-value\").textContent = computedMaxPrice + \" \u20ac\";\r\n  }\r\n\r\n  \/\/ Les sliders mettent \u00e0 jour leur affichage mais n'appliquent pas directement les filtres\r\n  function initPriceSliderEvents() {\r\n    const priceMinSlider = document.getElementById(\"price-min-slider\");\r\n    const priceMaxSlider = document.getElementById(\"price-max-slider\");\r\n\r\n    priceMinSlider.addEventListener(\"input\", function () {\r\n      let minVal = parseFloat(this.value);\r\n      let maxVal = parseFloat(priceMaxSlider.value);\r\n      if (minVal > maxVal) {\r\n        this.value = maxVal;\r\n        minVal = maxVal;\r\n      }\r\n      document.getElementById(\"price-min-value\").textContent = minVal + \" \u20ac\";\r\n      updateApplyButtonState();\r\n    });\r\n\r\n    priceMaxSlider.addEventListener(\"input\", function () {\r\n      let maxVal = parseFloat(this.value);\r\n      let minVal = parseFloat(priceMinSlider.value);\r\n      if (maxVal < minVal) {\r\n        this.value = minVal;\r\n        maxVal = minVal;\r\n      }\r\n      document.getElementById(\"price-max-value\").textContent = maxVal + \" \u20ac\";\r\n      updateApplyButtonState();\r\n    });\r\n  }\r\n\r\n  \/\/ Initialise les filtres \u00e0 partir des param\u00e8tres de l'URL\r\n  function initializeFiltersFromURL() {\r\n    const urlParams = new URLSearchParams(window.location.search);\r\n    const priceMinParam = urlParams.get(\"priceMin\");\r\n    const priceMaxParam = urlParams.get(\"priceMax\");\r\n    const gammesParam = urlParams.get(\"gammes\");\r\n\r\n    const priceMinSlider = document.getElementById(\"price-min-slider\");\r\n    const priceMaxSlider = document.getElementById(\"price-max-slider\");\r\n\r\n    if (priceMinParam !== null) {\r\n      const value = parseFloat(priceMinParam);\r\n      if (!isNaN(value) &&\r\n          value >= parseFloat(priceMinSlider.min) &&\r\n          value <= parseFloat(priceMinSlider.max)) {\r\n        priceMinSlider.value = value;\r\n        document.getElementById(\"price-min-value\").textContent = value + \" \u20ac\";\r\n      }\r\n    }\r\n    if (priceMaxParam !== null) {\r\n      const value = parseFloat(priceMaxParam);\r\n      if (!isNaN(value) &&\r\n          value >= parseFloat(priceMaxSlider.min) &&\r\n          value <= parseFloat(priceMaxSlider.max)) {\r\n        priceMaxSlider.value = value;\r\n        document.getElementById(\"price-max-value\").textContent = value + \" \u20ac\";\r\n      }\r\n    }\r\n    if (gammesParam !== null) {\r\n      const gammes = gammesParam.split(\",\");\r\n      gammes.forEach(g => {\r\n        const checkbox = document.querySelector(`input[name=\"gamme\"][value=\"${g}\"]`);\r\n        if (checkbox) {\r\n          checkbox.checked = true;\r\n        }\r\n      });\r\n    }\r\n  }\r\n\r\n  \/\/ \u00c9v\u00e9nement sur le bouton \"Appliquer les filtres\"\r\n  document.getElementById(\"shop-apply-filters\").addEventListener(\"click\", () => {\r\n    updateProductsDisplay(true);\r\n    \/\/ Mise \u00e0 jour de l'\u00e9tat de r\u00e9f\u00e9rence apr\u00e8s application\r\n    setBaselineFilters();\r\n    updateApplyButtonState();\r\n  });\r\n\r\n  \/\/ Chargement du CSV et initialisation\r\n  const csvData = await fetchCSV(csvUrl);\r\n  if (csvData.length > 1) {\r\n    globalHeaders = csvData[0];\r\n    globalProducts = csvData.slice(1);\r\n    generateGammeFilterOptions();\r\n    updatePriceSlider();\r\n    initPriceSliderEvents();\r\n\r\n    \/\/ Initialise les filtres depuis l'URL AVANT d'afficher les produits\r\n    initializeFiltersFromURL();\r\n    updateProductsDisplay(false);\r\n    \/\/ D\u00e9finir l'\u00e9tat de r\u00e9f\u00e9rence et mettre \u00e0 jour le bouton\r\n    setBaselineFilters();\r\n    updateApplyButtonState();\r\n  }\r\n});\r\n<\/script>\r\n[\/et_pb_code][\/et_pb_column][\/et_pb_row][\/et_pb_section]<\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"shop-widget\">\r\n  <!-- Sidebar -->\r\n  <aside class=\"shop-widget-sidebar\">\r\n    <div class=\"filter-widget\">\r\n      <div class=\"filter-title\">\r\n        <p>Filtrer par<\/p>\r\n      <\/div>\r\n\r\n      <!-- Section Gammes (g\u00e9n\u00e9r\u00e9e dynamiquement) -->\r\n      <div class=\"filter-section\">\r\n        <input type=\"checkbox\" id=\"toggle-gammes-section\" class=\"toggle-sections\" \/>\r\n        <label class=\"filter-section-title\" for=\"toggle-gammes-section\">\r\n          Gammes\r\n        <\/label>\r\n        <ul class=\"filter-options\" id=\"gamme-filter-options\">\r\n          <!-- Contenu g\u00e9n\u00e9r\u00e9 dynamiquement -->\r\n        <\/ul>\r\n      <\/div>\r\n\r\n      <!-- Section Prix avec slider double -->\r\n      <div class=\"filter-section\">\r\n        <input type=\"checkbox\" id=\"toggle-price-section\" class=\"toggle-sections\" \/>\r\n        <label class=\"filter-section-title\" for=\"toggle-price-section\">\r\n          Prix\r\n        <\/label>\r\n        <div class=\"filter-options\" id=\"price-filter-options\">\r\n          <div class=\"price-slider-container\">\r\n            <div class=\"price-values\">\r\n              <span id=\"price-min-value\">- \u20ac<\/span>\r\n              <span id=\"price-max-value\">- \u20ac<\/span>\r\n            <\/div>\r\n            <div class=\"slider-track\" id=\"slider-track\"><\/div>\r\n            <input type=\"range\" id=\"price-min-slider\" min=\"0\" max=\"1000\" step=\"1\" value=\"0\" \/>\r\n            <input type=\"range\" id=\"price-max-slider\" min=\"0\" max=\"1000\" step=\"1\" value=\"1000\" \/>\r\n          <\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <!-- Bouton pour appliquer les filtres -->\r\n      <div class=\"filter-section\">\r\n        <button id=\"shop-apply-filters\">Appliquer les filtres<\/button>\r\n      <\/div>\r\n    <\/div>\r\n  <\/aside>\r\n\r\n  <!-- Zone d'affichage des produits -->\r\n  <main class=\"product-grid\">\r\n    <div class=\"shop-cards-container\" id=\"shop-cards-container\"><\/div>\r\n    <div id=\"pagination-container\" class=\"pagination\"><\/div>\r\n  <\/main>\r\n<\/div>\r\n\r\n<!-- Inclusion de PapaParse -->\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/PapaParse\/5.3.2\/papaparse.min.js\"><\/script>\r\n\r\n<script>\r\ndocument.addEventListener(\"DOMContentLoaded\", async () => {\r\n  const csvUrl = \"https:\/\/docs.google.com\/spreadsheets\/d\/e\/2PACX-1vSBM-mBXPy2Kkq7klhOm9G3GNd-h6fiiadyZXXnsu724H_ozjvMysiQgeBXfUuBTEOSrnOe5Zb5Uw9b\/pub?gid=0&single=true&output=csv\";\r\n\r\n  \/\/ Variables pour la pagination\r\n  let currentPage;\r\n  const productsPerPage = 12;\r\n  let filteredProducts = [];\r\n\r\n  \/\/ R\u00e9cup\u00e9ration du param\u00e8tre \"prodpage\" dans l'URL\r\n  const urlParams = new URLSearchParams(window.location.search);\r\n  const pageParam = parseInt(urlParams.get(\"prodpage\"), 10);\r\n  currentPage = (!isNaN(pageParam) && pageParam > 0) ? pageParam : 1;\r\n\r\n  let globalHeaders = [];\r\n  let globalProducts = [];\r\n\r\n  \/\/ Stocke l'\u00e9tat de r\u00e9f\u00e9rence des filtres afin de d\u00e9sactiver le bouton s'il n'y a pas de changement\r\n  let baselineFilters = {};\r\n\r\n  \/\/ --- Fonctions utilitaires pour le bouton \"Appliquer les filtres\" ---\r\n  function getCurrentFilters() {\r\n    return {\r\n      priceMin: document.getElementById(\"price-min-slider\").value,\r\n      priceMax: document.getElementById(\"price-max-slider\").value,\r\n      gammes: Array.from(document.querySelectorAll('input[name=\"gamme\"]:checked'))\r\n                  .map(cb => cb.value)\r\n                  .sort()\r\n                  .join(',')\r\n    };\r\n  }\r\n\r\n  function setBaselineFilters() {\r\n    baselineFilters = getCurrentFilters();\r\n  }\r\n\r\n  function updateApplyButtonState() {\r\n    const current = getCurrentFilters();\r\n    const button = document.getElementById(\"shop-apply-filters\");\r\n    if (JSON.stringify(current) === JSON.stringify(baselineFilters)) {\r\n      button.disabled = true;\r\n    } else {\r\n      button.disabled = false;\r\n    }\r\n  }\r\n  \/\/ ---------------------------------------------------------------\r\n\r\n  \/\/ Met \u00e0 jour l'URL avec les filtres et la page en cours\r\n  function updateURL(page) {\r\n    const url = new URL(window.location);\r\n    url.searchParams.set(\"prodpage\", page);\r\n\r\n    const priceMinSlider = document.getElementById(\"price-min-slider\");\r\n    const priceMaxSlider = document.getElementById(\"price-max-slider\");\r\n    url.searchParams.set(\"priceMin\", priceMinSlider.value);\r\n    url.searchParams.set(\"priceMax\", priceMaxSlider.value);\r\n\r\n    const selectedGammes = Array.from(document.querySelectorAll('input[name=\"gamme\"]:checked')).map(cb => cb.value);\r\n    if (selectedGammes.length > 0) {\r\n      url.searchParams.set(\"gammes\", selectedGammes.join(\",\"));\r\n    } else {\r\n      url.searchParams.delete(\"gammes\");\r\n    }\r\n    window.history.replaceState({}, \"\", url);\r\n  }\r\n\r\n  async function fetchCSV(url) {\r\n    try {\r\n      const response = await fetch(url);\r\n      const text = await response.text();\r\n      const separator = text.includes(\";\") ? \";\" : \",\";\r\n      const results = Papa.parse(text, {\r\n        delimiter: separator,\r\n        skipEmptyLines: true,\r\n      });\r\n      return results.data;\r\n    } catch (error) {\r\n      console.error(\"Erreur de r\u00e9cup\u00e9ration du CSV:\", error);\r\n      return [];\r\n    }\r\n  }\r\n\r\n  \/\/ Applique les filtres (prix et gamme) et retourne les produits filtr\u00e9s\r\n  function applyFilters() {\r\n    const minPrice = parseFloat(document.getElementById(\"price-min-slider\").value);\r\n    const maxPrice = parseFloat(document.getElementById(\"price-max-slider\").value);\r\n    const selectedGammes = Array.from(\r\n      document.querySelectorAll('input[name=\"gamme\"]:checked')\r\n    ).map(cb => cb.value);\r\n\r\n    const headerNames = globalHeaders.map(h => h.trim().toLowerCase());\r\n    const priceIndex = headerNames.indexOf(\"price\");\r\n    const gammeIndex = headerNames.indexOf(\"gamme\");\r\n\r\n    return globalProducts.filter(product => {\r\n      if (product.length < globalHeaders.length) return false;\r\n      let price = 0;\r\n      if (priceIndex !== -1) {\r\n        price = parseFloat(product[priceIndex].replace(\",\", \".\"));\r\n        if (isNaN(price)) return false;\r\n      }\r\n      let gamme = \"\";\r\n      if (gammeIndex !== -1) {\r\n        gamme = product[gammeIndex].trim().toLowerCase();\r\n      }\r\n      let valid = true;\r\n      if (price < minPrice || price > maxPrice) valid = false;\r\n      if (selectedGammes.length > 0 && !selectedGammes.includes(gamme))\r\n        valid = false;\r\n      return valid;\r\n    });\r\n  }\r\n\r\n  \/\/ G\u00e9n\u00e8re les cartes produits pour la page donn\u00e9e\r\n  function renderProductsPage(page) {\r\n    const container = document.getElementById(\"shop-cards-container\");\r\n    container.innerHTML = \"\";\r\n    const headerNames = globalHeaders.map(h => h.trim().toLowerCase());\r\n    const productNameIndex = headerNames.indexOf(\"productname\");\r\n    const imgUrlIndex = headerNames.indexOf(\"imgurl\");\r\n    const pdfUrlIndex = headerNames.indexOf(\"pdfurl\");\r\n    const priceIndex = headerNames.indexOf(\"price\");\r\n    const gammeIndex = headerNames.indexOf(\"gamme\");\r\n\r\n    const start = (page - 1) * productsPerPage;\r\n    const end = start + productsPerPage;\r\n    const productsToShow = filteredProducts.slice(start, end);\r\n\r\n    productsToShow.forEach(product => {\r\n      const productName = product[productNameIndex].trim();\r\n      const imgUrl = product[imgUrlIndex].trim();\r\n      const pdfUrl = product[pdfUrlIndex].trim();\r\n      const price = (priceIndex !== -1)\r\n        ? parseFloat(product[priceIndex].replace(\",\", \".\"))\r\n        : null;\r\n      const gamme = (gammeIndex !== -1) ? product[gammeIndex].trim() : \"\";\r\n\r\n      const card = document.createElement(\"div\");\r\n      card.className = \"shop-card\";\r\n      card.setAttribute(\"data-price\", price);\r\n      card.setAttribute(\"data-gamme\", gamme.toLowerCase());\r\n\r\n      card.innerHTML = `\r\n        <img src=\"${imgUrl}\" alt=\"${productName}\">\r\n        <p class=\"shop-card-productname\">${productName}<\/p>\r\n        ${price !== null ? `<p class=\"shop-card-price\">${price.toFixed(2)} \u20ac<\/p>` : \"\"}\r\n        <a class=\"shop-card-btn\" href=\"${pdfUrl}\" target=\"_blank\">Voir la fiche produit<\/a>\r\n      `;\r\n      container.appendChild(card);\r\n    });\r\n\r\n    renderPaginationControls();\r\n  }\r\n\r\n  \/\/ G\u00e9n\u00e8re les contr\u00f4les de pagination avec maximum 5 boutons num\u00e9riques\r\n  function renderPaginationControls() {\r\n    const paginationContainer = document.getElementById(\"pagination-container\");\r\n    paginationContainer.innerHTML = \"\";\r\n    const totalPages = Math.ceil(filteredProducts.length \/ productsPerPage);\r\n    const maxButtons = 4;\r\n\r\n    let startPage = currentPage - 2;\r\n    let endPage = currentPage + 2;\r\n    if (startPage < 1) {\r\n      startPage = 1;\r\n      endPage = Math.min(maxButtons, totalPages);\r\n    }\r\n    if (endPage > totalPages) {\r\n      endPage = totalPages;\r\n      startPage = Math.max(1, totalPages - maxButtons + 1);\r\n    }\r\n\r\n    \/\/ Bouton \"Pr\u00e9c\u00e9dent\"\r\n    const prevButton = document.createElement(\"button\");\r\n    prevButton.textContent = \"\u25c0\ufe0e\";\r\n    prevButton.disabled = currentPage === 1;\r\n    prevButton.addEventListener(\"click\", () => {\r\n      if (currentPage > 1) {\r\n        currentPage--;\r\n        updateURL(currentPage);\r\n        renderProductsPage(currentPage);\r\n      }\r\n    });\r\n    paginationContainer.appendChild(prevButton);\r\n\r\n    \/\/ Ellipse \u00e0 gauche si n\u00e9cessaire\r\n    if (startPage > 1) {\r\n      const leftEllipsis = document.createElement(\"span\");\r\n      leftEllipsis.textContent = \"...\";\r\n      leftEllipsis.style.padding = \"0 8px\";\r\n      paginationContainer.appendChild(leftEllipsis);\r\n    }\r\n\r\n    \/\/ Boutons num\u00e9riques\r\n    for (let i = startPage; i <= endPage; i++) {\r\n      const pageButton = document.createElement(\"button\");\r\n      pageButton.textContent = i;\r\n      if (i === currentPage) pageButton.classList.add(\"active\");\r\n      pageButton.addEventListener(\"click\", () => {\r\n        currentPage = i;\r\n        updateURL(currentPage);\r\n        renderProductsPage(currentPage);\r\n      });\r\n      paginationContainer.appendChild(pageButton);\r\n    }\r\n\r\n    \/\/ Ellipse \u00e0 droite si n\u00e9cessaire\r\n    if (endPage < totalPages) {\r\n      const rightEllipsis = document.createElement(\"span\");\r\n      rightEllipsis.textContent = \"...\";\r\n      rightEllipsis.style.padding = \"0 8px\";\r\n      paginationContainer.appendChild(rightEllipsis);\r\n    }\r\n\r\n    \/\/ Bouton \"Suivant\"\r\n    const nextButton = document.createElement(\"button\");\r\n    nextButton.textContent = \"\u25b6\ufe0e\";\r\n    nextButton.disabled = currentPage === totalPages;\r\n    nextButton.addEventListener(\"click\", () => {\r\n      if (currentPage < totalPages) {\r\n        currentPage++;\r\n        updateURL(currentPage);\r\n        renderProductsPage(currentPage);\r\n      }\r\n    });\r\n    paginationContainer.appendChild(nextButton);\r\n  }\r\n\r\n  \/\/ Met \u00e0 jour l'affichage des produits en appliquant les filtres\r\n  \/\/ resetPage est vrai lors d'un changement de filtre (r\u00e9initialise \u00e0 1)\r\n  function updateProductsDisplay(resetPage = true) {\r\n    filteredProducts = applyFilters();\r\n    if (resetPage) {\r\n      currentPage = 1;\r\n    }\r\n    updateURL(currentPage);\r\n    renderProductsPage(currentPage);\r\n  }\r\n\r\n  \/\/ G\u00e9n\u00e8re la liste des gammes disponibles avec leur compte\r\n  function generateGammeFilterOptions() {\r\n    const headerNames = globalHeaders.map(h => h.trim().toLowerCase());\r\n    const gammeIndex = headerNames.indexOf(\"gamme\");\r\n    if (gammeIndex === -1) {\r\n      console.warn(\"La colonne 'gamme' est absente du CSV.\");\r\n      return;\r\n    }\r\n    const gammeCounts = {};\r\n    globalProducts.forEach(product => {\r\n      if (product.length < globalHeaders.length) return;\r\n      const gamme = product[gammeIndex].trim();\r\n      if (!gamme) return;\r\n      const key = gamme.toLowerCase();\r\n      gammeCounts[key] = (gammeCounts[key] || 0) + 1;\r\n    });\r\n    const container = document.getElementById(\"gamme-filter-options\");\r\n    container.innerHTML = \"\";\r\n    Object.keys(gammeCounts).sort().forEach(gammeValue => {\r\n      const count = gammeCounts[gammeValue];\r\n      const li = document.createElement(\"li\");\r\n      li.className = \"filter-option\";\r\n      li.innerHTML = `\r\n        <label>\r\n          <input type=\"checkbox\" name=\"gamme\" value=\"${gammeValue}\" \/>\r\n          ${gammeValue.charAt(0).toUpperCase() + gammeValue.slice(1)}\r\n          <span class=\"count\">(${count})<\/span>\r\n        <\/label>\r\n      `;\r\n      container.appendChild(li);\r\n\r\n      \/\/ Ajoute un \u00e9couteur pour mettre \u00e0 jour l'\u00e9tat du bouton\r\n      const checkbox = li.querySelector('input[name=\"gamme\"]');\r\n      checkbox.addEventListener(\"change\", updateApplyButtonState);\r\n    });\r\n  }\r\n\r\n  \/\/ Met \u00e0 jour le slider de prix selon les valeurs du CSV\r\n  function updatePriceSlider() {\r\n    const headerNames = globalHeaders.map(h => h.trim().toLowerCase());\r\n    const priceIndex = headerNames.indexOf(\"price\");\r\n    if (priceIndex === -1) return;\r\n    let computedMinPrice = Infinity;\r\n    let computedMaxPrice = -Infinity;\r\n    globalProducts.forEach(product => {\r\n      if (product.length < globalHeaders.length) return;\r\n      const price = parseFloat(product[priceIndex].replace(\",\", \".\"));\r\n      if (isNaN(price)) return;\r\n      if (price < computedMinPrice) computedMinPrice = price;\r\n      if (price > computedMaxPrice) computedMaxPrice = price;\r\n    });\r\n    const priceMinSlider = document.getElementById(\"price-min-slider\");\r\n    const priceMaxSlider = document.getElementById(\"price-max-slider\");\r\n    priceMinSlider.min = computedMinPrice;\r\n    priceMinSlider.max = computedMaxPrice;\r\n    priceMinSlider.value = computedMinPrice;\r\n    priceMaxSlider.min = computedMinPrice;\r\n    priceMaxSlider.max = computedMaxPrice;\r\n    priceMaxSlider.value = computedMaxPrice;\r\n    document.getElementById(\"price-min-value\").textContent = computedMinPrice + \" \u20ac\";\r\n    document.getElementById(\"price-max-value\").textContent = computedMaxPrice + \" \u20ac\";\r\n  }\r\n\r\n  \/\/ Les sliders mettent \u00e0 jour leur affichage mais n'appliquent pas directement les filtres\r\n  function initPriceSliderEvents() {\r\n    const priceMinSlider = document.getElementById(\"price-min-slider\");\r\n    const priceMaxSlider = document.getElementById(\"price-max-slider\");\r\n\r\n    priceMinSlider.addEventListener(\"input\", function () {\r\n      let minVal = parseFloat(this.value);\r\n      let maxVal = parseFloat(priceMaxSlider.value);\r\n      if (minVal > maxVal) {\r\n        this.value = maxVal;\r\n        minVal = maxVal;\r\n      }\r\n      document.getElementById(\"price-min-value\").textContent = minVal + \" \u20ac\";\r\n      updateApplyButtonState();\r\n    });\r\n\r\n    priceMaxSlider.addEventListener(\"input\", function () {\r\n      let maxVal = parseFloat(this.value);\r\n      let minVal = parseFloat(priceMinSlider.value);\r\n      if (maxVal < minVal) {\r\n        this.value = minVal;\r\n        maxVal = minVal;\r\n      }\r\n      document.getElementById(\"price-max-value\").textContent = maxVal + \" \u20ac\";\r\n      updateApplyButtonState();\r\n    });\r\n  }\r\n\r\n  \/\/ Initialise les filtres \u00e0 partir des param\u00e8tres de l'URL\r\n  function initializeFiltersFromURL() {\r\n    const urlParams = new URLSearchParams(window.location.search);\r\n    const priceMinParam = urlParams.get(\"priceMin\");\r\n    const priceMaxParam = urlParams.get(\"priceMax\");\r\n    const gammesParam = urlParams.get(\"gammes\");\r\n\r\n    const priceMinSlider = document.getElementById(\"price-min-slider\");\r\n    const priceMaxSlider = document.getElementById(\"price-max-slider\");\r\n\r\n    if (priceMinParam !== null) {\r\n      const value = parseFloat(priceMinParam);\r\n      if (!isNaN(value) &&\r\n          value >= parseFloat(priceMinSlider.min) &&\r\n          value <= parseFloat(priceMinSlider.max)) {\r\n        priceMinSlider.value = value;\r\n        document.getElementById(\"price-min-value\").textContent = value + \" \u20ac\";\r\n      }\r\n    }\r\n    if (priceMaxParam !== null) {\r\n      const value = parseFloat(priceMaxParam);\r\n      if (!isNaN(value) &&\r\n          value >= parseFloat(priceMaxSlider.min) &&\r\n          value <= parseFloat(priceMaxSlider.max)) {\r\n        priceMaxSlider.value = value;\r\n        document.getElementById(\"price-max-value\").textContent = value + \" \u20ac\";\r\n      }\r\n    }\r\n    if (gammesParam !== null) {\r\n      const gammes = gammesParam.split(\",\");\r\n      gammes.forEach(g => {\r\n        const checkbox = document.querySelector(`input[name=\"gamme\"][value=\"${g}\"]`);\r\n        if (checkbox) {\r\n          checkbox.checked = true;\r\n        }\r\n      });\r\n    }\r\n  }\r\n\r\n  \/\/ \u00c9v\u00e9nement sur le bouton \"Appliquer les filtres\"\r\n  document.getElementById(\"shop-apply-filters\").addEventListener(\"click\", () => {\r\n    updateProductsDisplay(true);\r\n    \/\/ Mise \u00e0 jour de l'\u00e9tat de r\u00e9f\u00e9rence apr\u00e8s application\r\n    setBaselineFilters();\r\n    updateApplyButtonState();\r\n  });\r\n\r\n  \/\/ Chargement du CSV et initialisation\r\n  const csvData = await fetchCSV(csvUrl);\r\n  if (csvData.length > 1) {\r\n    globalHeaders = csvData[0];\r\n    globalProducts = csvData.slice(1);\r\n    generateGammeFilterOptions();\r\n    updatePriceSlider();\r\n    initPriceSliderEvents();\r\n\r\n    \/\/ Initialise les filtres depuis l'URL AVANT d'afficher les produits\r\n    initializeFiltersFromURL();\r\n    updateProductsDisplay(false);\r\n    \/\/ D\u00e9finir l'\u00e9tat de r\u00e9f\u00e9rence et mettre \u00e0 jour le bouton\r\n    setBaselineFilters();\r\n    updateApplyButtonState();\r\n  }\r\n});\r\n<\/script>\r\n\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_et_pb_use_builder":"on","_et_pb_old_content":"","_et_gb_content_width":""},"_links":{"self":[{"href":"https:\/\/www.wescona.com\/fr\/wp-json\/wp\/v2\/pages\/283103"}],"collection":[{"href":"https:\/\/www.wescona.com\/fr\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.wescona.com\/fr\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.wescona.com\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wescona.com\/fr\/wp-json\/wp\/v2\/comments?post=283103"}],"version-history":[{"count":9,"href":"https:\/\/www.wescona.com\/fr\/wp-json\/wp\/v2\/pages\/283103\/revisions"}],"predecessor-version":[{"id":283142,"href":"https:\/\/www.wescona.com\/fr\/wp-json\/wp\/v2\/pages\/283103\/revisions\/283142"}],"wp:attachment":[{"href":"https:\/\/www.wescona.com\/fr\/wp-json\/wp\/v2\/media?parent=283103"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}