<div x-data="datePicker()" x-init="init().then()" class="w-full max-w-3xl mx-auto space-y-8">
    <h1 class="text-3xl font-normal mb-8">Date et heure du rendez-vous</h1>

    <div class="bg-white rounded-3xl p-8">

        <div class="flex justify-center items-center mb-8 relative">
            <button @click="handlePreviousClick().then()" class="absolute left-0 w-10 h-10 flex items-center justify-center rounded-full bg-neutral-100 hover:bg-neutral-200 transition-colors">
                <svg width="24" height="24" viewBox="0 0 24 24" fill="none" class="text-neutral-600">
                    <path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
                </svg>
            </button>

            <span x-text="displayRange" class="text-xl font-normal"></span>

            <button @click="handleNextClick().then()" class="absolute right-0 w-10 h-10 flex items-center justify-center rounded-full bg-neutral-100 hover:bg-neutral-200 transition-colors">
                <svg width="24" height="24" viewBox="0 0 24 24" fill="none" class="text-neutral-600">
                    <path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
                </svg>
            </button>
        </div>

        <div class="grid grid-cols-7 gap-0">

            <template x-for="day in ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim']">
                <div class="text-center py-2 text-neutral-900 font-normal text-lg" x-text="day"></div>
            </template>

            <template x-for="day in days">
                <div class="relative border-[0.5px] border-neutral-200">
                    <button @click="selectDate(day)" class="w-full aspect-square flex items-center justify-center relative" :class="{
								'bg-green-50': day.isAvailable,
								'cursor-pointer hover:bg-green-100': day.isAvailable,
								'cursor-not-allowed bg-neutral-50': !day.isAvailable
							}" :disabled="!day.isAvailable">

                        <div x-show="!day.isAvailable" class="absolute inset-0 bg-[linear-gradient(to_top_right,transparent_calc(50%_-_1px),#e5e7eb,transparent_calc(50%_+_1px))]">
                        </div>

                        <div class="relative flex flex-col items-center" :class="{
								 'text-green-700': day.isAvailable,
								 'text-neutral-400': !day.isAvailable
							 }">
                            <span class="text-lg" x-text="day.dayOfMonth"></span>
                            <div x-show="day.isAvailable" class="w-1.5 h-1.5 rounded-full bg-green-700 mt-1">
                            </div>
                        </div>

                        <div x-show="selectedDate && day.date.getTime() === selectedDate.getTime()" class="absolute inset-0 border-2 border-green-700 pointer-events-none">
                        </div>
                    </button>
                </div>
            </template>
        </div>

        <div class="mt-4 flex items-center gap-2 text-green-700">
            <div class="w-1.5 h-1.5 rounded-full bg-green-700"></div>
            <span class="text-sm">Jours avec des horaires disponibles</span>
        </div>
    </div>

    <div x-show="selectedDate" class="space-y-4">
        <h2 class="text-xl">Sélectionner l'horaire</h2>
        <div class="grid grid-cols-4 gap-4">
            <template x-for="slot in availableHours" :key="slot.initialHour">
                <button @click="selectTime(slot.initialHour)" class="py-3 px-6 rounded-full border border-gray-200 hover:border-brand-400 transition-colors" :class="{
                        'bg-brand-50 border-brand-400': selectedTime === slot.initialHour,
                        'bg-white': selectedTime !== slot.initialHour
                    }">
                    <span x-text="slot.initialHour"></span>
                </button>
            </template>
        </div>
    </div>
</div>

