/** * Gestion du formulaire d'horaires d'ouverture * */ const openingHoursFormManager = { defaultOpeningHours: '', inputSelector: '', init: function (inputSelector = 'input[name="custom__opening_hours"]') { // Rechercher l'élément par son attribut name plutôt que par son id this.setInputSelector(inputSelector); const openingHoursInput = document.querySelector(inputSelector); if (!openingHoursInput) { console.warn('Élément ', inputSelector, ' non trouvé'); return; } this.defaultOpeningHours = openingHoursInput.value; // Créer la div de rendu si elle n'existe pas let renderDiv = document.getElementById('opening_hours_render'); if (!renderDiv) { renderDiv = document.createElement('div'); renderDiv.id = 'opening_hours_render'; renderDiv.classList.add('mt-4'); openingHoursInput.parentNode.insertBefore(renderDiv, openingHoursInput.nextSibling); } this.makeForm(inputSelector); if (this.defaultOpeningHours !== '') { this.parseOpeningHoursValue(inputSelector); } // Ajouter un écouteur d'événement keyup sur l'input des horaires openingHoursInput.addEventListener('keyup', () => { this.defaultOpeningHours = openingHoursInput.value; this.parseOpeningHoursValue(inputSelector); }); }, setInputSelector: function (inputSelector) { this.inputSelector = inputSelector; }, /** * convertir les checkboxes et inputs en horaires OSM dans l'input de référence * @param {string} inputSelector */ parseOpeningHoursValue: function (inputSelector = 'input[name="custom__opening_hours"]') { // Analyser la chaîne d'horaires d'ouverture const parsedOpeningHours = []; // Masquer toutes les plages horaires par défaut const allDayContainers = document.querySelectorAll('.jour-container'); allDayContainers.forEach(container => { const checkbox = container.querySelector('input[type="checkbox"]'); const horairesContainer = container.querySelector('.horaires-container'); checkbox.checked = false; horairesContainer.classList.add('d-none'); }); if (this.defaultOpeningHours) { // Diviser les différentes règles (séparées par des points-virgules) const rules = this.defaultOpeningHours.split(';').map(r => r.trim()); rules.forEach(rule => { // Extraire les jours et les heures const parts = rule.split(' ').filter(Boolean); const days = parts[0]; const hours = parts.slice(1).join(' '); // Convertir les jours en français const daysMap = { 'Mo': 'lundi', 'Tu': 'mardi', 'We': 'mercredi', 'Th': 'jeudi', 'Fr': 'vendredi', 'Sa': 'samedi', 'Su': 'dimanche' }; // Gérer les plages de jours (ex: Mo-Fr) if (days.includes('-')) { const [start, end] = days.split('-'); const startIndex = Object.keys(daysMap).indexOf(start); const endIndex = Object.keys(daysMap).indexOf(end); const dayRange = []; for (let i = startIndex; i <= endIndex; i++) { const day = Object.keys(daysMap)[i]; dayRange.push(day); // Cocher la case du jour const checkbox = document.querySelector(`#jour-${daysMap[day]}`); if (checkbox) { checkbox.checked = true; const horairesContainer = checkbox.closest('.jour-container').querySelector('.horaires-container'); horairesContainer.classList.remove('d-none'); // Décocher la deuxième plage si elle n'est pas présente dans l'input if (hours && !hours.includes(',')) { const plage2Checkbox = horairesContainer.querySelector(`input[name="${daysMap[day]}-plage2-active"]`); if (plage2Checkbox) { plage2Checkbox.checked = false; } } // Remplir la première plage horaire si spécifiée if (hours) { const [startTime, endTime] = hours.split('-'); if (startTime && endTime) { const [startHour, startMinute] = startTime.split(':'); const [endHour, endMinute] = endTime.split(':'); const startHourInput = horairesContainer.querySelector(`input[name="${daysMap[day]}-plage1-start-hour"]`); const startMinuteInput = horairesContainer.querySelector(`input[name="${daysMap[day]}-plage1-start-minute"]`); const endHourInput = horairesContainer.querySelector(`input[name="${daysMap[day]}-plage1-end-hour"]`); const endMinuteInput = horairesContainer.querySelector(`input[name="${daysMap[day]}-plage1-end-minute"]`); if (startHourInput) startHourInput.value = startHour; if (startMinuteInput) startMinuteInput.value = startMinute; if (endHourInput) endHourInput.value = endHour; if (endMinuteInput) endMinuteInput.value = endMinute; } } } } parsedOpeningHours.push({ days: dayRange, hours: hours }); } else { // Jour unique const day = daysMap[days]; const checkbox = document.querySelector(`#jour-${day}`); if (checkbox) { checkbox.checked = true; const horairesContainer = checkbox.closest('.jour-container').querySelector('.horaires-container'); horairesContainer.classList.remove('d-none'); // Décocher la deuxième plage si elle n'est pas présente dans l'input if (hours && !hours.includes(',')) { const plage2Checkbox = horairesContainer.querySelector(`input[name="${day}-plage2-active"]`); if (plage2Checkbox) { plage2Checkbox.checked = false; } } // Remplir la première plage horaire si spécifiée if (hours) { const [startTime, endTime] = hours.split('-'); if (startTime && endTime) { const [startHour, startMinute] = startTime.split(':'); const [endHour, endMinute] = endTime.split(':'); const startHourInput = horairesContainer.querySelector(`input[name="${day}-plage1-start-hour"]`); const startMinuteInput = horairesContainer.querySelector(`input[name="${day}-plage1-start-minute"]`); const endHourInput = horairesContainer.querySelector(`input[name="${day}-plage1-end-hour"]`); const endMinuteInput = horairesContainer.querySelector(`input[name="${day}-plage1-end-minute"]`); if (startHourInput) startHourInput.value = startHour; if (startMinuteInput) startMinuteInput.value = startMinute; if (endHourInput) endHourInput.value = endHour; if (endMinuteInput) endMinuteInput.value = endMinute; } } } parsedOpeningHours.push({ days: [days], hours: hours }); } }); } this.renderOpeningHours(parsedOpeningHours); console.log(parsedOpeningHours); }, makeForm: function (inputSelector = 'input[name="custom__opening_hours"]') { const customOpeningHours = document.querySelector(inputSelector); console.log('makeForm customOpeningHours', customOpeningHours); if (customOpeningHours) { // Créer un conteneur flex pour aligner l'input et le formulaire const container = document.createElement('div'); container.classList.add('d-flex', 'flex-column', 'flex-md-row', 'align-items-start', 'gap-3', 'w-100'); // Créer le formulaire const form = document.createElement('form'); form.id = 'app_public_opening_hours'; form.classList.add('mt-3', 'flex-grow-1'); // Créer les cases à cocher pour chaque jour const jours = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']; const joursDiv = document.createElement('div'); joursDiv.classList.add('jours-ouverture', 'mb-4', 'row', 'mx-4'); jours.forEach(jour => { const jourContainer = document.createElement('div'); jourContainer.classList.add('jour-container', 'col-12'); // Checkbox pour le jour const divCheck = document.createElement('div'); divCheck.classList.add('form-check', 'mb-2'); const input = document.createElement('input'); input.type = 'checkbox'; input.id = `jour-${jour.toLowerCase()}`; input.name = `jour-${jour.toLowerCase()}`; input.classList.add('form-check-input'); const label = document.createElement('label'); label.htmlFor = `jour-${jour.toLowerCase()}`; label.classList.add('form-check-label'); label.textContent = jour; divCheck.appendChild(input); divCheck.appendChild(label); jourContainer.appendChild(divCheck); // Conteneur pour les plages horaires const horairesContainer = document.createElement('div'); horairesContainer.classList.add('horaires-container', 'ms-4', 'd-none', 'row', 'g-3'); // Première plage horaire const plage1 = this.createTimeRangeInputs(`${jour.toLowerCase()}-plage1`); horairesContainer.appendChild(plage1); // Deuxième plage horaire const plage2 = this.createTimeRangeInputs(`${jour.toLowerCase()}-plage2`); horairesContainer.appendChild(plage2); jourContainer.appendChild(horairesContainer); joursDiv.appendChild(jourContainer); // Ajouter l'événement pour afficher/masquer les plages horaires input.addEventListener('change', (e) => { horairesContainer.classList.toggle('d-none', !e.target.checked); this.convertToOSMOpeningHours(inputSelector); }); }); form.appendChild(joursDiv); // Ajouter le formulaire au conteneur container.appendChild(form); // Insérer le conteneur après l'input original const parent = customOpeningHours.parentNode; if (parent) { parent.insertBefore(container, customOpeningHours.nextSibling); } // Ajouter un debounce pour limiter les appels lors des modifications const debounce = (func, wait) => { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }; // Appliquer le debounce à la fonction de conversion const debouncedConvert = debounce(() => { this.convertToOSMOpeningHours(); }, 300); // Ajouter les listeners sur tous les inputs const allInputs = form.querySelectorAll('input'); allInputs.forEach(input => { input.addEventListener('change', debouncedConvert); input.addEventListener('input', debouncedConvert); }); } else { console.log('pas d input opening hours détecté') } }, createTimeRangeInputs: function (prefix) { const container = document.createElement('div'); container.classList.add('time-range', 'mb-2', 'col-12', 'col-md-6'); // Case à cocher pour activer la plage const checkboxContainer = document.createElement('div'); checkboxContainer.classList.add('form-check', 'mb-2'); const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = `${prefix}-active`; checkbox.name = `${prefix}-active`; checkbox.classList.add('form-check-input'); checkbox.checked = true; const checkboxLabel = document.createElement('label'); checkboxLabel.htmlFor = `${prefix}-active`; checkboxLabel.classList.add('form-check-label'); checkboxLabel.textContent = ''; checkboxContainer.appendChild(checkbox); checkboxContainer.appendChild(checkboxLabel); container.appendChild(checkboxContainer); // Conteneur pour les inputs d'horaires const timeContainer = document.createElement('div'); timeContainer.classList.add('ms-4', 'row', 'g-2'); // Heure de début const startContainer = document.createElement('div'); startContainer.classList.add('col-6', 'd-flex', 'align-items-center', 'gap-2'); const startHour = document.createElement('input'); startHour.type = 'number'; startHour.min = '0'; startHour.max = '23'; startHour.classList.add('form-control', 'form-control-sm'); startHour.style.width = '60px'; startHour.placeholder = 'HH'; startHour.name = `${prefix}-start-hour`; // Définir les horaires par défaut selon la plage startHour.value = prefix.includes('plage1') ? '08' : '14'; const startMinute = document.createElement('input'); startMinute.type = 'number'; startMinute.min = '0'; startMinute.max = '59'; startMinute.classList.add('form-control', 'form-control-sm'); startMinute.style.width = '60px'; startMinute.placeholder = 'MM'; startMinute.name = `${prefix}-start-minute`; startMinute.value = '00'; // Heure de fin const endContainer = document.createElement('div'); endContainer.classList.add('col-6', 'd-flex', 'align-items-center', 'gap-2'); const endHour = document.createElement('input'); endHour.type = 'number'; endHour.min = '0'; endHour.max = '23'; endHour.classList.add('form-control', 'form-control-sm'); endHour.style.width = '60px'; endHour.placeholder = 'HH'; endHour.name = `${prefix}-end-hour`; // Définir les horaires par défaut selon la plage endHour.value = prefix.includes('plage1') ? '12' : '18'; const endMinute = document.createElement('input'); endMinute.type = 'number'; endMinute.min = '0'; endMinute.max = '59'; endMinute.classList.add('form-control', 'form-control-sm'); endMinute.style.width = '60px'; endMinute.placeholder = 'MM'; endMinute.name = `${prefix}-end-minute`; endMinute.value = '00'; // Créer le texte avec les horaires const timeText = document.createElement('div'); timeText.classList.add('d-flex', 'align-items-center', 'gap-2', 'flex-wrap'); // Texte de début const startText = document.createElement('span'); startText.textContent = 'de'; timeText.appendChild(startText); timeText.appendChild(startHour); timeText.appendChild(document.createTextNode(':')); timeText.appendChild(startMinute); // Texte du milieu const middleText = document.createElement('span'); middleText.textContent = 'à'; timeText.appendChild(middleText); // Texte de fin timeText.appendChild(endHour); timeText.appendChild(document.createTextNode(':')); timeText.appendChild(endMinute); timeContainer.appendChild(timeText); container.appendChild(timeContainer); // Fonction pour mettre à jour le style des inputs const updateInputsStyle = (isActive) => { const inputs = timeContainer.querySelectorAll('input'); inputs.forEach(input => { if (isActive) { input.classList.remove('bg-light', 'text-muted'); input.disabled = false; } else { input.classList.add('bg-light', 'text-muted'); input.disabled = true; } }); }; // Ajouter l'événement sur la checkbox checkbox.addEventListener('change', (e) => { updateInputsStyle(e.target.checked); }); // Appliquer le style initial updateInputsStyle(checkbox.checked); return container; }, convertToOSMOpeningHours: function (inputSelector = 'input[name="custom__opening_hours"]') { const jours = { 'Lundi': 'Mo', 'Mardi': 'Tu', 'Mercredi': 'We', 'Jeudi': 'Th', 'Vendredi': 'Fr', 'Samedi': 'Sa', 'Dimanche': 'Su' }; let joursSelectionnes = []; let horairesParJour = {}; // Parcourir les checkboxes des jours Object.keys(jours).forEach(jour => { const checkbox = document.getElementById(`jour-${jour.toLowerCase()}`); if (checkbox && checkbox.checked) { joursSelectionnes.push(jours[jour]); // Récupérer les horaires pour ce jour const prefix = jour.toLowerCase(); const plage1 = this.getTimeRange(`${prefix}-plage1`); const plage2 = this.getTimeRange(`${prefix}-plage2`); let horaires = []; if (plage1) horaires.push(plage1); if (plage2) horaires.push(plage2); if (horaires.length > 0) { horairesParJour[jours[jour]] = horaires.join(','); } } }); // Optimiser les plages horaires identiques consécutives let optimizedRules = []; let currentRule = null; let currentDays = []; let currentHours = null; joursSelectionnes.forEach((jour, index) => { const hours = horairesParJour[jour]; if (currentHours === null) { // Première règle currentHours = hours; currentDays = [jour]; } else if (hours === currentHours) { // Mêmes horaires que la règle en cours currentDays.push(jour); } else { // Horaires différents, on finalise la règle en cours if (currentDays.length > 0) { optimizedRules.push({ days: currentDays, hours: currentHours }); } // On commence une nouvelle règle currentDays = [jour]; currentHours = hours; } // Si c'est le dernier jour, on finalise la règle en cours if (index === joursSelectionnes.length - 1 && currentDays.length > 0) { optimizedRules.push({ days: currentDays, hours: currentHours }); } }); // Construire la chaîne au format OSM let osmFormat = optimizedRules.map(rule => { const days = rule.days.length > 1 ? `${rule.days[0]}-${rule.days[rule.days.length - 1]}` : rule.days[0]; return rule.hours ? `${days} ${rule.hours}` : days; }).join('; '); // Mettre à jour l'input custom__opening_hours const customOpeningHours = document.querySelector(inputSelector); if (customOpeningHours) { customOpeningHours.value = osmFormat; } // Mettre à jour le rendu visuel this.renderOpeningHours(optimizedRules); }, renderOpeningHours: function (rules) { const container = document.getElementById('opening_hours_render'); console.log('renderOpeningHours', rules); if (!container) return; // Vider le conteneur container.innerHTML = ''; // Créer le style pour les sections d'ouverture const style = document.createElement('style'); style.textContent = ` .opening-hours-day { background-color: #f8f9fa; border-radius: 4px; margin-bottom: 8px; padding: 8px; position: relative; height: 40px; } .opening-hours-time { background-color: #d4edda; border-radius: 4px; position: absolute; height: 24px; top: 8px; } .opening-hours-label { position: absolute; left: 8px; top: 50%; transform: translateY(-50%); z-index: 1; } `; document.head.appendChild(style); // Mapping des jours OSM vers français const joursMap = { 'Mo': 'Lundi', 'Tu': 'Mardi', 'We': 'Mercredi', 'Th': 'Jeudi', 'Fr': 'Vendredi', 'Sa': 'Samedi', 'Su': 'Dimanche' }; // Créer les lignes pour chaque jour Object.entries(joursMap).forEach(([osmDay, frenchDay]) => { const dayDiv = document.createElement('div'); dayDiv.classList.add('opening-hours-day'); // Ajouter le label du jour const label = document.createElement('span'); label.classList.add('opening-hours-label'); label.textContent = frenchDay; dayDiv.appendChild(label); // Trouver les horaires pour ce jour const rule = rules.find(r => r.days.includes(osmDay)); if (rule && rule.hours) { const timeRanges = rule.hours.split(','); timeRanges.forEach((timeRange, index) => { const [start, end] = timeRange.split('-'); const [startHour, startMinute] = start.split(':'); const [endHour, endMinute] = end.split(':'); // Calculer la position et la largeur const startMinutes = parseInt(startHour) * 60 + parseInt(startMinute); const endMinutes = parseInt(endHour) * 60 + parseInt(endMinute); const totalMinutes = 24 * 60; const left = (startMinutes / totalMinutes) * 100; const width = ((endMinutes - startMinutes) / totalMinutes) * 100; const timeDiv = document.createElement('div'); timeDiv.classList.add('opening-hours-time'); timeDiv.style.left = `${left}%`; timeDiv.style.width = `${width}%`; timeDiv.title = `${start}-${end}`; dayDiv.appendChild(timeDiv); }); } container.appendChild(dayDiv); }); }, getTimeRange: function (prefix) { const isActive = document.querySelector(`input[name="${prefix}-active"]`).checked; if (!isActive) return null; const startHour = document.querySelector(`input[name="${prefix}-start-hour"]`).value; const startMinute = document.querySelector(`input[name="${prefix}-start-minute"]`).value; const endHour = document.querySelector(`input[name="${prefix}-end-hour"]`).value; const endMinute = document.querySelector(`input[name="${prefix}-end-minute"]`).value; if (startHour && startMinute && endHour && endMinute) { const start = `${startHour.padStart(2, '0')}:${startMinute.padStart(2, '0')}`; const end = `${endHour.padStart(2, '0')}:${endMinute.padStart(2, '0')}`; return `${start}-${end}`; } return null; } } window.openingHoursFormManager = openingHoursFormManager;