// ULTRA-EARLY: Hide apartments immediately if URL has date params (before DOM is fully ready) // This runs as soon as the script loads to prevent ANY flash (function() { const urlParams = new URLSearchParams(window.location.search); const dateParam = urlParams.get('date'); const hasDateParams = dateParam && dateParam.includes(' to '); console.log('🔍 DEBUG: ULTRA-EARLY check - Has date params?', hasDateParams); if (hasDateParams) { console.log('🔍 DEBUG: ULTRA-EARLY - Adding style to hide apartments on load'); // Inject CSS to hide elements immediately const style = document.createElement('style'); style.id = 'instant-hide-style'; style.textContent = ` #apartments-grid, #apartments-map, #not-available { display: none !important; } #loader { display: flex !important; } `; document.head.appendChild(style); console.log('🔍 DEBUG: ULTRA-EARLY - Style injected'); } })(); // Sélectionner le formulaire et les champs const form = document.querySelector('[data-form="reservation"]'); const nameInput = document.querySelector('[data-input="name"]'); const numberInput = document.querySelector('[data-input="counter"]'); const quartierInputs = document.querySelectorAll('[data-input="quartier"]'); const dateInput = document.querySelector('[data-input="date"]'); const ctaButton = document.querySelector('#cta-form'); // Simplified URL parameter update function function updateUrlParams() { // Get current form values const name = nameInput?.value; const guest = numberInput?.value; const quartier = Array.from(quartierInputs).find(input => input.checked)?.value; // Better date handling for flatpickr let date = dateInput?.value; if (dateInput && dateInput._flatpickr) { const selectedDates = dateInput._flatpickr.selectedDates; if (selectedDates.length === 2) { const formatDate = (dateObj) => { const year = dateObj.getFullYear(); const month = String(dateObj.getMonth() + 1).padStart(2, '0'); const day = String(dateObj.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; }; date = `${formatDate(selectedDates[0])} to ${formatDate(selectedDates[1])}`; } } console.log('Form values:', { name, guest, quartier, date }); // Use centralized URL parameter manager URLParamManager.set({ name, guest, quartier, date }); } // Fonction pour remplir les inputs avec les paramètres d'URL function fillInputsFromUrlParams() { const url = new URL(window.location.href); const params = new URLSearchParams(url.searchParams); // Récupérer les valeurs des paramètres d'URL const name = params.get('name'); const guest = params.get('guest'); const quartier = params.get('quartier'); const date = params.get('date'); console.log('Paramètres URL récupérés:', { name, guest, quartier, date }); // Remplir les inputs avec les valeurs if (name && nameInput) nameInput.value = name; if (guest && numberInput) numberInput.value = guest; // Gérer la sélection du quartier et mettre à jour l'affichage if (quartier && quartierInputs.length > 0) { const checkedInput = Array.from(quartierInputs).find(input => input.value === quartier); if (checkedInput) { checkedInput.checked = true; // Mettre à jour le texte affiché du quartier const labelElement = document.querySelector(`span[for="${checkedInput.id}"]`); const placeElement = document.getElementById('place'); if (labelElement && placeElement) { placeElement.textContent = labelElement.textContent.trim(); } } } // Gérer les dates avec décodage correct des espaces if (date && dateInput) { // Décoder les + en espaces et traiter le format "date1 to date2" const decodedDate = date.replace(/\+/g, ' '); console.log('Date décodée:', decodedDate); if (decodedDate.includes(' to ')) { const [startDate, endDate] = decodedDate.split(' to '); console.log('Dates séparées:', { startDate, endDate }); // Define French date formatting function const formatDate = (dateStr) => { const date = new Date(dateStr + 'T12:00:00'); // Add noon to avoid timezone issues return date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' }); }; const formattedDisplay = `${formatDate(startDate)} - ${formatDate(endDate)}`; // Si flatpickr est disponible, utiliser son API if (dateInput._flatpickr) { const startDateObj = new Date(startDate + 'T12:00:00'); const endDateObj = new Date(endDate + 'T12:00:00'); dateInput._flatpickr.setDate([startDateObj, endDateObj]); // Force update the input display with French format after setting flatpickr dates setTimeout(() => { // Try multiple approaches to update the visible date input const possibleInputs = [ dateInput, dateInput._flatpickr.altInput, document.querySelector('.flatpickr-input[readonly]'), document.querySelector('input[data-input="date"]'), document.querySelector('#DateReservation'), document.querySelector('.input-date') ]; possibleInputs.forEach(input => { if (input) { input.value = formattedDisplay; console.log('Updated input with French format:', input.className || input.id, '→', formattedDisplay); } }); // Also try to override flatpickr's formatDate if possible if (dateInput._flatpickr.config) { const originalFormat = dateInput._flatpickr.config.altFormat; dateInput._flatpickr.config.altFormat = formattedDisplay; console.log('Updated flatpickr altFormat from', originalFormat, 'to', formattedDisplay); } }, 200); } else { dateInput.value = formattedDisplay; } // Update multiple possible date display elements const dateDisplayElements = [ document.querySelector('.input-value.dates'), document.querySelector('#date-display'), document.querySelector('.date-display') ]; dateDisplayElements.forEach(element => { if (element) { if (element.tagName === 'INPUT') { element.placeholder = formattedDisplay; element.value = formattedDisplay; } else { element.textContent = formattedDisplay; } console.log('Updated date display element:', formattedDisplay); } }); } else { dateInput.value = decodedDate; } } } // Add dropdown auto-close functionality with URL update function addDropdownAutoClose() { const quartierInputs = document.querySelectorAll('[data-input="quartier"]'); quartierInputs.forEach(input => { input.addEventListener('change', function() { if (this.checked) { // Update the display text const labelElement = document.querySelector(`span[for="${this.id}"]`); const placeElement = document.getElementById('place'); if (labelElement && placeElement) { placeElement.textContent = labelElement.textContent.trim(); } // Update URL parameters immediately updateUrlParams(); // Close the dropdown const dropdown = this.closest('.w-dropdown-list'); const dropdownToggle = this.closest('.w-dropdown').querySelector('.w-dropdown-toggle'); if (dropdown && dropdownToggle) { dropdown.classList.remove('w--open'); dropdownToggle.setAttribute('aria-expanded', 'false'); dropdownToggle.classList.remove('w--open'); } // Handle quartier change - filter based on current state setTimeout(() => { const currentData = getCurrentFormData(); const quartierName = labelElement.textContent.trim(); if (hasCompleteSearchCriteria(currentData)) { // Dates are set - filter within API results if available console.log('Quartier changed with dates set - filtering API results'); if (ApartmentDisplayManager.lastApiResults) { filterApartmentsByQuartier(quartierName); } else { // No API results yet, will be handled when search is clicked console.log('No API results to filter - awaiting search click'); } } else { // No dates - show local quartier filtering console.log('Quartier changed without dates - local filtering'); if (quartierName && !isDefaultQuartierValue(quartierName)) { filterApartmentsByQuartier(quartierName); } else { showAllApartments(); } } }, 100); } }); }); } // Simplified form data retrieval using URL parameter manager function getCurrentFormData() { const urlParams = URLParamManager.getAll(); return { name: urlParams.name || nameInput?.value || '', guest: urlParams.guest || numberInput?.value || '', quartier: urlParams.quartier || Array.from(quartierInputs).find(input => input.checked)?.value || '', date: urlParams.date || dateInput?.value || '' }; } function hasCompleteSearchCriteria(data) { const hasDates = data.date && data.date.includes(' to '); const hasGuests = data.guest && data.guest !== ''; // Accept any guest count including 1 console.log('Checking search criteria:', { hasDates, hasGuests, guestValue: data.guest, dateValue: data.date, quartierValue: data.quartier }); // For API call: need dates. Quartier is optional, guests default to 1 return hasDates; } function isDefaultQuartierValue(quartierName) { const defaultValues = ['', 'District', 'Quartier', 'Tous les quartiers']; return !quartierName || defaultValues.includes(quartierName); } // Enhanced apartment display manager with state management const ApartmentDisplayManager = { // Store the last API results for quartier filtering lastApiResults: null, showAll: function() { console.log('Showing all apartments'); this.lastApiResults = null; // Clear API results when showing all this.setApartmentVisibility(true); this.setContainerVisibility(true, false); }, hideAll: function() { console.log('Hiding all apartments'); this.setApartmentVisibility(false); }, showNoResults: function() { console.log('Displaying no results message'); this.hideAll(); this.setContainerVisibility(false, true); }, // Show apartments filtered by quartier only (no API results) showByQuartier: function(quartierName) { console.log('Showing apartments by quartier only:', quartierName); const mapItems = document.querySelectorAll('[data-item="apartment-map"]'); const gridItems = document.querySelectorAll('[data-item="apartment-grid"]'); let foundAny = false; [...mapItems, ...gridItems].forEach(item => { const itemQuartierName = item.getAttribute('data-quartier'); const shouldShow = itemQuartierName && itemQuartierName.toLowerCase() === quartierName.toLowerCase(); item.style.display = shouldShow ? '' : 'none'; if (shouldShow) foundAny = true; }); this.setContainerVisibility(foundAny, !foundAny); return foundAny; }, // Show apartments from API results, optionally filtered by quartier showFromApiResults: function(availableIds, selectedQuartier = null) { console.log('🏢 Showing apartments from API results:', { availableIds: availableIds.length + ' apartments', availableIdsArray: availableIds, selectedQuartier }); const mapItems = document.querySelectorAll('[data-item="apartment-map"]'); const gridItems = document.querySelectorAll('[data-item="apartment-grid"]'); let foundAny = false; let foundMapItems = 0; let foundGridItems = 0; console.log(`🔍 Processing ${mapItems.length} map items and ${gridItems.length} grid items`); // Process map items mapItems.forEach(item => { const bookingId = item.getAttribute('data-bookingsync'); const itemQuartierName = item.getAttribute('data-quartier'); // Must be in available IDs from API let shouldShow = bookingId && availableIds.includes(bookingId); let reason = ''; if (!bookingId) { reason = 'no booking ID'; } else if (!availableIds.includes(bookingId)) { reason = `not in API results (ID: ${bookingId})`; } else { // If quartier is selected, check if item has quartier data // Map items often don't have data-quartier attribute in Webflow CMS // So we skip quartier filtering for map items with null/missing quartier if (selectedQuartier && itemQuartierName && itemQuartierName !== 'null') { const quartierMatch = itemQuartierName.toLowerCase() === selectedQuartier.toLowerCase(); if (!quartierMatch) { shouldShow = false; reason = `quartier mismatch (item: "${itemQuartierName}" vs selected: "${selectedQuartier}")`; } else { reason = 'matches quartier and API results'; } } else if (selectedQuartier && (!itemQuartierName || itemQuartierName === 'null')) { // Map item has no quartier data - show it if it's in API results reason = 'in API results (map item has no quartier data - skipping quartier filter)'; } else { reason = 'in API results (no quartier filter)'; } } item.style.display = shouldShow ? '' : 'none'; if (shouldShow) { foundAny = true; foundMapItems++; } console.log(`🗺️ MAP Apartment ${bookingId} (quartier: "${itemQuartierName}"): ${shouldShow ? '✅ SHOWN' : '❌ HIDDEN'} - ${reason}`); }); // Process grid items gridItems.forEach(item => { const bookingId = item.getAttribute('data-bookingsync'); const itemQuartierName = item.getAttribute('data-quartier'); // Must be in available IDs from API let shouldShow = bookingId && availableIds.includes(bookingId); let reason = ''; if (!bookingId) { reason = 'no booking ID'; } else if (!availableIds.includes(bookingId)) { reason = `not in API results (ID: ${bookingId})`; } else { // If quartier is selected, also must match quartier if (selectedQuartier) { const quartierMatch = itemQuartierName && itemQuartierName.toLowerCase() === selectedQuartier.toLowerCase(); if (!quartierMatch) { shouldShow = false; reason = `quartier mismatch (item: "${itemQuartierName}" vs selected: "${selectedQuartier}")`; } else { reason = 'matches quartier and API results'; } } else { reason = 'in API results (no quartier filter)'; } } item.style.display = shouldShow ? '' : 'none'; if (shouldShow) { foundAny = true; foundGridItems++; } console.log(`📋 GRID Apartment ${bookingId} (quartier: "${itemQuartierName}"): ${shouldShow ? '✅ SHOWN' : '❌ HIDDEN'} - ${reason}`); }); this.setContainerVisibility(foundAny, !foundAny); console.log(`📊 Result: ${foundMapItems} map items shown, ${foundGridItems} grid items shown. Total found: ${foundAny}`); return foundAny; }, // Filter existing API results by quartier (when quartier changes after API call) filterApiResultsByQuartier: function(quartierName) { if (!this.lastApiResults) { console.log('No API results to filter, showing by quartier only'); return this.showByQuartier(quartierName); } console.log('Filtering existing API results by quartier:', quartierName); const availableIds = this.lastApiResults.filter(apt => apt.is_available === true) .map(apt => apt.bookingsync_apt_id.toString()); return this.showFromApiResults(availableIds, quartierName); }, setApartmentVisibility: function(visible) { const mapItems = document.querySelectorAll('[data-item="apartment-map"]'); const gridItems = document.querySelectorAll('[data-item="apartment-grid"]'); const displayValue = visible ? '' : 'none'; [...mapItems, ...gridItems].forEach(item => { item.style.display = displayValue; }); }, setContainerVisibility: function(showGrid, showNoResults) { const apartmentsGrid = document.getElementById('apartments-grid'); const notAvailableDiv = document.getElementById('not-available'); if (apartmentsGrid) { apartmentsGrid.style.display = showGrid ? 'block' : 'none'; } if (notAvailableDiv) { notAvailableDiv.style.display = showNoResults ? 'flex' : 'none'; } }, // Store API results for future quartier filtering storeApiResults: function(apiData) { this.lastApiResults = apiData && apiData.rentals ? apiData.rentals : null; console.log('Stored API results:', this.lastApiResults ? this.lastApiResults.length : 0, 'apartments'); } }; // Legacy function for backward compatibility function showAllApartments() { ApartmentDisplayManager.showAll(); } // Enhanced quartier filtering that considers API state function filterApartmentsByQuartier(selectedQuartier) { console.log('Filtering apartments by quartier:', selectedQuartier); // If we have stored API results, filter within those results if (ApartmentDisplayManager.lastApiResults) { console.log('Using stored API results for quartier filtering'); const found = ApartmentDisplayManager.filterApiResultsByQuartier(selectedQuartier); console.log(`Filter result within API results: ${found ? 'Found apartments' : 'No apartments found'} for quartier: ${selectedQuartier}`); } else { // No API results, show all apartments in that quartier console.log('No API results stored, showing all apartments in quartier'); const found = ApartmentDisplayManager.showByQuartier(selectedQuartier); console.log(`Filter result: ${found ? 'Found apartments' : 'No apartments found'} for quartier: ${selectedQuartier}`); } } // Enhanced approach: Hook into flatpickr and force French formatting function initializeFlatpickrWithFrenchFormat() { let attempts = 0; const maxAttempts = 200; // 20 seconds with 100ms intervals const enhanceExistingFlatpickr = setInterval(() => { attempts++; const dateInput = document.querySelector('[data-input="date"]'); // Check if we have flatpickr library and an initialized instance if (typeof flatpickr !== 'undefined' && dateInput && dateInput._flatpickr) { clearInterval(enhanceExistingFlatpickr); console.log('Found existing flatpickr, adding French formatting...'); try { const fp = dateInput._flatpickr; // Store original onChange const originalOnChange = fp.config.onChange; // Create enhanced onChange function fp.config.onChange = function(selectedDates, dateStr, instance) { console.log('Flatpickr onChange triggered:', selectedDates.length, 'dates selected'); // Call original onChange if it exists if (typeof originalOnChange === 'function') { originalOnChange(selectedDates, dateStr, instance); } else if (Array.isArray(originalOnChange)) { originalOnChange.forEach(fn => fn(selectedDates, dateStr, instance)); } // Force French formatting immediately after any change setTimeout(() => { forceFrenchDateFormat(); }, 10); // Update URL parameters if (selectedDates.length === 2) { updateUrlParams(); } }; // Also add an onClose handler to ensure French format is applied const originalOnClose = fp.config.onClose; fp.config.onClose = function(selectedDates, dateStr, instance) { console.log('Flatpickr onClose triggered'); // Call original onClose if it exists if (typeof originalOnClose === 'function') { originalOnClose(selectedDates, dateStr, instance); } else if (Array.isArray(originalOnClose)) { originalOnClose.forEach(fn => fn(selectedDates, dateStr, instance)); } // Force French formatting when calendar closes setTimeout(() => { forceFrenchDateFormat(); }, 10); }; // Try to apply French formatting to any existing selected dates if (fp.selectedDates.length === 2) { setTimeout(() => { forceFrenchDateFormat(); }, 100); } console.log('Successfully enhanced existing flatpickr with French formatting'); } catch (error) { console.error('Error enhancing flatpickr:', error); ErrorHandler.handleFlatpickrError(error); } } else if (attempts >= maxAttempts) { clearInterval(enhanceExistingFlatpickr); console.log('Timeout: Could not find flatpickr instance to enhance'); } }, 100); // Also set up a recurring check to ensure French format is maintained setInterval(() => { const dateInput = document.querySelector('[data-input="date"]'); if (dateInput && dateInput._flatpickr && dateInput._flatpickr.selectedDates.length === 2) { forceFrenchDateFormat(); } }, 2000); // Check every 2 seconds } // Deprecated - we now enhance existing flatpickr instead of recreating // This prevents breaking the existing functionality function setupFlatpickrFrenchFormat() { console.log('setupFlatpickrFrenchFormat is deprecated - using enhancement approach instead'); return null; } // Enhanced French date format function with multiple approaches function forceFrenchDateFormat() { const dateInput = document.querySelector('[data-input="date"]'); if (!dateInput) return; console.log('Attempting to force French date format...'); // Approach 1: Update flatpickr alt input if (dateInput._flatpickr && dateInput._flatpickr.altInput) { const altInput = dateInput._flatpickr.altInput; const currentValue = altInput.value; console.log('Current alt input value:', currentValue); // Check if it's in technical format const technicalDatePattern = /(\d{4}-\d{2}-\d{2}) to (\d{4}-\d{2}-\d{2})/; const match = currentValue && currentValue.match(technicalDatePattern); if (match) { const [, startDate, endDate] = match; const formatDate = (dateStr) => { const date = new Date(dateStr + 'T12:00:00'); return date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' }); }; const frenchFormat = `${formatDate(startDate)} - ${formatDate(endDate)}`; // Force update the alt input altInput.value = frenchFormat; altInput.placeholder = frenchFormat; // Also dispatch an input event to trigger any listeners altInput.dispatchEvent(new Event('input', { bubbles: true })); console.log('✅ Updated alt input to French format:', frenchFormat); } } // Approach 2: Update all possible date display inputs const allDateInputs = document.querySelectorAll('input[data-input="date"], .flatpickr-input, input[readonly]'); allDateInputs.forEach((input, index) => { const value = input.value; if (value && value.includes('2025-') && value.includes(' to ')) { const technicalDatePattern = /(\d{4}-\d{2}-\d{2}) to (\d{4}-\d{2}-\d{2})/; const match = value.match(technicalDatePattern); if (match) { const [, startDate, endDate] = match; const formatDate = (dateStr) => { const date = new Date(dateStr + 'T12:00:00'); return date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' }); }; const frenchFormat = `${formatDate(startDate)} - ${formatDate(endDate)}`; input.value = frenchFormat; console.log(`✅ Updated input ${index} to French format:`, frenchFormat); } } }); // Approach 3: Use MutationObserver to continuously monitor and fix if (!window.frenchDateObserver) { window.frenchDateObserver = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'attributes' && mutation.attributeName === 'value') { const target = mutation.target; if (target.value && target.value.includes('2025-') && target.value.includes(' to ')) { setTimeout(() => forceFrenchDateFormat(), 50); } } }); }); // Observe all date-related inputs allDateInputs.forEach(input => { window.frenchDateObserver.observe(input, { attributes: true, attributeFilter: ['value'] }); }); console.log('Started MutationObserver for French date formatting'); } } // Main initialization - safer approach that preserves existing functionality document.addEventListener('DOMContentLoaded', function() { // IMMEDIATE: Check if we need to hide apartments before API search (prevent flash) console.log('🔍 DEBUG: DOMContentLoaded - immediate check for URL params'); const immediateUrlParams = URLParamManager.getAll(); const immediateDate = immediateUrlParams.date; const hasDateParams = immediateDate && immediateDate.includes(' to '); console.log('🔍 DEBUG: Has date params on immediate load?', hasDateParams); if (hasDateParams) { console.log('🔍 DEBUG: IMMEDIATE hiding of apartments to prevent flash'); // Hide everything immediately - don't wait for setTimeout const apartmentsGridContainer = document.getElementById('apartments-grid'); const apartmentsMapContainer = document.getElementById('apartments-map'); const notAvailableDiv = document.getElementById('not-available'); if (apartmentsGridContainer) { apartmentsGridContainer.style.display = 'none'; console.log('🔍 DEBUG: IMMEDIATE - Grid container hidden'); } if (apartmentsMapContainer) { apartmentsMapContainer.style.display = 'none'; console.log('🔍 DEBUG: IMMEDIATE - Map container hidden'); } if (notAvailableDiv) { notAvailableDiv.style.display = 'none'; } // Show loader immediately showLoadingIndicator(); console.log('🔍 DEBUG: IMMEDIATE - Loader shown'); } // Wait for any existing Webflow scripts to initialize first setTimeout(() => { console.log('Starting enhanced initialization...'); // Try to enhance existing flatpickr (non-destructive) initializeFlatpickrWithFrenchFormat(); // Initialize other components addDropdownAutoClose(); initializeViewAndMap(); setupLinkInterception(); // Fill inputs from URL parameters after a delay to ensure flatpickr is ready setTimeout(() => { fillInputsFromUrlParams(); // Force French formatting after filling inputs setTimeout(() => { console.log('Forcing French format after URL parameter fill...'); forceFrenchDateFormat(); // Keep trying for a few more seconds in case it takes time const formatInterval = setInterval(() => { forceFrenchDateFormat(); }, 1000); setTimeout(() => { clearInterval(formatInterval); }, 5000); }, 500); }, 2000); }, 1000); // Wait 1 second for other initializations // Monitor for flatpickr calendar interactions and maintain French format document.addEventListener('click', function(e) { if (e.target.closest('.flatpickr-calendar') || e.target.closest('.flatpickr-input')) { // Give flatpickr time to update, then ensure French format setTimeout(() => { console.log('Click detected on flatpickr, forcing French format...'); forceFrenchDateFormat(); }, 50); // Also check again after a longer delay setTimeout(() => { forceFrenchDateFormat(); }, 500); } }); // Additional monitoring for any input changes document.addEventListener('input', function(e) { if (e.target.matches('[data-input="date"], .flatpickr-input')) { setTimeout(() => { console.log('Input detected on date field, forcing French format...'); forceFrenchDateFormat(); }, 50); } }); // Monitor for any changes to date inputs document.addEventListener('change', function(e) { if (e.target.matches('[data-input="date"], .flatpickr-input')) { setTimeout(() => { console.log('Change detected on date field, forcing French format...'); forceFrenchDateFormat(); }, 50); } }); // Form submission handling - improved UX without page reload const ctaButton = document.querySelector('#cta-form'); if (ctaButton) { ctaButton.addEventListener('click', async function(e) { e.preventDefault(); console.log('Form submission triggered'); // Update URL parameters first updateUrlParams(); // Get current form data const currentData = getCurrentFormData(); console.log('Search triggered with data:', currentData); // Show loader immediately showLoadingIndicator(); // Add small delay to ensure loader is visible await new Promise(resolve => setTimeout(resolve, 100)); // Decide whether to call API or filter locally if (hasCompleteSearchCriteria(currentData)) { console.log('Complete search criteria - calling API'); await performApiSearch(); } else { console.log('Incomplete search criteria - filtering locally'); hideLoadingIndicator(); // Handle incomplete search criteria (no dates) const quartierName = document.getElementById('place')?.textContent?.trim(); if (quartierName && !isDefaultQuartierValue(quartierName)) { // Quartier selected but no dates - filter by quartier console.log('No dates but quartier selected:', quartierName); filterApartmentsByQuartier(quartierName); } else { // No dates and no quartier - show all apartments console.log('No dates and no quartier - showing all apartments'); showAllApartments(); initMapWithAllApartments(); } } }); } }); // Perform API search without page reload async function performApiSearch() { console.log('🔍 DEBUG: performApiSearch called'); try { const url = new URL(window.location.href); const params = new URLSearchParams(url.search); // Get search parameters const dateParam = params.get('date'); let checkinDate = ""; let checkoutDate = ""; if (dateParam && dateParam.includes(' to ')) { const dateParts = dateParam.split(' to '); if (dateParts.length === 2) { checkinDate = dateParts[0].replace(/\+/g, ' ').trim(); checkoutDate = dateParts[1].replace(/\+/g, ' ').trim(); } } console.log('🔍 DEBUG: Search parameters parsed:', { checkinDate, checkoutDate }); const guestParam = params.get('guest'); const numberOfGuests = guestParam ? parseInt(guestParam) : 1; // Default to 1 guest const quartierParam = params.get('quartier'); const quartierId = quartierParam ? parseInt(quartierParam) : null; // Prepare API request const requestData = { checkin_date: checkinDate, checkout_date: checkoutDate, nb_adults: numberOfGuests }; // Always include guest count in API request requestData.number_of_guests = numberOfGuests; if (quartierParam) { requestData.quartier_id = quartierId; } console.log('API Request:', requestData); // Hide apartments grid during search, hide not-available div const apartmentsGrid = document.getElementById('apartments-grid'); const notAvailableDiv = document.getElementById('not-available'); if (apartmentsGrid) { apartmentsGrid.style.display = 'none'; } if (notAvailableDiv) { notAvailableDiv.style.display = 'none'; } // Make API call const workerUrl = 'https://oniri.beveillard.workers.dev/availabilities'; const response = await fetch(workerUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData), mode: 'cors', credentials: 'omit', cache: 'no-cache', }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log('API Response:', data); // Process results and update display updateApartmentDisplay(data); } catch (error) { console.error('🔍 DEBUG: API Error occurred:', error); handleApiError(error); } finally { console.log('🔍 DEBUG: Finally block - hiding loader'); hideLoadingIndicator(); console.log('🔍 DEBUG: Loader hidden, display value:', document.getElementById('loader')?.style.display); } } // Enhanced apartment display update that respects quartier selection function updateApartmentDisplay(data) { console.log('🔍 DEBUG: updateApartmentDisplay called'); // Store API results for future quartier filtering ApartmentDisplayManager.storeApiResults(data); if (!data || !data.rentals || data.rentals.length === 0) { console.log('No available apartments returned from API'); ApartmentDisplayManager.showNoResults(); return; } // Filter only available apartments and get their IDs const availableApartments = data.rentals.filter(apt => apt.is_available === true); const availableIds = availableApartments.map(apt => apt.bookingsync_apt_id.toString()); console.log('Available apartment IDs:', availableIds); // Check if a specific quartier is selected const urlParams = URLParamManager.getAll(); const selectedQuartier = urlParams.quartier; const quartierName = selectedQuartier ? getQuartierNameFromValue(selectedQuartier) : null; console.log('Selected quartier for API results:', { selectedQuartier, quartierName }); // Show apartments from API results, filtered by quartier if selected const found = ApartmentDisplayManager.showFromApiResults(availableIds, quartierName); if (found) { const filteredText = quartierName ? ` in ${quartierName}` : ''; console.log(`Showing available apartments${filteredText}`); console.log('🔍 DEBUG: About to call initMap()'); // Remove the ultra-early injected style so elements can be shown properly const instantHideStyle = document.getElementById('instant-hide-style'); if (instantHideStyle) { instantHideStyle.remove(); console.log('🔍 DEBUG: Removed instant-hide-style'); } // Ensure grid is visible and map is hidden by default after API results const apartmentsMap = document.getElementById("apartments-map"); const apartmentsGrid = document.getElementById("apartments-grid"); if (apartmentsGrid) { console.log('🔍 DEBUG: Making grid visible'); apartmentsGrid.style.display = 'block'; // Override the injected style apartmentsGrid.classList.remove("visibility-hidden"); apartmentsGrid.classList.add("visibility-visible"); } if (apartmentsMap) { console.log('🔍 DEBUG: Making map visible after API results'); // CRITICAL: Remove any inline display:none and make map visible apartmentsMap.style.removeProperty('display'); // Remove any hiding classes apartmentsMap.classList.remove("visibility-hidden"); apartmentsMap.classList.add("visibility-visible"); console.log('🔍 DEBUG: Map display value after reset:', apartmentsMap.style.display); console.log('🔍 DEBUG: Map classes:', apartmentsMap.className); } console.log('🔍 DEBUG: Calling initMap() to prepare map markers'); initMap(); // Update map with filtered results (ready when user toggles) // Populate booking data for available apartments console.log('🔍 DEBUG: Populating booking data for apartments'); populateBookingData(data.rentals); } else { const noResultsText = quartierName ? `No available apartments in ${quartierName}` : 'No available apartments found'; console.log(noResultsText); // Remove the ultra-early injected style const instantHideStyle = document.getElementById('instant-hide-style'); if (instantHideStyle) { instantHideStyle.remove(); console.log('🔍 DEBUG: Removed instant-hide-style (no results)'); } ApartmentDisplayManager.showNoResults(); } } // Helper function to get quartier display name from value function getQuartierNameFromValue(quartierValue) { console.log('🔍 Looking for quartier name for value:', quartierValue); const quartierInputs = document.querySelectorAll('[data-input="quartier"]'); for (const input of quartierInputs) { if (input.value === quartierValue) { const labelElement = document.querySelector(`span[for="${input.id}"]`); if (labelElement) { const quartierName = labelElement.textContent.trim(); console.log('📍 Found quartier name:', quartierName); return quartierName; } } } console.log('⚠️ No quartier name found, using value as fallback:', quartierValue); return quartierValue; // Fallback to the value itself } // Populate booking data for apartments from API results function populateBookingData(rentals) { console.log('📊 Populating booking data for', rentals.length, 'rentals'); if (!rentals || rentals.length === 0) { console.log('❌ No rentals to populate'); return; } // Get URL parameters for dates const urlParams = new URLSearchParams(window.location.search); const dateParam = urlParams.get('date'); const guestParam = urlParams.get('guest') || '2'; let checkinDate = ''; let checkoutDate = ''; if (dateParam && dateParam.includes(' to ')) { const dateParts = dateParam.split(' to '); if (dateParts.length === 2) { checkinDate = dateParts[0].replace(/\+/g, ' ').trim(); checkoutDate = dateParts[1].replace(/\+/g, ' ').trim(); } } console.log('📅 Booking dates:', { checkinDate, checkoutDate, guests: guestParam }); // Process each rental and find matching apartment items rentals.forEach(rental => { const bookingsyncId = rental.bookingsync_apt_id.toString(); console.log('🏠 Processing rental:', { id: bookingsyncId, name: rental.apartment_name, available: rental.is_available, pricingOptions: rental.pricing ? rental.pricing.length : 0 }); // Find all apartment items (both grid and map) with this bookingsync ID const apartmentItems = document.querySelectorAll(`[data-bookingsync="${bookingsyncId}"]`); if (apartmentItems.length === 0) { console.log(`⚠️ No apartment items found for ID ${bookingsyncId}`); return; } console.log(`✅ Found ${apartmentItems.length} apartment item(s) for ID ${bookingsyncId}`); apartmentItems.forEach(item => { // Find the booking component within this item const bookingComponent = item.querySelector('.item_apartment-book-component'); if (!bookingComponent) { console.log(`⚠️ No booking component found in item for ID ${bookingsyncId}`); return; } console.log(`📦 Found booking component for ID ${bookingsyncId}`); // Check if rental has pricing data if (!rental.pricing || rental.pricing.length === 0) { console.log(`❌ No pricing data for ID ${bookingsyncId}`); bookingComponent.style.display = 'none'; return; } // Log all available pricing options for debugging console.log('💰 All pricing options for apartment:', rental.pricing.map(p => ({ short_name: p.short_name, display_name: p.display_name, total: p.total_amount }))); // Find flexible and non-flexible rates const flexibleRate = rental.pricing.find(p => p.short_name === 'tarif-flexible' || p.display_name?.toLowerCase().includes('flexible') ); const nonFlexibleRate = rental.pricing.find(p => p.short_name === 'tarif-non-remboursable' || (p.display_name?.toLowerCase().includes('non') && p.display_name?.toLowerCase().includes('remboursable')) ); console.log('💰 Pricing options found:', { flexible: flexibleRate ? { name: flexibleRate.display_name, short_name: flexibleRate.short_name, total: flexibleRate.total_amount } : null, nonFlexible: nonFlexibleRate ? { name: nonFlexibleRate.display_name, short_name: nonFlexibleRate.short_name, total: nonFlexibleRate.total_amount } : null }); const nights = rental.nb_nights || 0; // Populate flexible rate - find by data attribute if (flexibleRate) { const nightsSpan = bookingComponent.querySelector('[data-book-flexible="nights-number"]'); const priceSpan = bookingComponent.querySelector('[data-book-flexible="total-price"]'); const bookBtn = bookingComponent.querySelector('[data-book-btn="flexible"]'); if (nightsSpan) { nightsSpan.textContent = nights; console.log(`✅ Set flexible nights to ${nights} for ID ${bookingsyncId}`); } if (priceSpan) { priceSpan.textContent = `${flexibleRate.total_amount}€`; console.log(`✅ Set flexible price to ${flexibleRate.total_amount}€ for ID ${bookingsyncId}`); } if (bookBtn) { // Store booking data on button bookBtn.setAttribute('data-apartment-id', bookingsyncId); bookBtn.setAttribute('data-rate-name', 'tarif-flexible'); bookBtn.setAttribute('data-total-amount', flexibleRate.total_amount); bookBtn.setAttribute('data-checkin', checkinDate); bookBtn.setAttribute('data-checkout', checkoutDate); bookBtn.setAttribute('data-guests', guestParam); console.log('✅ Flexible rate fully populated for ID', bookingsyncId); } else { console.warn('⚠️ Flexible book button not found for ID', bookingsyncId); } } else { console.warn('⚠️ No flexible rate found in API data for ID', bookingsyncId); } // Populate non-flexible rate - find by data attribute if (nonFlexibleRate) { const nightsSpan = bookingComponent.querySelector('[data-book-no-flexible="nights-number"]'); const priceSpan = bookingComponent.querySelector('[data-book-no-flexible="total-price"]'); const bookBtn = bookingComponent.querySelector('[data-book-btn="no-flexible"]'); console.log(`🔍 Non-flexible elements found for ID ${bookingsyncId}:`, { nightsSpan: !!nightsSpan, priceSpan: !!priceSpan, bookBtn: !!bookBtn }); if (nightsSpan) { nightsSpan.textContent = nights; console.log(`✅ Set non-flexible nights to ${nights} for ID ${bookingsyncId}`); } if (priceSpan) { priceSpan.textContent = `${nonFlexibleRate.total_amount}€`; console.log(`✅ Set non-flexible price to ${nonFlexibleRate.total_amount}€ for ID ${bookingsyncId}`); } if (bookBtn) { // Store booking data on button bookBtn.setAttribute('data-apartment-id', bookingsyncId); bookBtn.setAttribute('data-rate-name', 'tarif-non-remboursable'); bookBtn.setAttribute('data-total-amount', nonFlexibleRate.total_amount); bookBtn.setAttribute('data-checkin', checkinDate); bookBtn.setAttribute('data-checkout', checkoutDate); bookBtn.setAttribute('data-guests', guestParam); console.log('✅ Non-flexible rate fully populated for ID', bookingsyncId); } else { console.warn('⚠️ Non-flexible book button not found for ID', bookingsyncId); } } else { console.warn('⚠️ No non-flexible rate found in API data for ID', bookingsyncId); } // Show the booking component bookingComponent.style.display = ''; }); }); // Set up click handlers for all booking buttons (only once) setupBookingButtonHandlers(); } // Set up click handlers for booking buttons function setupBookingButtonHandlers() { // Remove any existing handlers to avoid duplicates const allBookingButtons = document.querySelectorAll('[data-book-btn]'); allBookingButtons.forEach(btn => { // Clone and replace to remove old event listeners const newBtn = btn.cloneNode(true); btn.parentNode.replaceChild(newBtn, btn); // Set href to prevent default navigation newBtn.setAttribute('href', 'javascript:void(0)'); // Add new click handler with capture phase newBtn.addEventListener('click', handleBookingButtonClick, true); }); console.log(`🔘 Set up ${allBookingButtons.length} booking button handlers`); } // Handle booking button click function handleBookingButtonClick(event) { event.preventDefault(); event.stopPropagation(); const btn = event.currentTarget; const apartmentId = btn.getAttribute('data-apartment-id'); const rateName = btn.getAttribute('data-rate-name'); const totalAmount = btn.getAttribute('data-total-amount'); const checkinDate = btn.getAttribute('data-checkin'); const checkoutDate = btn.getAttribute('data-checkout'); const guests = btn.getAttribute('data-guests'); console.log('🎯 Booking button clicked:', { apartmentId, rateName, totalAmount, checkinDate, checkoutDate, guests }); if (!apartmentId || !rateName || !totalAmount || !checkinDate || !checkoutDate) { console.error('❌ Missing booking data on button'); alert('Missing booking information. Please try again.'); return; } // Find the apartment item that contains this button const apartmentItem = btn.closest('[data-bookingsync]'); let itemLoader = null; console.log('🔍 Looking for loader in apartment item:', apartmentId); if (apartmentItem) { console.log('✅ Found apartment item'); // Try multiple selectors to find the loader itemLoader = apartmentItem.querySelector('[data-loader="item"]'); if (!itemLoader) { itemLoader = apartmentItem.querySelector('.loader-wrapper-item'); } if (itemLoader) { itemLoader.style.display = 'flex'; console.log('✅ Showing item loader for apartment', apartmentId); } else { console.warn('⚠️ No loader found in apartment item - tried both selectors'); console.log('Apartment item HTML:', apartmentItem.outerHTML.substring(0, 500)); } } else { console.warn('⚠️ Could not find parent apartment item with [data-bookingsync]'); } // Get main page loader reference const mainLoader = document.getElementById('loader'); // Only show main loader if we couldn't find the item loader if (!itemLoader) { console.log('⚠️ Falling back to main page loader'); if (mainLoader) { mainLoader.style.display = 'flex'; } } else { console.log('✅ Using item loader - NOT showing main page loader'); } // Prepare booking data similar to apart-template-new.js const bookingData = { checkin_date: checkinDate, checkout_date: checkoutDate, nb_adults: parseInt(guests) || 2, nb_children: 0, nb_babies: 0, rate_name: rateName, price: parseFloat(totalAmount), customer: { email_address: "client@example.com", first_name: "Invité", last_name: "Anonyme", phone_number: "+33 0 00 00 00 00" } }; console.log('📤 Sending booking request:', bookingData); // Make booking API call const CLOUDFLARE_WORKER_URL = 'https://oniri.beveillard.workers.dev'; const bookingUrl = `${CLOUDFLARE_WORKER_URL}/booking/${apartmentId}`; fetch(bookingUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(bookingData) }) .then(response => { if (response.ok) { return response.json(); } else { return response.json().then(errorData => { throw new Error(errorData.message || 'Booking failed'); }); } }) .then(data => { console.log('✅ Booking successful:', data); // Hide loaders if (itemLoader) { itemLoader.style.display = 'none'; } if (mainLoader) { mainLoader.style.display = 'none'; } // Redirect to payment URL if (data.payment_url) { const paymentUrlObj = new URL(data.payment_url); // Add customer data to URL if (data.customer) { paymentUrlObj.searchParams.set('email', data.customer.email_address); paymentUrlObj.searchParams.set('first_name', data.customer.first_name); paymentUrlObj.searchParams.set('last_name', data.customer.last_name); } // Add price paymentUrlObj.searchParams.set('price-booking', totalAmount); // Build final URL let finalUrl = paymentUrlObj.origin + paymentUrlObj.pathname; let params = []; for (const [key, value] of paymentUrlObj.searchParams.entries()) { params.push(`${key}=${value}`); } if (params.length > 0) { finalUrl += '?' + params.join('&'); } console.log('🔗 Redirecting to:', finalUrl); // Store booking info in sessionStorage sessionStorage.setItem('lastBooking', JSON.stringify(data)); // Redirect to payment setTimeout(() => { window.location.href = finalUrl; }, 200); } else { console.warn('⚠️ No payment URL in response'); alert('Booking created but no payment URL found. Please contact support.'); } }) .catch(error => { console.error('❌ Booking error:', error); // Hide loaders if (itemLoader) { itemLoader.style.display = 'none'; } if (mainLoader) { mainLoader.style.display = 'none'; } alert(`Booking error: ${error.message}. Please try again.`); }); } // Legacy function for backward compatibility function showNoResultsMessage() { ApartmentDisplayManager.showNoResults(); } // Enhanced error handling and cleanup utilities const ErrorHandler = { handleApiError: function(error) { console.error('API call failed:', error); this.showUserFriendlyError('Erreur lors de la recherche. Veuillez réessayer.'); ApartmentDisplayManager.showNoResults(); }, handleFlatpickrError: function(error) { console.error('Flatpickr initialization failed:', error); this.showUserFriendlyError('Erreur lors de l\'initialisation du calendrier.'); }, handleUrlParamError: function(error) { console.error('URL parameter error:', error); // Don't show user error for URL params, just log }, showUserFriendlyError: function(message) { // Remove any existing error messages const existingError = document.querySelector('.user-error-message'); if (existingError) { existingError.remove(); } const errorDiv = document.createElement('div'); errorDiv.className = 'user-error-message'; errorDiv.innerHTML = `
${message}
`; errorDiv.style.cssText = ` background-color: #ffeeee; color: #cc0000; text-align: center; padding: 15px; margin-bottom: 15px; border-radius: 4px; border-left: 4px solid #cc0000; position: relative; z-index: 1000; `; // Insert at top of main content area const mainContent = document.querySelector('main, .main-content, body'); if (mainContent && mainContent.firstChild) { mainContent.insertBefore(errorDiv, mainContent.firstChild); } else { document.body.prepend(errorDiv); } // Auto-remove after 8 seconds setTimeout(() => { if (errorDiv.parentNode) { errorDiv.remove(); } }, 8000); }, clearAllErrors: function() { const errorMessages = document.querySelectorAll('.user-error-message, .error-message'); errorMessages.forEach(msg => msg.remove()); } }; // Legacy function for backward compatibility function handleApiError(error) { ErrorHandler.handleApiError(error); } // Consolidated view and map initialization function initializeViewAndMap() { const apartmentsMap = document.getElementById("apartments-map"); const apartmentsGrid = document.getElementById("apartments-grid"); console.log('🔍 DEBUG: initializeViewAndMap - apartmentsMap exists?', apartmentsMap !== null); console.log('🔍 DEBUG: initializeViewAndMap - apartmentsGrid exists?', apartmentsGrid !== null); if (apartmentsMap) apartmentsMap.classList.add("visibility-visible"); if (apartmentsGrid) apartmentsGrid.classList.add("visibility-visible"); // Show map button const showMapButton = document.getElementById("show-map"); if (showMapButton) { showMapButton.addEventListener("click", function() { console.log('🔍 DEBUG: Show map button clicked'); if (apartmentsMap) { console.log('🔍 DEBUG: Before showing map - display:', apartmentsMap.style.display, 'classes:', apartmentsMap.className); // Make sure display is not 'none' if (apartmentsMap.style.display === 'none') { apartmentsMap.style.display = ''; console.log('🔍 DEBUG: Reset display from none to empty'); } apartmentsMap.classList.remove("visibility-hidden"); apartmentsMap.classList.add("visibility-visible"); console.log('🔍 DEBUG: After showing map - display:', apartmentsMap.style.display, 'classes:', apartmentsMap.className); } if (apartmentsGrid) { apartmentsGrid.classList.remove("visibility-visible"); apartmentsGrid.classList.add("visibility-hidden"); } window.location.href = "#filters"; }); } // Show grid button const showGridButton = document.getElementById("show-grid"); if (showGridButton) { showGridButton.addEventListener("click", function() { if (apartmentsGrid) { apartmentsGrid.classList.remove("visibility-hidden"); apartmentsGrid.classList.add("visibility-visible"); } if (apartmentsMap) { apartmentsMap.classList.remove("visibility-visible"); apartmentsMap.classList.add("visibility-hidden"); } window.location.href = "#filters"; }); } // Initial load - check URL parameters and act accordingly const currentData = getCurrentFormData(); console.log('🔍 DEBUG: Initial load data:', currentData); console.log('🔍 DEBUG: hasCompleteSearchCriteria result:', hasCompleteSearchCriteria(currentData)); console.log('🔍 DEBUG: Loader element exists?', document.getElementById('loader') !== null); const willPerformApiSearch = hasCompleteSearchCriteria(currentData); // Only hide map after 1 second if NOT performing API search on load // (if doing API search, let the results determine visibility) if (!willPerformApiSearch) { setTimeout(function() { console.log('🔍 DEBUG: Hiding map after 1 second timeout (no API search)'); if (apartmentsMap) { apartmentsMap.classList.remove("visibility-visible"); apartmentsMap.classList.add("visibility-hidden"); } }, 1000); } else { console.log('🔍 DEBUG: Skipping map auto-hide timeout because API search will happen'); } if (willPerformApiSearch) { console.log('✅ DEBUG: Initial load with complete criteria (has dates) - calling API'); // IMPORTANT: Hide ALL apartment containers and show loader IMMEDIATELY to prevent any flash console.log('🔍 DEBUG: Hiding all apartment containers before showing loader'); const apartmentsGridContainer = document.getElementById('apartments-grid'); const apartmentsMapContainer = document.getElementById('apartments-map'); const notAvailableDiv = document.getElementById('not-available'); // Hide everything immediately if (apartmentsGridContainer) { apartmentsGridContainer.style.display = 'none'; console.log('🔍 DEBUG: Grid container hidden'); } if (apartmentsMapContainer) { apartmentsMapContainer.classList.add('visibility-hidden'); apartmentsMapContainer.classList.remove('visibility-visible'); console.log('🔍 DEBUG: Map container hidden'); } if (notAvailableDiv) { notAvailableDiv.style.display = 'none'; } // Also hide individual apartment items hideGridApartments(); console.log('🔍 DEBUG: About to show loader in initial load path'); showLoadingIndicator(); console.log('🔍 DEBUG: Loader should now be visible, display value:', document.getElementById('loader')?.style.display); performApiSearch(); } else { console.log('❌ DEBUG: Initial load without complete criteria - checking quartier filter'); // Only hide grid apartments if not doing API search hideGridApartments(); const quartierName = document.getElementById('place')?.textContent?.trim(); if (quartierName && !isDefaultQuartierValue(quartierName)) { console.log('Initial load: Filtering by quartier:', quartierName); filterApartmentsByQuartier(quartierName); } else { console.log('Initial load: No quartier filter - showing all apartments'); showAllApartments(); initMapWithAllApartments(); } } } // Sélectionner les boutons d'incrément et décrément const incrementButton = document.querySelector('[fs-inputcounter-element="increment-2"]'); const decrementButton = document.querySelector('[fs-inputcounter-element="decrement-2"]'); // Ajouter des écouteurs d'événements sur ces boutons if (incrementButton) { incrementButton.addEventListener('click', function() { // Laisser un court délai pour que Finsweet mette à jour la valeur setTimeout(updateUrlParams, 50); }); } if (decrementButton) { decrementButton.addEventListener('click', function() { // Laisser un court délai pour que Finsweet mette à jour la valeur setTimeout(updateUrlParams, 50); }); } // Alternative : surveillance des mutations DOM (plus robuste) const inputObserver = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'attributes' && mutation.attributeName === 'value') { updateUrlParams(); } }); }); if (numberInput) { inputObserver.observe(numberInput, { attributes: true, attributeFilter: ['value'] }); } // Modification du wording selon l'endroit sélectionné // Consolidated URL parameter utilities const URLParamManager = { // Get a single parameter value get: function(name) { const urlParams = new URLSearchParams(window.location.search); const value = urlParams.get(name); return value ? value.replace(/\+/g, ' ') : null; }, // Get all parameters as an object getAll: function() { const urlParams = new URLSearchParams(window.location.search); const params = {}; for (const [key, value] of urlParams.entries()) { params[key] = value.replace(/\+/g, ' '); } return params; }, // Set parameters and update URL set: function(paramObj) { const url = new URL(window.location.href); const params = new URLSearchParams(url.searchParams); // Clear existing relevant parameters ['name', 'guest', 'quartier', 'date'].forEach(key => { params.delete(key); }); // Set new parameters Object.entries(paramObj).forEach(([key, value]) => { if (value) params.set(key, value); }); // Update URL url.search = params.toString(); window.history.replaceState({}, '', url); console.log('URL updated to:', url.toString()); }, // Add parameters to a URL without modifying current page addToUrl: function(targetUrl, params) { const urlObj = new URL(targetUrl, window.location.origin); const urlParams = new URLSearchParams(urlObj.search); Object.entries(params).forEach(([key, value]) => { if (!urlParams.has(key)) { urlParams.set(key, value); } }); urlObj.search = urlParams.toString(); return urlObj.toString(); } }; // Simplified place text update using URL parameter manager function updatePlaceText() { const quartier = URLParamManager.get('quartier'); const placeElement = document.getElementById('place'); if (quartier && placeElement) { // Find the radio button with this value const selectedRadio = document.querySelector(`input[data-input="quartier"][value="${quartier}"]`); if (selectedRadio) { // Find the associated label const labelElement = document.querySelector(`span[for="${selectedRadio.id}"]`); if (labelElement) { placeElement.textContent = labelElement.textContent.trim(); } else { placeElement.textContent = quartier; } } else { placeElement.textContent = quartier; } } else if (placeElement) { placeElement.textContent = 'District'; } } // Appeler la fonction au chargement de la page window.addEventListener('load', updatePlaceText); // Pour détecter les changements dans l'URL window.addEventListener('popstate', updatePlaceText); window.addEventListener('hashchange', updatePlaceText); // Rendre la fonction de l'historique disponible globalement const originalPushState = window.history.pushState; window.history.pushState = function() { originalPushState.apply(this, arguments); updatePlaceText(); }; const originalReplaceState = window.history.replaceState; window.history.replaceState = function() { originalReplaceState.apply(this, arguments); updatePlaceText(); }; // Duplicate DOMContentLoaded removed - functionality moved to initializeViewAndMap() // Fonction pour cacher tous les appartements de la grille function hideGridApartments() { const gridApartmentItems = document.querySelectorAll('[data-item="apartment-grid"]'); gridApartmentItems.forEach(item => { item.style.display = 'none'; }); console.log(`${gridApartmentItems.length} appartements de la grille cachés au chargement.`); } // Fonction pour initialiser les éléments du slider et CTA function initializeItemElements() { const sliders = document.querySelectorAll('#slider-images'); const ctas = document.querySelectorAll('#cta-card'); sliders.forEach(slider => { // Utiliser visibility au lieu de display pour permettre à Swiper de calculer correctement les dimensions slider.style.visibility = 'hidden'; slider.style.position = 'absolute'; slider.style.opacity = '0'; }); ctas.forEach(cta => { cta.style.display = 'none'; }); } // Old fetchAvailabilities function removed - replaced by performApiSearch // Note: showAllApartments function already defined above - avoiding duplication // Function to hide only map apartments (used by specific map logic) function hideAllMapApartments() { const mapApartmentItems = document.querySelectorAll('[data-item="apartment-map"]'); mapApartmentItems.forEach(item => { item.style.display = 'none'; }); } // Fonction pour initialiser la carte avec tous les appartements function initMapWithAllApartments() { // Afficher tous les appartements de la carte const mapApartmentItems = document.querySelectorAll('[data-item="apartment-map"]'); mapApartmentItems.forEach(item => { item.style.display = ''; }); // Initialiser la carte initMap(); } // Fonction pour initialiser la carte avec seulement les appartements disponibles function initMapWithAvailableApartments(data) { // Extraire le tableau rentals de la réponse API const rentals = data.rentals || []; // Si les données sont invalides ou vides if (!rentals || !Array.isArray(rentals) || rentals.length === 0) { showNoResultsMessage(); return; } // Extraire les IDs des appartements disponibles const availableApartments = rentals.filter(apt => apt.is_available === true); const availableIds = availableApartments.map(apt => apt.bookingsync_apt_id.toString()); //const availableIds = rentals.map(apt => apt.bookingsync_apt_id.toString()); console.log('IDs disponibles:', availableIds); let foundAtLeastOneMap = false; let foundAtLeastOneGrid = false; let matchedCountMap = 0; let matchedCountGrid = 0; // Filtrer les appartements de la carte - afficher seulement ceux disponibles const mapApartmentItems = document.querySelectorAll('[data-item="apartment-map"]'); mapApartmentItems.forEach(item => { const bookingId = item.getAttribute('data-bookingsync'); const titleElement = item.querySelector('h1, h2, h3, h4, .apartment-title, .apt-title'); const title = titleElement ? titleElement.textContent.trim() : "Titre non trouvé"; if (bookingId && availableIds.includes(bookingId)) { console.log(`✅ MATCH (carte): "${title}" (ID: ${bookingId})`); item.style.display = ''; // Rendre visible foundAtLeastOneMap = true; matchedCountMap++; } else { console.log(`❌ NON-MATCH (carte): "${title}" (ID: ${bookingId})`); item.style.display = ''; // Garder caché } }); // Filtrer les appartements de la grille const gridApartmentItems = document.querySelectorAll('[data-item="apartment-grid"]'); gridApartmentItems.forEach(item => { const bookingId = item.getAttribute('data-bookingsync'); const titleElement = item.querySelector('h1, h2, h3, h4, .apartment-title, .apt-title'); const title = titleElement ? titleElement.textContent.trim() : "Titre non trouvé"; if (bookingId && availableIds.includes(bookingId)) { console.log(`✅ MATCH (grille): "${title}" (ID: ${bookingId})`); item.style.display = ''; // Rendre visible foundAtLeastOneGrid = true; matchedCountGrid++; } else { console.log(`❌ NON-MATCH (grille): "${title}" (ID: ${bookingId})`); item.style.display = 'none'; // Garder caché } }); console.log(`👉 ${matchedCountMap} appartements de carte et ${matchedCountGrid} appartements de grille affichés sur ${rentals.length} disponibles dans l'API.`); // Si aucun appartement correspondant n'a été trouvé dans les deux collections if (!foundAtLeastOneMap && !foundAtLeastOneGrid) { showNoResultsMessage(); return; // Ne pas initialiser la carte si aucun résultat } // Maintenant qu'on a filtré les appartements, initialiser la carte initMap(); } function initMap() { // Votre token Mapbox (à remplacer par votre propre token) mapboxgl.accessToken = 'pk.eyJ1IjoiYnJhbmRvbjM1NzUwIiwiYSI6ImNtOHliOGZlaDA5bWQycnF4NzJ4YWEwODcifQ._YC_rXSBtKZ-NBJ0UXNu2g'; // Création de la carte const map = new mapboxgl.Map({ container: 'map', style: 'mapbox://styles/mapbox/streets-v11', // Style de base center: [2.3488, 48.8534], // Paris par défaut (à ajuster) zoom: 20 }); // Une fois que la carte est chargée, ajoutez les contrôles map.on('load', function() { // Ajouter le contrôle de navigation (zoom et rotation) map.addControl(new mapboxgl.NavigationControl(), 'top-right'); // Ajouter le contrôle de géolocalisation map.addControl(new mapboxgl.GeolocateControl({ positionOptions: { enableHighAccuracy: true }, trackUserLocation: true, showUserHeading: true }), 'top-right'); // Ajouter le contrôle de plein écran map.addControl(new mapboxgl.FullscreenControl(), 'bottom-right'); }); // Variable pour stocker les éléments de style const styleElement = document.createElement('style'); styleElement.textContent = ` .mapboxgl-popup { max-width: 300px; } .mapboxgl-popup-content { padding: 15px; border-radius: 8px; } .custom-marker { background-color: #F8F6F4; border-radius: 100px; border: 1px solid #C9C9C6; padding: 4px 8px; display: flex; align-items: center; gap: 4px; font-size: 15px; white-space: nowrap; font-family: 'Open Sans', sans-serif; box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); cursor: pointer; transition: background-color 0.3s ease; } .marker-icon { width: 14px; height: 18px; display: inline-block; } .marker-price { font-weight: 500; color: #1D1D1B; } .active-marker { background-color: #1D1D1B; font-size: 18px; color: #DFD9D2; } .active-marker .marker-price { color: #DFD9D2; } `; document.head.appendChild(styleElement); // Attendre que la carte soit chargée map.on('load', function() { // Collecter les données à partir des champs cachés const markers = []; const markerElements = []; // Pour stocker les références aux éléments DOM des marqueurs // Sélectionner les éléments visibles uniquement const collectionItems = document.querySelectorAll('[data-item="apartment-map"]'); // Filtrer pour ne prendre que les éléments visibles (non cachés) const visibleItems = Array.from(collectionItems).filter(item => item.style.display !== 'none' ); console.log(`Création des marqueurs pour ${visibleItems.length} appartements visibles`); // Parcourir les éléments visibles uniquement visibleItems.forEach(function(item) { // Récupérer les données depuis les champs cachés const slugInput = item.querySelector('#locationID'); const latitudeInput = item.querySelector('#locationLatitude'); const longitudeInput = item.querySelector('#locationLongitude'); const priceElement = item.querySelector('#price'); // Récupérer les éléments par ID au lieu de classe const titleElement = item.querySelector('#title'); const descriptionElement = item.querySelector('#description'); // Extraire les valeurs const slug = slugInput ? slugInput.value : ''; const latitude = latitudeInput ? parseFloat(latitudeInput.value) : null; const longitude = longitudeInput ? parseFloat(longitudeInput.value) : null; const title = titleElement ? titleElement.textContent.trim() : slug; const description = descriptionElement ? descriptionElement.textContent.trim() : ''; // Récupérer et formater le prix let price = priceElement ? priceElement.textContent.trim() : ''; // Formater le prix pour l'affichage (supprimer les espaces et ajouter € si nécessaire) const displayPrice = price ? price.replace(/\s+/g, '').replace(/^([^€]*)$/, '$1€') : ''; // Vérifier que les coordonnées sont valides if (latitude && longitude && !isNaN(latitude) && !isNaN(longitude)) { markers.push({ type: 'Feature', geometry: { type: 'Point', coordinates: [longitude, latitude] }, properties: { id: slug, title: title, description: description, price: price, displayPrice: displayPrice } }); // Ajouter l'attribut data-marker-id à l'élément de la liste if (slug) { item.setAttribute('data-marker-id', slug); } } }); // Ajouter les markers à la carte map.addSource('places', { type: 'geojson', data: { type: 'FeatureCollection', features: markers } }); // Créer des marqueurs personnalisés markers.forEach(marker => { // Créer l'élément div pour le marqueur personnalisé const el = document.createElement('div'); el.className = 'custom-marker'; el.setAttribute('data-marker-id', marker.properties.id); // Ajouter le prix d'abord const priceSpan = document.createElement('span'); priceSpan.className = 'marker-price'; priceSpan.textContent = marker.properties.displayPrice; // Ajouter l'icône SVG ensuite const iconSvg = document.createElement('div'); iconSvg.className = 'marker-icon'; iconSvg.innerHTML = ``; // Assembler le marqueur el.appendChild(priceSpan); el.appendChild(iconSvg); // Stocker la référence à l'élément marqueur markerElements.push({ id: marker.properties.id, element: el }); // Ajouter le marqueur à la carte new mapboxgl.Marker(el) .setLngLat(marker.geometry.coordinates) .addTo(map); // Ajouter un événement de clic sur le marqueur el.addEventListener('click', function() { openMarkerAndCenterMap(marker.properties.id); }); }); // Si nous avons des marqueurs, ajuster la vue de la carte if (markers.length > 0) { // Créer un bounds pour englober tous les marqueurs const bounds = new mapboxgl.LngLatBounds(); markers.forEach(marker => { bounds.extend(marker.geometry.coordinates); }); // Ajuster la vue pour voir tous les marqueurs map.fitBounds(bounds, { padding: 50, // Ajouter du padding autour des limites maxZoom: 15 // Limiter le zoom max }); } // Initialiser les éléments du slider et CTA initializeItemElements(); // Fonction pour ouvrir un marker et centrer la carte dessus function openMarkerAndCenterMap(markerId) { // Trouver le feature correspondant au markerId const feature = markers.find(m => m.properties.id === markerId); if (!feature) return; // Récupérer les coordonnées const coordinates = feature.geometry.coordinates.slice(); // Centrer la carte sur le marker avec une animation map.flyTo({ center: coordinates, zoom: 13, // Ajustez le niveau de zoom selon vos besoins essential: true }); // Réinitialiser tous les marqueurs (suppression de la classe active) markerElements.forEach(item => { item.element.classList.remove('active-marker'); }); // Ajouter la classe active au marqueur cliqué const activeMarker = markerElements.find(m => m.id === markerId); if (activeMarker) { activeMarker.element.classList.add('active-marker'); } // Masquer tous les sliders et CTA const allSliders = document.querySelectorAll('#slider-images'); const allCtas = document.querySelectorAll('#cta-card'); allSliders.forEach(slider => { slider.style.visibility = 'hidden'; slider.style.position = 'absolute'; slider.style.opacity = '0'; }); allCtas.forEach(cta => { cta.style.display = 'none'; }); // Réinitialiser tous les éléments image-main pour les rendre visibles const allMainImages = document.querySelectorAll('#image-main'); allMainImages.forEach(img => { img.style.display = 'block'; }); // Mettre à jour la classe active sur l'élément de liste correspondant const listItems = document.querySelectorAll('[data-marker-id]'); listItems.forEach(item => { item.classList.remove('active-location'); }); const activeItem = document.querySelector(`[data-marker-id="${markerId}"]`); if (activeItem) { activeItem.classList.add('active-location'); // Cacher l'image principale de l'élément actif const activeMainImage = activeItem.querySelector('#image-main'); if (activeMainImage) { activeMainImage.style.display = 'none'; } // Afficher les éléments slider-images et cta-card de l'élément actif const activeSlider = activeItem.querySelector('#slider-images'); const activeCta = activeItem.querySelector('#cta-card'); if (activeSlider) { // Rendre le slider visible avec position normale activeSlider.style.visibility = 'visible'; activeSlider.style.position = 'static'; activeSlider.style.opacity = '1'; // Mettre à jour Swiper pour qu'il recalcule ses dimensions setTimeout(() => { if (typeof Swiper !== 'undefined') { const swipers = activeSlider.querySelectorAll('.swiper'); swipers.forEach(slider => { if (slider.swiper) { slider.swiper.update(); } }); // Mettre à jour spécifiquement le swiperNestedMap if (swiperNestedMap) { swiperNestedMap.update(); } } }, 50); } if (activeCta) { activeCta.style.display = 'block'; } // Si vous voulez que la liste défile pour montrer l'élément actif activeItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } } // Ajouter des écouteurs d'événements aux items de la liste const listItems = document.querySelectorAll('[data-marker-id]'); listItems.forEach(item => { item.addEventListener('click', function() { const markerId = this.getAttribute('data-marker-id'); if (markerId) { openMarkerAndCenterMap(markerId); } }); // Ajouter un style de curseur pour indiquer que l'élément est cliquable item.style.cursor = 'pointer'; }); }); } function showLoadingIndicator() { console.log('🔍 DEBUG: showLoadingIndicator called'); const loader = document.getElementById('loader'); console.log('🔍 DEBUG: Loader element found?', loader !== null); if (loader) { console.log('🔍 DEBUG: Before setting display - current value:', loader.style.display); loader.style.display = "flex"; console.log('🔍 DEBUG: After setting display - new value:', loader.style.display); } else { console.warn('⚠️ DEBUG: Loader element not found in DOM!'); } } function hideLoadingIndicator() { console.log('🔍 DEBUG: hideLoadingIndicator called'); const loader = document.getElementById('loader'); if (loader) { loader.style.display = "none"; console.log('🔍 DEBUG: Loader hidden'); } } // Duplicate function removed - using standardized approach above // Legacy function for backward compatibility function hideAllApartments() { ApartmentDisplayManager.hideAll(); } // Legacy function - now uses ErrorHandler function showErrorMessage(message) { ErrorHandler.showUserFriendlyError(message); } // Cleanup utilities const CleanupManager = { // Clean up event listeners removeEventListeners: function() { // Remove any dynamically added event listeners const dynamicElements = document.querySelectorAll('[data-dynamic-listener]'); dynamicElements.forEach(element => { element.replaceWith(element.cloneNode(true)); }); }, // Clean up temporary DOM elements removeTempElements: function() { const tempElements = document.querySelectorAll('.temp-element, .user-error-message, .no-results-message'); tempElements.forEach(element => element.remove()); }, // Reset form to initial state resetForm: function() { if (nameInput) nameInput.value = ''; if (numberInput) numberInput.value = '1'; quartierInputs.forEach(input => { input.checked = false; }); if (dateInput && dateInput._flatpickr) { dateInput._flatpickr.clear(); } const placeElement = document.getElementById('place'); if (placeElement) { placeElement.textContent = 'District'; } }, // Full cleanup fullCleanup: function() { this.removeEventListeners(); this.removeTempElements(); ErrorHandler.clearAllErrors(); } }; // Make cleanup available globally for debugging window.debugCleanup = CleanupManager.fullCleanup.bind(CleanupManager); // Link interception now uses centralized URL parameter manager function setupLinkInterception() { document.addEventListener('click', function(event) { let target = event.target; while (target && target.tagName !== 'A') { target = target.parentElement; } if (target && target.href && target.hostname === window.location.hostname) { const currentParams = URLParamManager.getAll(); if (Object.keys(currentParams).length > 0) { event.preventDefault(); const newUrl = URLParamManager.addToUrl(target.href, currentParams); window.location.href = newUrl; } } }); }