diff --git a/.env b/.env index 52a1e20..7bfe671 100644 --- a/.env +++ b/.env @@ -44,3 +44,4 @@ MAILER_DSN=null://null APP_OSM_BEARER=CHANGE_IT MAPBOX_TOKEN= MAPTILER_TOKEN= +USE_PLACES_WITHOUT_EMAIL_TO_REFERENCE=false diff --git a/assets/app.js b/assets/app.js index 2ba6642..bad58b0 100644 --- a/assets/app.js +++ b/assets/app.js @@ -16,6 +16,7 @@ function labourer() { // Attendre le chargement du DOM document.addEventListener('DOMContentLoaded', () => { + console.log('DOMContentLoaded'); // Générer une couleur pastel aléatoire const genererCouleurPastel = () => { // Utiliser des valeurs plus claires (180-255) pour obtenir des tons pastel @@ -60,5 +61,386 @@ document.addEventListener('DOMContentLoaded', () => { }); } + + const openingHoursFormManager = { + defaultOpeningHours: '', + init: function () { + // Rechercher l'élément par son attribut name plutôt que par son id + const openingHoursInput = document.querySelector('input[name="custom__opening_hours"]'); + if (!openingHoursInput) { + console.warn('Élément input[name="custom__opening_hours"] non trouvé'); + return; + } + this.defaultOpeningHours = openingHoursInput.value; + + this.makeForm(); + this.parseOpeningHoursValue(); + }, + parseOpeningHoursValue: function () { + + + + // Analyser la chaîne d'horaires d'ouverture + const parsedOpeningHours = {}; + + 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 [days, hours] = rule.split(' ').filter(Boolean); + + // 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); + + for (let i = startIndex; i <= endIndex; i++) { + const day = daysMap[Object.keys(daysMap)[i]]; + // Cocher la case du jour + const checkbox = document.querySelector(`#jour-${day}`); + if (checkbox) { + checkbox.checked = true; + checkbox.closest('.jour-container').querySelector('.horaires-container').classList.remove('d-none'); + } + } + } else { + // Jour unique + const day = daysMap[days]; + const checkbox = document.querySelector(`#jour-${day}`); + if (checkbox) { + checkbox.checked = true; + checkbox.closest('.jour-container').querySelector('.horaires-container').classList.remove('d-none'); + } + } + }); + } + + console.log(parsedOpeningHours); + }, + makeForm: function () { + const customOpeningHours = document.querySelector('input[name="custom__opening_hours"]'); + 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', 'g-3'); + + 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'); + + // Première plage horaire + const plage1 = this.createTimeRangeInputs(`${jour.toLowerCase()}-plage1`); + horairesContainer.appendChild(plage1); + + // Séparateur + const separator = document.createElement('div'); + separator.classList.add('text-center', 'my-2'); + separator.textContent = 'et'; + horairesContainer.appendChild(separator); + + + // 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(); + }); + }); + + 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'); + + // 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 = 'Activer cette plage'; + + 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 () { + 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(','); + } + } + }); + + // Construire la chaîne au format OSM + let osmFormat = ''; + if (joursSelectionnes.length > 0) { + const horairesStrings = joursSelectionnes.map(jour => { + return horairesParJour[jour] ? `${jour} ${horairesParJour[jour]}` : jour; + }); + osmFormat = horairesStrings.join('; '); + } + + // Mettre à jour l'input custom__opening_hours + const customOpeningHours = document.querySelector('input[name="custom__opening_hours"]'); + if (customOpeningHours) { + customOpeningHours.value = osmFormat; + } + }, + + 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; + } + } + + openingHoursFormManager.init(); + }); diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php index a7489d5..792053d 100644 --- a/src/Controller/AdminController.php +++ b/src/Controller/AdminController.php @@ -94,7 +94,7 @@ final class AdminController extends AbstractController // Redirection vers la page de modification avec les paramètres nécessaires return $this->redirectToRoute('app_public_edit', [ 'zipcode' => $commerce->getZipCode(), - 'name' => $commerce->getName() ?? '?', + 'name' => $commerce->getName()!='' ? $commerce->getName() : '?', 'uuid' => $commerce->getUuidForUrl() ]); } @@ -130,6 +130,9 @@ final class AdminController extends AbstractController $this->entityManager->flush(); } + $commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $zip_code]); + + // Initialiser les compteurs $counters = [ 'avec_horaires' => 0, @@ -213,6 +216,9 @@ final class AdminController extends AbstractController $this->entityManager->flush(); + $commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $zip_code]); + + // var_dump($commerces[0]); return $this->render('admin/labourage_results.html.twig', [ 'results' => $results, 'commerces' => $commerces, diff --git a/src/Controller/PublicController.php b/src/Controller/PublicController.php index b52bd57..e707427 100644 --- a/src/Controller/PublicController.php +++ b/src/Controller/PublicController.php @@ -377,4 +377,14 @@ class PublicController extends AbstractController 'closed_places' => $closedPlaces ]); } + + #[Route('/places_with_note', name: 'app_public_places_with_note')] + public function places_with_note(): Response + { + $places = $this->entityManager->getRepository(Place::class)->findBy(['note' => '']); + return $this->render('public/places_with_note.html.twig', [ + 'controller_name' => 'PublicController', + 'places' => $places + ]); + } } \ No newline at end of file diff --git a/src/Service/Motocultrice.php b/src/Service/Motocultrice.php index 3ea75c0..2913af3 100644 --- a/src/Service/Motocultrice.php +++ b/src/Service/Motocultrice.php @@ -111,6 +111,9 @@ class Motocultrice public function labourer(string $zone): array { + + $use_places_without_email_to_reference = $_ENV['USE_PLACES_WITHOUT_EMAIL_TO_REFERENCE'] ?? false; + if (!$zone) { throw new \InvalidArgumentException("La zone ne peut pas être vide"); } @@ -119,27 +122,44 @@ class Motocultrice $zone = addslashes(trim($zone)); $query = <<.searchArea; - ( - // Recherche des commerces et services avec email - nw["amenity"]["contact:email"](area.searchArea); - nw["amenity"]["email"](area.searchArea); - nw["shop"]["contact:email"](area.searchArea); - nw["shop"]["email"](area.searchArea); - nw["office"]["contact:email"](area.searchArea); - nw["office"]["email"](area.searchArea); - - // Recherche des commerces et services sans email pour référence - nw["amenity"](area.searchArea); - nw["shop"](area.searchArea); - nw["office"](area.searchArea); - ); - out body; - >; - out skel qt; +[out:json][timeout:25]; +area["postal_code"="{$zone}"]->.searchArea; +( + // Recherche des commerces et services avec email + nw["amenity"]["contact:email"](area.searchArea); + nw["amenity"]["email"](area.searchArea); + nw["shop"]["contact:email"](area.searchArea); + nw["shop"]["email"](area.searchArea); + nw["office"]["contact:email"](area.searchArea); + nw["office"]["email"](area.searchArea); + + // Recherche des commerces et services sans email pour référence + nw["amenity"](area.searchArea); + nw["shop"](area.searchArea); + nw["office"](area.searchArea); +); +out body; +>; +out skel qt; QUERY; + if($use_places_without_email_to_reference) { + $query = <<.searchArea; +( + nw["amenity"]["cafe|bar|restaurant"](area.searchArea); + nw["shop"](area.searchArea); + nw["tourism"="museum"](area.searchArea); + nw["office"](area.searchArea); +); +out body; +>; +out skel qt; +QUERY; + } + try { $response = $this->client->request('POST', $this->overpassApiUrl, [ 'body' => ['data' => $query] @@ -152,13 +172,15 @@ QUERY; foreach ($data['elements'] as $element) { if (isset($element['tags'])) { - + $email = ""; + if( ! $use_places_without_email_to_reference){ - $email = $element['tags']['contact:email'] ?? $element['tags']['email'] ?? null; - // On passe si pas d'email - if (!$email) { - continue; - } + $email = $element['tags']['contact:email'] ?? $element['tags']['email'] ?? null; + // On passe si pas d'email + if (!$email) { + continue; + } + } $places[] = [ 'id' => $element['id'], @@ -175,6 +197,9 @@ QUERY; return $places; } catch (\Exception $e) { + var_dump($query); + var_dump($e->getMessage()); + die(); throw new \Exception("Erreur lors de la requête Overpass : " . $e->getMessage()); } } @@ -202,9 +227,19 @@ QUERY; } elseif (isset($osm_object_data['way'])) { $osm_object_data['way']['tags_converted'] = []; } - if(isset($osm_object_data['node'])){ - foreach ($osm_object_data['node']['tag'] as $attribute) { - $osm_object_data['node']['tags_converted'][$attribute['@attributes']['k']] = $attribute['@attributes']['v']; + if(isset($osm_object_data['node'])){ + // Vérifier si le nœud a des tags + if (!isset($osm_object_data['node']['tag'])) { + $osm_object_data['node']['tags_converted'] = []; + return $osm_object_data['node']; + } + + // Si un seul tag, le convertir en tableau + if (isset($osm_object_data['node']['tag']['@attributes'])) { + $osm_object_data['node']['tag'] = [$osm_object_data['node']['tag']]; + } + foreach ($osm_object_data['node']['tag'] as $tag) { + $osm_object_data['node']['tags_converted'][$tag['@attributes']['k']] = $tag['@attributes']['v']; } $osm_object_data['node']['tags_converted'] = $this->migrate_tags($osm_object_data['node']['tags_converted']); return $osm_object_data['node']; diff --git a/templates/admin/labourage_results.html.twig b/templates/admin/labourage_results.html.twig index 2c91677..2d17d26 100644 --- a/templates/admin/labourage_results.html.twig +++ b/templates/admin/labourage_results.html.twig @@ -22,26 +22,66 @@ commerces existants disposant d'un moyen de contact mail: {{ commerces|length }}

{# {{ dump(commerces[0]) }} #} -