<script>
    function datePicker() {
        return {
            // États initiaux explicitement définis
            startDate: new Date(), // On initialise directement avec une date par défaut
            selectedDate: null,
            selectedTime: null,
            days: [],
            availabilities: {},
            availableHours: [],
            isLoading: false,
            displayRange: '',
            initialized: false,
            // Getter pour accéder aux données du stepper
            get stepperData() {
                return this.$store.locator.stepperData;
            },
            // Nouvelle méthode d'initialisation
            async init() {
                if (this.initialized) return;
                try {
                    this.isLoading = true;
                    // Initialisation avec la date du jour
                    const today = new Date();
                    // Calcul du début de la semaine
                    let startOfWeek = new Date(today);
                    startOfWeek.setHours(0, 0, 0, 0);
                    // Ajustement pour commencer au lundi
                    const dayOfWeek = today.getDay();
                    const diff = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
                    startOfWeek.setDate(today.getDate() + diff);
                    // Définition explicite de la date de début
                    this.startDate = startOfWeek;
                    // Initialisation du calendrier
                    await this.updateDateRange(this.startDate);
                    this.initialized = true;
                } catch (error) {
                    console.error('Erreur lors de l\'initialisation:', error);
                } finally {
                    this.isLoading = false;
                }
            },
            // Le reste des méthodes reste identique à votre code original
            async fetchAvailabilities(startDate, endDate) {
                try {
                    const url = new URL(`${window.location.origin}/js/json/getStoreServiceAvailabilities.json`);
                    url.searchParams.append('date_start', this.formatDateISO(startDate));
                    url.searchParams.append('date_end', this.formatDateISO(endDate));
                    const response = await fetch(url);
                    const data = await response.json();
                    this.availabilities = data.reduce((acc, item) => {
                        acc[item.disponibilityDay] = item.disponibilityHours;
                        return acc;
                    }, {});
                } catch (error) {
                    console.error('Erreur lors de la récupération des disponibilités:', error);
                    this.availabilities = {};
                }
            },
            // Mise à jour améliorée du calendrier
            async updateDateRange(newStartDate) {
                if (!newStartDate) return;
                try {
                    this.isLoading = true;
                    // Création d'une copie de la date de début
                    const startDate = new Date(newStartDate);
                    startDate.setHours(0, 0, 0, 0);
                    // Calcul de la date de fin
                    const endDate = new Date(startDate);
                    endDate.setDate(startDate.getDate() + 13);
                    // Récupération des disponibilités
                    await this.fetchAvailabilities(startDate, endDate);
                    // Mise à jour de l'état
                    this.startDate = startDate;
                    // Mise à jour de l'affichage
                    this.updateCalendarDisplay();
                } catch (error) {
                    console.error('Erreur lors de la mise à jour:', error);
                } finally {
                    this.isLoading = false;
                }
            },
            // Navigation améliorée
            async handlePreviousClick() {
                if (this.isLoading) return;
                const newStartDate = new Date(this.startDate);
                newStartDate.setDate(newStartDate.getDate() - 14);
                await this.updateDateRange(newStartDate);
            },
            async handleNextClick() {
                if (this.isLoading) return;
                const newStartDate = new Date(this.startDate);
                newStartDate.setDate(newStartDate.getDate() + 14);
                await this.updateDateRange(newStartDate);
            },
            // Mise à jour robuste de l'affichage du calendrier
            updateCalendarDisplay() {
                if (!this.startDate) return;
                const newDays = [];
                const endDate = new Date(this.startDate);
                endDate.setDate(endDate.getDate() + 13);
                // Itération sur les dates
                for (let currentDate = new Date(this.startDate); currentDate <= endDate; currentDate.setDate(currentDate.getDate() + 1)) {
                    const dateStr = this.formatDateISO(new Date(currentDate));
                    const isAvailable = Boolean(
                        this.availabilities &&
                        this.availabilities[dateStr] &&
                        this.availabilities[dateStr].length > 0
                    );
                    newDays.push({
                        date: new Date(currentDate),
                        dayOfMonth: currentDate.getDate(),
                        isAvailable,
                        dateStr
                    });
                }
                this.days = newDays;
                this.updateDisplayRange();
            },
            // Formatage amélioré des dates
            formatDateISO(date) {
                const d = new Date(date);
                const year = d.getFullYear();
                const month = String(d.getMonth() + 1).padStart(2, '0');
                const day = String(d.getDate()).padStart(2, '0');
                return `${year}-${month}-${day}`;
            },
            updateDisplayRange() {
                if (!this.startDate) return;
                const endDate = new Date(this.startDate);
                endDate.setDate(endDate.getDate() + 13);
                const formatOptions = {
                    day: 'numeric',
                    month: 'short',
                    year: 'numeric'
                };
                this.displayRange = `du ${this.startDate.toLocaleDateString('fr-FR', formatOptions)} au ${endDate.toLocaleDateString('fr-FR', formatOptions)}`;
            },
            // Sélection d'une date
            selectDate(day) {
                if (!day.isAvailable) return;
                this.selectedDate = day.date;
                this.selectedTime = null;
                this.availableHours = this.availabilities[day.dateStr] || [];
            },
            // Formatage d'une date selon le format demandé
            formatDate(date, format = 'long') {
                const options = format === 'long' ?
                    {
                        weekday: 'long',
                        day: 'numeric',
                        month: 'long',
                        year: 'numeric'
                    } :
                    {
                        day: 'numeric',
                        month: 'short',
                        year: 'numeric'
                    };
                return date.toLocaleDateString('fr-FR', options);
            },
            // Sélection d'un créneau horaire
            selectTime(time) {
                if (!this.selectedDate) return;
                // Formatage de la date et l'heure pour l'affichage
                const dateStr = this.selectedDate.toLocaleDateString('fr-FR', {
                    weekday: 'long',
                    day: 'numeric',
                    month: 'long',
                    year: 'numeric'
                });
                // Mise à jour des données du stepper et passage à l'étape suivante
                this.$store.locator.updateStepperData(
                    'DATE ET HEURE DU RENDEZ-VOUS',
                    `${dateStr} à ${time}`,
                    this.stepperData.currentStep + 1
                );
            }
        };
    }
