<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Preview Layout</title>
<link media="all" rel="stylesheet" href="../../css/plyr.css">
<link media="all" rel="stylesheet" href="../../css/swiper-bundle.min.css">
<link media="all" rel="stylesheet" href="../../css/styles.css">
<script>
window.APP = {
modules: {},
addModule: function(name, config) {
this.modules[name] = this.modules[name] || [];
this.modules[name].push(config);
},
DEBUG: 0,
CONFIG: {},
};
</script>
<style>
body {
margin: 25px !important;
}
</style>
</head>
<body style="background-color: " class="antialiased text-base font-sans ">
<script>
'use strict';
(function(hyva, undefined) {
function lifetimeToExpires(options, defaults) {
const lifetime = options.lifetime || defaults.lifetime;
if (lifetime) {
const date = new Date;
date.setTime(date.getTime() + lifetime * 1000);
return date;
}
return null;
}
function generateRandomString() {
const allowedCharacters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
length = 16;
let formKey = '',
charactersLength = allowedCharacters.length;
for (let i = 0; i < length; i++) {
formKey += allowedCharacters[Math.round(Math.random() * (charactersLength - 1))]
}
return formKey;
}
const sessionCookieMarker = {
noLifetime: true
}
const cookieTempStorage = {};
const internalCookie = {
get(name) {
const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
return v ? v[2] : null;
},
set(name, value, days, skipSetDomain) {
let expires,
path,
domain,
secure,
samesite;
const defaultCookieConfig = {
expires: null,
path: '/',
domain: null,
secure: false,
lifetime: null,
samesite: 'lax'
};
const cookieConfig = window.COOKIE_CONFIG || {};
expires = days && days !== sessionCookieMarker ?
lifetimeToExpires({
lifetime: 24 * 60 * 60 * days,
expires: null
}, defaultCookieConfig) :
lifetimeToExpires(window.COOKIE_CONFIG, defaultCookieConfig) || defaultCookieConfig.expires;
path = cookieConfig.path || defaultCookieConfig.path;
domain = !skipSetDomain && (cookieConfig.domain || defaultCookieConfig.domain);
secure = cookieConfig.secure || defaultCookieConfig.secure;
samesite = cookieConfig.samesite || defaultCookieConfig.samesite;
document.cookie = name + "=" + encodeURIComponent(value) +
(expires && days !== sessionCookieMarker ? '; expires=' + expires.toGMTString() : '') +
(path ? '; path=' + path : '') +
(domain ? '; domain=' + domain : '') +
(secure ? '; secure' : '') +
(samesite ? '; samesite=' + samesite : 'lax');
},
isWebsiteAllowedToSaveCookie() {
const allowedCookies = this.get('user_allowed_save_cookie');
if (allowedCookies) {
const allowedWebsites = JSON.parse(unescape(allowedCookies));
return allowedWebsites[CURRENT_WEBSITE_ID] === 1;
}
return false;
},
getGroupByCookieName(name) {
const cookieConsentConfig = window.cookie_consent_config || {};
let group = null;
for (let prop in cookieConsentConfig) {
if (!cookieConsentConfig.hasOwnProperty(prop)) continue;
if (cookieConsentConfig[prop].includes(name)) {
group = prop;
break;
}
}
return group;
},
isCookieAllowed(name) {
const cookieGroup = this.getGroupByCookieName(name);
return cookieGroup ?
window.cookie_consent_groups[cookieGroup] :
this.isWebsiteAllowedToSaveCookie();
},
saveTempStorageCookies() {
for (const [name, data] of Object.entries(cookieTempStorage)) {
if (this.isCookieAllowed(name)) {
this.set(name, data['value'], data['days'], data['skipSetDomain']);
delete cookieTempStorage[name];
}
}
}
};
hyva.getCookie = (name) => {
const cookieConfig = window.COOKIE_CONFIG || {};
if (cookieConfig.cookie_restriction_enabled && !internalCookie.isCookieAllowed(name)) {
return cookieTempStorage[name] ? cookieTempStorage[name]['value'] : null;
}
return internalCookie.get(name);
}
hyva.setCookie = (name, value, days, skipSetDomain) => {
const cookieConfig = window.COOKIE_CONFIG || {};
if (cookieConfig.cookie_restriction_enabled && !internalCookie.isCookieAllowed(name)) {
cookieTempStorage[name] = {
value,
days,
skipSetDomain
};
return;
}
return internalCookie.set(name, value, days, skipSetDomain);
}
hyva.setSessionCookie = (name, value, skipSetDomain) => {
return hyva.setCookie(name, value, sessionCookieMarker, skipSetDomain)
}
hyva.getBrowserStorage = () => {
const browserStorage = window.localStorage || window.sessionStorage;
if (!browserStorage) {
console.warn('Browser Storage is unavailable');
return false;
}
try {
browserStorage.setItem('storage_test', '1');
browserStorage.removeItem('storage_test');
} catch (error) {
console.warn('Browser Storage is not accessible', error);
return false;
}
return browserStorage;
}
hyva.postForm = (postParams) => {
const form = document.createElement("form");
let data = postParams.data;
if (!postParams.skipUenc && !data.uenc) {
data.uenc = btoa(window.location.href);
}
form.method = "POST";
form.action = postParams.action;
Object.keys(postParams.data).map(key => {
const field = document.createElement("input");
field.type = 'hidden'
field.value = postParams.data[key];
field.name = key;
form.appendChild(field);
});
const form_key = document.createElement("input");
form_key.type = 'hidden';
form_key.value = hyva.getFormKey();
form_key.name = "form_key";
form.appendChild(form_key);
document.body.appendChild(form);
form.submit();
}
hyva.getFormKey = function() {
let formKey = hyva.getCookie('form_key');
if (!formKey) {
formKey = generateRandomString();
hyva.setCookie('form_key', formKey);
}
return formKey;
}
hyva.formatPrice = (value, showSign, options = {}) => {
const formatter = new Intl.NumberFormat(
'en-US',
Object.assign({
style: 'currency',
currency: 'EUR',
signDisplay: showSign ? 'always' : 'auto'
}, options)
);
return (typeof Intl.NumberFormat.prototype.formatToParts === 'function') ?
formatter.formatToParts(value).map(({
type,
value
}) => {
switch (type) {
case 'currency':
return '€' || value;
case 'minusSign':
return '- ';
case 'plusSign':
return '+ ';
default:
return value;
}
}).reduce((string, part) => string + part) :
formatter.format(value);
}
/**
* Internal string replacement function implementation, see hyva.str() for usage details.
*
* @param string str Template string with optional placeholders
* @param int nStart Offset for placeholders, 0 means %0 is replaced with args[0], 1 means %1 is replaced with args[0]
* @param array ...args Positional replacement arguments. Rest arguments support isn't at 97% yet, so Array.from(arguments).slice() is used instead.
*/
const formatStr = function(str, nStart) {
const args = Array.from(arguments).slice(2);
return str.replace(/(%+)([0-9]+)/g, (m, p, n) => {
const idx = parseInt(n) - nStart;
if (args[idx] === null || args[idx] === void 0) {
return m;
}
return p.length % 2 ?
p.slice(0, -1).replace('%%', '%') + args[idx] :
p.replace('%%', '%') + n;
})
}
/**
* Replace positional parameters like %1 in string with the rest argument in the matching position.
* The first rest argument replaces %1, the second %2 and so on.
*
* Example: hyva.str('%3 %2 %1', 'a', 'b', 'c') => "c b a"
*
* To insert a literal % symbol followed by a number, duplicate the %, for example %%2 is returned as %2.
*/
hyva.str = function(string) {
const args = Array.from(arguments);
args.splice(1, 0, 1);
return formatStr.apply(undefined, args);
}
/**
* Zero based version of hyva.str(): the first rest argument replaces %0, the second %1 and so on.
*
* Example: hyva.strf('%2 %1 %0', 'a', 'b', 'c') => "c b a"
*
* If in doubt whether to use hyva.str() or hyva.strf(), prefer hyva.str() because it is more similar to __()
* and it might be possible to reuse existing phrase translations with placeholders.
*/
hyva.strf = function() {
const args = Array.from(arguments);
args.splice(1, 0, 0);
return formatStr.apply(undefined, args);
}
/**
* Take a html string as `content` parameter and
* extract an element from the DOM to replace in
* the current page under the same selector,
* defined by `targetSelector`
*/
hyva.replaceDomElement = (targetSelector, content) => {
// Parse the content and extract the DOM node using the `targetSelector`
const parser = new DOMParser();
const doc = parser.parseFromString(content, 'text/html');
const contentNode = doc.querySelector(targetSelector);
// Bail if content can't be found
if (!contentNode) {
return;
}
hyva.activateScripts(contentNode)
// Replace the old DOM node with the new content
document.querySelector(targetSelector).replaceWith(contentNode);
// Reload customerSectionData and display cookie-messages if present
window.dispatchEvent(new CustomEvent("reload-customer-section-data"));
hyva.initMessages();
}
hyva.activateScripts = (contentNode) => {
// Extract all the script tags from the content.
// Script tags won't execute when inserted into a dom-element directly,
// therefore we need to inject them to the head of the document.
const tmpScripts = contentNode.getElementsByTagName('script');
if (tmpScripts.length > 0) {
// Push all script tags into an array
// (to prevent dom manipulation while iterating over dom nodes)
const scripts = [];
for (let i = 0; i < tmpScripts.length; i++) {
scripts.push(tmpScripts[i]);
}
// Iterate over all script tags and duplicate+inject each into the head
for (let i = 0; i < scripts.length; i++) {
let script = document.createElement('script');
script.innerHTML = scripts[i].innerHTML;
document.head.appendChild(script);
// Remove the original (non-executing) node from the content
scripts[i].parentNode.removeChild(scripts[i]);
}
}
return contentNode;
}
/**
* Return base64 encoded current URL that can be used by Magento to redirect the visitor back to the current page.
* The func hyva.getUenc handles additional encoding of +, / and = like \Magento\Framework\Url\Encoder::encode().
*/
const replace = {
['+']: '-',
['/']: '_',
['=']: ','
};
hyva.getUenc = () => btoa(window.location.href).replace(/[+/=]/g, match => replace[match]);
let currentTrap;
const focusableElements = (rootElement) => {
const selector = 'button, [href], input, select, textarea, details, [tabindex]:not([tabindex="-1"]';
return Array.from(rootElement.querySelectorAll(selector))
.filter(el => {
return el.style.display !== 'none' &&
!el.disabled &&
el.tabIndex !== -1 &&
(el.offsetWidth || el.offsetHeight || el.getClientRects().length)
})
}
const focusTrap = (e) => {
const isTabPressed = e.key === 'Tab' || e.keyCode === 9;
if (!isTabPressed) return;
const focusable = focusableElements(currentTrap)
const firstFocusableElement = focusable[0]
const lastFocusableElement = focusable[focusable.length - 1]
e.shiftKey ?
document.activeElement === firstFocusableElement && (lastFocusableElement.focus(), e.preventDefault()) :
document.activeElement === lastFocusableElement && (firstFocusableElement.focus(), e.preventDefault())
};
hyva.releaseFocus = (rootElement) => {
if (currentTrap && (!rootElement || rootElement === currentTrap)) {
currentTrap.removeEventListener('keydown', focusTrap)
currentTrap = null
}
}
hyva.trapFocus = (rootElement) => {
if (!rootElement) return;
hyva.releaseFocus()
currentTrap = rootElement
rootElement.addEventListener('keydown', focusTrap)
const firstElement = focusableElements(rootElement)[0]
firstElement && firstElement.focus()
}
hyva.alpineInitialized = (fn) => window.addEventListener('alpine:initialized', fn, {
once: true
})
window.addEventListener('alpine:initialized', () => {
console.log('Alpine.js initialized')
})
window.addEventListener('user-allowed-save-cookie', () => internalCookie.saveTempStorageCookies())
}(window.hyva = window.hyva || {}));
</script>
<button type="button" x-data x-init="$store.asideBlocs.addAside('')" @click="$store.asideBlocs.toggleAside('')" class="max-md:btn-size-md btn btn-dark ">
Open side panel
</button>
<script src="../../js/gsap.min.js" defer crossorigin></script>
<script src="../../js/scrollTrigger.min.js" defer crossorigin></script>
<script src="../../js/swiper-bundle.min.js" defer crossorigin></script>
<script type="module" src="../../js/anchor.min.js" defer crossorigin></script>
<script type="module" src="../../js/persist.min.js" defer crossorigin></script>
<script type="module" src="../../js/intersect.min.js" defer crossorigin></script>
<script type="module" src="../../js/plyr.min.js" defer crossorigin></script>
<script type="module" src="../../js/collapse.min.js" defer crossorigin></script>
<script type="module" src="../../js/alpine3.min.js" defer crossorigin></script>
<script>
(g => {
var h, a, k, p = "The Google Maps JavaScript API",
c = "google",
l = "importLibrary",
q = "__ib__",
m = document,
b = window;
b = b[c] || (b[c] = {});
var d = b.maps || (b.maps = {}),
r = new Set,
e = new URLSearchParams,
u = () => h || (h = new Promise(async (f, n) => {
await (a = m.createElement("script"));
e.set("libraries", [...r] + "");
for (k in g) e.set(k.replace(/[A-Z]/g, t => "_" + t[0].toLowerCase()), g[k]);
e.set("callback", c + ".maps." + q);
a.src = `https://maps.${c}apis.com/maps/api/js?` + e;
d[q] = f;
a.onerror = () => h = n(Error(p + " could not load."));
a.nonce = m.querySelector("script[nonce]")?.nonce || "";
m.head.append(a)
}));
d[l] ? console.warn(p + " only loads once. Ignoring:", g) : d[l] = (f, ...n) => r.add(f) && u().then(() => d[l](f, ...n))
})({
key: "AIzaSyAmlBHNWuFlAL7elylRqvhRn4MD7ko0sWs",
v: "weekly",
// Use the 'v' parameter to indicate the version to use (weekly, beta, alpha, etc.).
// Add other bootstrap parameters as needed, using camel case.
});
</script>
<script src="../../js/markerclusterer.min.js" defer crossorigin></script>
<script>
window.addEventListener('alpine:init', () => {
console.log('Alpine.js has been initialized');
Alpine.store('screen', {
isMobile: window.matchMedia('(max-width: 768px)').matches,
isTablet: window.matchMedia('(max-width: 1024px)').matches,
// Méthode d'initialisation pour mettre à jour `isMobile` en fonction de la taille de l'écran
init() {
const mobileMedia = window.matchMedia('(max-width: 768px)');
const tabletMedia = window.matchMedia('(max-width: 1024px)');
this.isMobile = mobileMedia.matches;
this.isTablet = tabletMedia.matches;
const updateScreen = (event, type) => {
if (type === 'mobile') this.isMobile = event.matches;
if (type === 'tablet') this.isTablet = event.matches;
};
[{
media: mobileMedia,
type: 'mobile'
},
{
media: tabletMedia,
type: 'tablet'
}
].forEach(({
media,
type
}) => {
if (typeof media.onchange !== 'object') {
media.addListener((e) => updateScreen(e, type));
} else {
media.addEventListener('change', (e) => updateScreen(e, type));
}
});
}
});
Alpine.store('filter', {
filters: [],
getFilter(type, value) {
return this.filters.some((filter) => filter.type === type && filter.value === value);
},
toggleFilter(type, value) {
const filterArray = this.filters.selected[type];
const index = filterArray.indexOf(value);
if (index === -1) {
filterArray.push(value);
} else {
filterArray.splice(index, 1);
}
this.applyFilters();
},
clearFilters() {
this.filters = [];
}
});
Alpine.store('asideBlocs', {
asides: [],
// Ajouter un nouvel aside au tableau
addAside(name, customProperties = {}) {
if (!this.asides.find(aside => aside.name === name)) {
this.asides.push({
name,
open: false,
...customProperties, // Ajoute des propriétés spécifiques
});
}
},
// Supprimer un aside par son nom
removeAside(name) {
this.asides = this.asides.filter(aside => aside.name !== name);
},
// Basculer l'état d'ouverture d'un aside
toggleAside(name) {
const aside = this.asides.find(aside => aside.name === name);
if (aside) {
aside.open = !aside.open;
document.body.classList.toggle('overflow-hidden', aside.open);
}
},
// Fermer un aside spécifique
closeAside(name) {
const aside = this.asides.find(aside => aside.name === name);
if (aside) {
aside.open = false;
document.body.classList.remove('overflow-hidden');
}
},
// Ouvrir un aside spécifique
openAside(name) {
const aside = this.asides.find(aside => aside.name === name);
if (aside) {
aside.open = true;
document.body.classList.add('overflow-hidden');
}
}
});
Alpine.store('locator', {
allStores: [], // Liste complète des magasins
countStore: "",
filteredStores: [], // Liste des magasins filtrés
filteredDistanceStores: null, //Liste des magasins trié par distance
selectedStore: null, // Magasin sélectionné
isAudio: false, // Type de magasin affiché (true = audio, false = optique)
loading: true, // État de chargement
mapCenter: null, // Centre lat et lng de la google map
mapInstance: null, // Instance de la google map. ( /!\ Toutes les fonctions ne sont pas disponible )
// Listes des filtres disponibles (extraites des données)
filterLists: {
audio: {
mutuals: [], // Liste des mutuelles optique disponibles
brands: [], // Liste des marques optique disponibles
types: [] // Contiendra ['optic', 'teleophtalmologie'] selon disponibilité
},
optic: {
mutuals: [], // Liste des mutuelles optique disponibles
brands: [], // Liste des marques optique disponibles
types: [] // Contiendra ['audio', 'teleophtalmologie'] selon disponibilité
}
},
// Modification des selectedFilters pour inclure les types
selectedFilters: {
audio: {
mutuals: [], // Mutuelles audio sélectionnées
brands: [], // Marques audio sélectionnées
types: [] // Les types sélectionnés
},
optic: {
mutuals: [], // Mutuelles audio sélectionnées
brands: [], // Marques audio sélectionnées
types: [] // Les types sélectionnés
},
search: ''
},
// Défault donnée des prises de rdv
defaultStepperState: {
steps: [{
id: 1,
title: 'Où',
completed: true
},
{
id: 2,
title: 'Quoi',
completed: false
},
{
id: 3,
title: 'Quand',
completed: false
},
{
id: 4,
title: 'Qui',
completed: false
}
],
completed: false,
currentStep: 2,
code_mur: null,
type: null,
data: []
},
// Initialisation avec persist
stepperData: Alpine.$persist(function() {
return {
...this.defaultStepperState
}
}).as('stepperData'),
// Getters
get currentFilterList() {
return this.filterLists[this.currentType];
},
get currentType() {
return this.isAudio ? 'audio' : 'optic';
},
get currentSelectedFilters() {
return this.selectedFilters[this.currentType];
},
async init() {
try {
// Détection du type depuis l'URL
try {
const pathname = window.location.pathname;
this.isAudio = pathname.includes('/acousticien');
} catch (error) {
console.error('Erreur lors de la détection du type depuis l\'URL:', error);
this.isAudio = false; // Défault value
}
await this.loadLibraries();
await this.loadStores();
this.extractFiltersFromStores();
this.applyFilters();
} finally {
this.loading = false;
}
},
async loadLibraries() {
const [geometry] = await Promise.all([
google.maps.importLibrary("geometry"),
]);
},
async loadStores() {
try {
const response = await fetch(`${window.location.origin}/js/json/getListv2.json`);
const data = await response.json();
if (data.success) {
this.countStore = data.data.totalCount ?? data.data.items.length;
this.allStores = data.data.items;
}
} catch (error) {
console.error('Erreur lors du chargement des magasins:', error);
this.allStores = [];
}
},
extractFiltersFromStores() {
const storesArray = Object.values(this.allStores);
// Pour les magasins Audio
const audioStores = storesArray.filter(store =>
store.locations.some(location => location.is_audio)
);
// Comptage pour les mutuelles audio
const audioMutualCounts = new Map();
audioStores.forEach(store => {
store.locations
.filter(location => location.is_audio)
.forEach(location => {
(location.mutuelles || []).forEach(mutual => {
audioMutualCounts.set(mutual, (audioMutualCounts.get(mutual) || 0) + 1);
});
});
});
// Comptage pour les marques audio
const audioBrandCounts = new Map();
audioStores.forEach(store => {
store.locations
.filter(location => location.is_audio)
.forEach(location => {
(location.brands || []).forEach(brand => {
audioBrandCounts.set(brand, (audioBrandCounts.get(brand) || 0) + 1);
});
});
});
// Pour les magasins Optique
const opticStores = storesArray.filter(store =>
store.locations.some(location => !location.is_audio)
);
// Comptage pour les mutuelles optique
const opticMutualCounts = new Map();
opticStores.forEach(store => {
store.locations
.filter(location => !location.is_audio)
.forEach(location => {
(location.mutuelles || []).forEach(mutual => {
opticMutualCounts.set(mutual, (opticMutualCounts.get(mutual) || 0) + 1);
});
});
});
// Comptage pour les marques optique
const opticBrandCounts = new Map();
opticStores.forEach(store => {
store.locations
.filter(location => !location.is_audio)
.forEach(location => {
(location.brands || []).forEach(brand => {
opticBrandCounts.set(brand, (opticBrandCounts.get(brand) || 0) + 1);
});
});
});
this.filterLists.audio = {
mutuals: Array.from(audioMutualCounts.entries())
.map(([id, count]) => ({
id,
label: id,
count
}))
.sort((a, b) => a.label.localeCompare(b.label)),
brands: Array.from(audioBrandCounts.entries())
.map(([id, count]) => ({
id,
label: id,
count
}))
.sort((a, b) => a.label.localeCompare(b.label)),
types: this.extractAvailableTypes(audioStores, true)
};
this.filterLists.optic = {
mutuals: Array.from(opticMutualCounts.entries())
.map(([id, count]) => ({
id,
label: id,
count
}))
.sort((a, b) => a.label.localeCompare(b.label)),
brands: Array.from(opticBrandCounts.entries())
.map(([id, count]) => ({
id,
label: id,
count
}))
.sort((a, b) => a.label.localeCompare(b.label)),
types: this.extractAvailableTypes(opticStores, false)
};
},
// Méthode pour extraire les types disponibles
extractAvailableTypes(stores, isAudio) {
const types = new Set();
const counts = {
[isAudio ? 'optic' : 'audio']: 0,
teleophtalmologie: 0
};
stores.forEach(store => {
// Vérifie si le magasin a l'autre type de service
const hasOtherService = store.locations.some(location =>
isAudio ? !location.is_audio : location.is_audio
);
if (hasOtherService) {
types.add(isAudio ? 'optic' : 'audio');
counts[isAudio ? 'optic' : 'audio']++;
}
// Vérifie si le magasin a la téléophtalmologie
const hasTelephtalmology = store.locations.some(location =>
location.attributes.teleophtalmologie?.value === "1"
);
if (hasTelephtalmology) {
types.add('teleophtalmologie');
counts.teleophtalmologie++;
}
});
// Retourne un tableau d'objets avec le type et son count
return Array.from(types).sort().map(type => ({
id: type,
label: type,
count: counts[type]
}));
},
// Dans toggleStoreType du store locator
toggleStoreType(isAudio) {
if (isAudio !== undefined) {
this.isAudio = isAudio;
} else {
this.isAudio = !this.isAudio;
}
// Mise à jour de l'URL
try {
const currentUrl = new URL(window.location.href);
const params = new URLSearchParams(currentUrl.search);
// Changer le pathname en gardant la même base
const newPathname = currentUrl.pathname.replace(
/(\/opticien|\/acousticien)/,
this.isAudio ? '/acousticien' : '/opticien'
);
// Construire la nouvelle URL avec les paramètres existants
const newUrl = `${currentUrl.origin}${newPathname}${params.toString() ? '?' + params.toString() : ''}`;
// Mettre à jour l'URL sans recharger la page
window.history.pushState({}, '', newUrl);
} catch (error) {
console.error('Erreur lors de la mise à jour de l\'URL:', error);
}
this.selectedStore = null;
this.clearFilters();
this.applyFilters();
},
updateFilter(category, value) {
const filters = this.selectedFilters[this.currentType][category];
const index = filters.indexOf(value.id || value);
if (index === -1) {
filters.push(value.id || value);
} else {
filters.splice(index, 1);
}
},
updateSearchTerm(term) {
this.filters.selected.search = term;
this.applyFilters();
},
clearFilters() {
this.selectedFilters[this.currentType].mutuals = [];
this.selectedFilters[this.currentType].brands = [];
this.selectedFilters[this.currentType].types = [];
this.selectedFilters.search = '';
this.applyFilters();
},
// Application des filtres modifiée
applyFilters() {
const searchTerm = this.selectedFilters.search?.toLowerCase() || '';
const selectedMutuals = this.selectedFilters[this.currentType].mutuals || [];
const selectedBrands = this.selectedFilters[this.currentType].brands || [];
const selectedTypes = this.selectedFilters[this.currentType].types || [];
const filteredStores = Object.values(this.allStores).filter(store => {
// 1. Vérification du service principal (toujours requis)
const hasMainService = store.locations.some(location =>
this.isAudio ? location.is_audio : !location.is_audio
);
if (!hasMainService) return false;
// 2. Vérification des types additionnels si sélectionnés
if (selectedTypes.length > 0) {
// Vérifie l'autre service si sélectionné
const otherServiceType = this.isAudio ? 'optic' : 'audio';
if (selectedTypes.includes(otherServiceType)) {
const hasOtherService = store.locations.some(location =>
this.isAudio ? !location.is_audio : location.is_audio
);
if (!hasOtherService) return false;
}
// Vérifie la téléophtalmologie si sélectionnée
if (selectedTypes.includes('teleophtalmologie')) {
const hasTelephtalmology = store.locations.some(location =>
location.attributes.teleophtalmologie?.value === "1"
);
if (!hasTelephtalmology) return false;
}
}
// Filtres existants inchangés
if (searchTerm && !this.matchesSearch(store, searchTerm)) return false;
// Filtre par mutuelles
if (selectedMutuals.length > 0) {
const storeMutuals = store.locations
.filter(location => this.isAudio ? location.is_audio : !location.is_audio)
.flatMap(location => location.mutuelles || []);
if (!selectedMutuals.some(mutualId => storeMutuals.includes(mutualId))) {
return false;
}
}
if (selectedBrands.length > 0) {
const storeBrands = store.locations
.filter(location => this.isAudio ? location.is_audio : !location.is_audio)
.flatMap(location => location.brands || []);
if (!selectedBrands.some(brandId => storeBrands.includes(brandId))) {
return false;
}
}
return true;
});
this.filteredStores = filteredStores;
this.updateDistances(filteredStores);
},
// Mise à jour de la méthode matchesSearch également si nécessaire
matchesSearch(store, searchTerm) {
return store.locations.some(location =>
location.name?.toLowerCase().includes(searchTerm) ||
store.city?.toLowerCase().includes(searchTerm) ||
store.zip?.toLowerCase().includes(searchTerm)
);
},
selectStore(store) {
this.selectedStore = store;
},
isFilterSelected(category, value) {
return this.selectedFilters[category].includes(value);
},
updateDistances(filteredStores) {
if (!this.mapInstance) return;
const center = this.mapInstance.getCenter();
const stores = Object.values(filteredStores || {});
// Créer de nouveaux objets avec les distances au lieu de modifier les existants
this.filteredDistanceStores = stores.map(store => {
const distance = google.maps.geometry.spherical.computeDistanceBetween(
center, {
lat: parseFloat(store.lat),
lng: parseFloat(store.lng)
}
);
// Retourner un nouvel objet au lieu de modifier l'original
return {
...store,
distance: (distance / 1000).toFixed(1) + ' km'
};
}).sort((a, b) => {
const distA = parseFloat(a.distance);
const distB = parseFloat(b.distance);
return distA - distB;
});
},
setMapCenter(lat, lng, zoom) {
if (!isNaN(lat) && !isNaN(lng)) {
this.mapInstance.panTo({
lat,
lng
})
}
if (zoom) {
this.mapInstance.setZoom(zoom)
}
},
goToStore(store) {
if (!this.mapInstance || !store) return;
const lat = parseFloat(store.lat);
const lng = parseFloat(store.lng);
if (isNaN(lat) || isNaN(lng)) return;
this.setMapCenter(lat, lng, 15)
this.selectStore(store);
},
// PARTIE: PRISE DE RENDEZ-VOUS
initStepper(store) {
this.stepperData = {
...this.defaultStepperState,
code_mur: store.mur_code,
type: this.isAudio,
data: [{
title: 'MAGASIN',
value: `${store.address}, ${store.zip} ${store.city}`
}]
};
},
updateStepperData(title, value, targetStep) {
if (!this.stepperData) return;
// Trouver l'index de la donnée existante
const currentIndex = this.stepperData.data.findIndex(item => item.title === title);
// Mise à jour ou ajout des données
if (currentIndex === -1) {
// Ajout d'une nouvelle entrée
this.stepperData.data.push({
title,
value
});
} else if (this.stepperData.data[currentIndex].value !== value) {
// Si la valeur change, on supprime les données suivantes
this.stepperData.data = this.stepperData.data.slice(0, currentIndex + 1);
this.stepperData.data[currentIndex] = {
title,
value
};
}
// Mise à jour du step et des états des étapes si nécessaire
if (targetStep) {
this.stepperData.currentStep = targetStep;
this.stepperData.steps = this.stepperData.steps.map(step => ({
...step,
completed: step.id < targetStep
}));
}
// Alpine.persist se charge automatiquement de la persistance
return this.stepperData;
},
completeStepperData(appointmentId) {
if (!this.stepperData) return;
// Marquer toutes les étapes comme complétées
this.stepperData.steps = this.stepperData.steps.map(step => ({
...step,
completed: true
}));
// Marquer le stepper comme complété et ajouter l'ID du rendez-vous
this.stepperData.completed = true;
this.stepperData.appointmentId = appointmentId;
},
goToStepperStep(targetStep) {
// Vérifier que l'étape cible est valide
if (!this.stepperData || targetStep < 1 || targetStep > 4) return;
// Si on retourne à l'étape 1, réinitialiser complètement
if (targetStep === 1) {
this.resetStepperData();
this.$store.asideBlocs.closeAside('storeLocatorAppointment');
return;
}
// Mettre à jour l'étape courante et l'état des étapes
this.stepperData.currentStep = targetStep;
this.stepperData.steps = this.stepperData.steps.map(step => ({
...step,
completed: step.id < targetStep
}));
},
resetStepperData() {
// Réinitialiser avec les valeurs par défaut
this.stepperData = Alpine.persist({
...this.defaultStepperState
}, 'stepperData');
}
});
});
</script>
</body>
</html>
{% extends '@layout' %}
{% block yield %}
{% render "@template-button" with {
mobile_size:'md',
label:'Open side panel',
color: colorType|default('dark'),
button_attribute: ('x-data x-init="$store.asideBlocs.addAside(\'' ~ _target.meta.name ~ '\')" @click="$store.asideBlocs.toggleAside(\'' ~ _target.meta.name ~ '\')"')|replace({"\'": "'"}),
} %}
{{ yield }}
{% endblock %}
/* No context defined. */
No notes defined.