<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>
<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>
/* No context defined. */
No notes defined.