</script>
{# datetime-picker.html.twig #}
<div
		x-data="datePicker()"
		x-init="init().then()"
		class="w-full max-w-3xl mx-auto space-y-8"
>
    <h1 class="text-3xl font-normal mb-8">Date et heure du rendez-vous</h1>

    {# Sélection de la date #}
    <div class="bg-white rounded-3xl p-8">
        {# Navigation du mois #}
        <div class="flex justify-center items-center mb-8 relative">
            <button
					@click="handlePreviousClick().then()"
                    class="absolute left-0 w-10 h-10 flex items-center justify-center rounded-full bg-neutral-100 hover:bg-neutral-200 transition-colors"
            >
                <svg width="24" height="24" viewBox="0 0 24 24" fill="none" class="text-neutral-600">
                    <path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                          stroke-linejoin="round"/>
                </svg>
            </button>

            <span x-text="displayRange" class="text-xl font-normal"></span>

            <button
					@click="handleNextClick().then()"
                    class="absolute right-0 w-10 h-10 flex items-center justify-center rounded-full bg-neutral-100 hover:bg-neutral-200 transition-colors"
            >
                <svg width="24" height="24" viewBox="0 0 24 24" fill="none" class="text-neutral-600">
                    <path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                          stroke-linejoin="round"/>
                </svg>
            </button>
        </div>

        {# Calendrier #}
        <div class="grid grid-cols-7 gap-0">
            {# Jours de la semaine #}
            <template x-for="day in ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim']">
                <div class="text-center py-2 text-neutral-900 font-normal text-lg" x-text="day"></div>
            </template>

            {# Grille des jours #}
			<template x-for="day in days">
				<div class="relative border-[0.5px] border-neutral-200">
					<button
							@click="selectDate(day)"
							class="w-full aspect-square flex items-center justify-center relative"
							:class="{
								'bg-green-50': day.isAvailable,
								'cursor-pointer hover:bg-green-100': day.isAvailable,
								'cursor-not-allowed bg-neutral-50': !day.isAvailable
							}"
							:disabled="!day.isAvailable"
					>
						{# Jour non disponible avec motif diagonal #}
						<div x-show="!day.isAvailable"
							 class="absolute inset-0 bg-[linear-gradient(to_top_right,transparent_calc(50%_-_1px),#e5e7eb,transparent_calc(50%_+_1px))]">
						</div>

						{# Contenu du jour #}
						<div class="relative flex flex-col items-center"
							 :class="{
								 'text-green-700': day.isAvailable,
								 'text-neutral-400': !day.isAvailable
							 }">
							<span class="text-lg" x-text="day.dayOfMonth"></span>
							<div x-show="day.isAvailable"
								 class="w-1.5 h-1.5 rounded-full bg-green-700 mt-1">
							</div>
						</div>

						{# Bordure de sélection #}
						<div x-show="selectedDate && day.date.getTime() === selectedDate.getTime()"
							 class="absolute inset-0 border-2 border-green-700 pointer-events-none">
						</div>
					</button>
				</div>
			</template>
        </div>

        {# Légende #}
        <div class="mt-4 flex items-center gap-2 text-green-700">
            <div class="w-1.5 h-1.5 rounded-full bg-green-700"></div>
            <span class="text-sm">Jours avec des horaires disponibles</span>
        </div>
    </div>

    {# Sélection de l'heure #}
    <div x-show="selectedDate" class="space-y-4">
        <h2 class="text-xl">Sélectionner l'horaire</h2>
        <div class="grid grid-cols-4 gap-4">
            <template x-for="slot in availableHours" :key="slot.initialHour">
                <button
                        @click="selectTime(slot.initialHour)"
                        class="py-3 px-6 rounded-full border border-gray-200 hover:border-brand-400 transition-colors"
                        :class="{
                        'bg-brand-50 border-brand-400': selectedTime === slot.initialHour,
                        'bg-white': selectedTime !== slot.initialHour
                    }"
                >
                    <span x-text="slot.initialHour"></span>
                </button>
            </template>
        </div>
    </div>
</div>

<script>
	function datePicker() {
		return {
			// États initiaux explicitement définis
			startDate: new Date(),  // On initialise directement avec une date par défaut
			selectedDate: null,
			selectedTime: null,
			days: [],
			availabilities: {},
			availableHours: [],
			isLoading: false,
			displayRange: '',
			initialized: false,

			// Getter pour accéder aux données du stepper
			get stepperData() {
				return this.$store.locator.stepperData;
			},

			// Nouvelle méthode d'initialisation
			async init() {
				if (this.initialized) return;

				try {
					this.isLoading = true;

					// Initialisation avec la date du jour
					const today = new Date();

					// Calcul du début de la semaine
					let startOfWeek = new Date(today);
					startOfWeek.setHours(0, 0, 0, 0);

					// Ajustement pour commencer au lundi
					const dayOfWeek = today.getDay();
					const diff = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
					startOfWeek.setDate(today.getDate() + diff);

					// Définition explicite de la date de début
					this.startDate = startOfWeek;

					// Initialisation du calendrier
					await this.updateDateRange(this.startDate);

					this.initialized = true;
				} catch (error) {
					console.error('Erreur lors de l\'initialisation:', error);
				} finally {
					this.isLoading = false;
				}
			},

			// Le reste des méthodes reste identique à votre code original
			async fetchAvailabilities(startDate, endDate) {
				try {
					const url = new URL(`${window.location.origin}/js/json/getStoreServiceAvailabilities.json`);
					url.searchParams.append('date_start', this.formatDateISO(startDate));
					url.searchParams.append('date_end', this.formatDateISO(endDate));

					const response = await fetch(url);
					const data = await response.json();

					this.availabilities = data.reduce((acc, item) => {
						acc[item.disponibilityDay] = item.disponibilityHours;
						return acc;
					}, {});
				} catch (error) {
					console.error('Erreur lors de la récupération des disponibilités:', error);
					this.availabilities = {};
				}
			},

			// Mise à jour améliorée du calendrier
			async updateDateRange(newStartDate) {
				if (!newStartDate) return;

				try {
					this.isLoading = true;

					// Création d'une copie de la date de début
					const startDate = new Date(newStartDate);
					startDate.setHours(0, 0, 0, 0);

					// Calcul de la date de fin
					const endDate = new Date(startDate);
					endDate.setDate(startDate.getDate() + 13);

					// Récupération des disponibilités
					await this.fetchAvailabilities(startDate, endDate);

					// Mise à jour de l'état
					this.startDate = startDate;

					// Mise à jour de l'affichage
					this.updateCalendarDisplay();

				} catch (error) {
					console.error('Erreur lors de la mise à jour:', error);
				} finally {
					this.isLoading = false;
				}
			},

			// Navigation améliorée
			async handlePreviousClick() {
				if (this.isLoading) return;

				const newStartDate = new Date(this.startDate);
				newStartDate.setDate(newStartDate.getDate() - 14);
				await this.updateDateRange(newStartDate);
			},

			async handleNextClick() {
				if (this.isLoading) return;

				const newStartDate = new Date(this.startDate);
				newStartDate.setDate(newStartDate.getDate() + 14);
				await this.updateDateRange(newStartDate);
			},

			// Mise à jour robuste de l'affichage du calendrier
			updateCalendarDisplay() {
				if (!this.startDate) return;

				const newDays = [];
				const endDate = new Date(this.startDate);
				endDate.setDate(endDate.getDate() + 13);

				// Itération sur les dates
				for (let currentDate = new Date(this.startDate);
					 currentDate <= endDate;
					 currentDate.setDate(currentDate.getDate() + 1)) {

					const dateStr = this.formatDateISO(new Date(currentDate));
					const isAvailable = Boolean(
							this.availabilities &&
							this.availabilities[dateStr] &&
							this.availabilities[dateStr].length > 0
					);

					newDays.push({
						date: new Date(currentDate),
						dayOfMonth: currentDate.getDate(),
						isAvailable,
						dateStr
					});
				}

				this.days = newDays;
				this.updateDisplayRange();
			},

			// Formatage amélioré des dates
			formatDateISO(date) {
				const d = new Date(date);
				const year = d.getFullYear();
				const month = String(d.getMonth() + 1).padStart(2, '0');
				const day = String(d.getDate()).padStart(2, '0');
				return `${year}-${month}-${day}`;
			},

			updateDisplayRange() {
				if (!this.startDate) return;

				const endDate = new Date(this.startDate);
				endDate.setDate(endDate.getDate() + 13);

				const formatOptions = {day: 'numeric', month: 'short', year: 'numeric'};
				this.displayRange = `du ${this.startDate.toLocaleDateString('fr-FR', formatOptions)} au ${endDate.toLocaleDateString('fr-FR', formatOptions)}`;
			},

			// Sélection d'une date
			selectDate(day) {
				if (!day.isAvailable) return;
				this.selectedDate = day.date;
				this.selectedTime = null;
				this.availableHours = this.availabilities[day.dateStr] || [];
			},

			// Formatage d'une date selon le format demandé
			formatDate(date, format = 'long') {
				const options = format === 'long'
					? {weekday: 'long', day: 'numeric', month: 'long', year: 'numeric'}
					: {day: 'numeric', month: 'short', year: 'numeric'};
				return date.toLocaleDateString('fr-FR', options);
			},

			// Sélection d'un créneau horaire
			selectTime(time) {
				if (!this.selectedDate) return;

				// Formatage de la date et l'heure pour l'affichage
				const dateStr = this.selectedDate.toLocaleDateString('fr-FR', {
					weekday: 'long',
					day: 'numeric',
					month: 'long',
					year: 'numeric'
				});

				// Mise à jour des données du stepper et passage à l'étape suivante
				this.$store.locator.updateStepperData(
					'DATE ET HEURE DU RENDEZ-VOUS',
					`${dateStr} à ${time}`,
					this.stepperData.currentStep + 1
				);
			}
		};
	}
</script>
/* No context defined. */
  • Handle: @storelocator-step3
  • Preview:
  • Filesystem Path: src/components/organisms/storeLocator/components/step/storelocator-step3.twig

No notes defined.