From c837095cc3a42b8955f2e68ad5fdb05780704be2 Mon Sep 17 00:00:00 2001 From: Serreau Jovann Date: Wed, 4 Feb 2026 12:35:53 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(ReserverController):=20Calcule?= =?UTF-8?q?=20l'itin=C3=A9raire=20et=20affiche=20sur=20une=20carte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajoute le calcul de l'itinéraire via l'API Geoplateforme et affiche le résultat sur une carte Leaflet. Met à jour la CSP. --- assets/reserve.js | 3 +- assets/tools/LeafletMap.js | 53 ++++++++ config/packages/nelmio_security.yaml | 4 + migrations/Version20260204084418.php | 31 +++++ src/Controller/ReserverController.php | 29 ++-- templates/revervation/estimate_delivery.twig | 134 +++++++++++++++++++ 6 files changed, 241 insertions(+), 13 deletions(-) create mode 100644 assets/tools/LeafletMap.js create mode 100644 migrations/Version20260204084418.php create mode 100644 templates/revervation/estimate_delivery.twig diff --git a/assets/reserve.js b/assets/reserve.js index 1472a57..e57d3f8 100644 --- a/assets/reserve.js +++ b/assets/reserve.js @@ -4,6 +4,7 @@ import { CookieBanner } from "./tools/CookieBanner.js"; import { FlowReserve } from "./tools/FlowReserve.js"; import { FlowDatePicker } from "./tools/FlowDatePicker.js"; import { FlowAddToCart } from "./tools/FlowAddToCart.js"; +import { LeafletMap } from "./tools/LeafletMap.js"; import * as Turbo from "@hotwired/turbo"; import { onLCP, onINP, onCLS } from 'web-vitals'; import AOS from 'aos'; @@ -252,7 +253,7 @@ const initRegisterLogic = () => { // --- INITIALISATION --- const registerComponents = () => { - const comps = [['utm-event', UtmEvent], ['utm-account', UtmAccount], ['cookie-banner', CookieBanner]]; + const comps = [['utm-event', UtmEvent], ['utm-account', UtmAccount], ['cookie-banner', CookieBanner], ['leaflet-map', LeafletMap]]; comps.forEach(([name, cl]) => { if (!customElements.get(name)) customElements.define(name, cl); }); if(!customElements.get('flow-reserve')) diff --git a/assets/tools/LeafletMap.js b/assets/tools/LeafletMap.js new file mode 100644 index 0000000..424c960 --- /dev/null +++ b/assets/tools/LeafletMap.js @@ -0,0 +1,53 @@ + +export class LeafletMap extends HTMLElement { + connectedCallback() { + if (this.map) return; + + // Ensure Leaflet is loaded + if (typeof L === 'undefined') { + const checkLeaflet = setInterval(() => { + if (typeof L !== 'undefined') { + clearInterval(checkLeaflet); + this.initMap(); + } + }, 100); + return; + } + + this.initMap(); + } + + initMap() { + if (!this.dataset.geometry) return; + + try { + const geometry = JSON.parse(this.dataset.geometry); + + // 'this' refers to the custom element itself. + // Leaflet can attach to it if it has dimensions. + this.map = L.map(this); + + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(this.map); + + const layer = L.geoJSON(geometry).addTo(this.map); + this.map.fitBounds(layer.getBounds(), {padding: [50, 50]}); + + // Fix for map not rendering correctly sometimes when container size changes or initial load + setTimeout(() => { + this.map.invalidateSize(); + }, 100); + + } catch (e) { + console.error('Error initializing Leaflet map:', e); + } + } + + disconnectedCallback() { + if (this.map) { + this.map.remove(); + this.map = null; + } + } +} diff --git a/config/packages/nelmio_security.yaml b/config/packages/nelmio_security.yaml index e25abef..297bbb7 100644 --- a/config/packages/nelmio_security.yaml +++ b/config/packages/nelmio_security.yaml @@ -36,6 +36,7 @@ nelmio_security: - "https://auth.esy-web.dev" - "https://static.cloudflareinsights.com" - "https://challenges.cloudflare.com" + - "https://unpkg.com" connect-src: - "'self'" - "https://sentry.esy-web.dev" @@ -54,10 +55,13 @@ nelmio_security: - "'self'" - "'unsafe-inline'" - "https://chat.esy-web.dev" + - "https://unpkg.com" img-src: - "'self'" - "data:" - "https://chat.esy-web.dev" + - "https://*.tile.openstreetmap.org" + - "https://unpkg.com" font-src: - "'self'" - "data:" diff --git a/migrations/Version20260204084418.php b/migrations/Version20260204084418.php new file mode 100644 index 0000000..8599375 --- /dev/null +++ b/migrations/Version20260204084418.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE order_session ADD options JSON DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE order_session DROP options'); + } +} diff --git a/src/Controller/ReserverController.php b/src/Controller/ReserverController.php index 866b280..93cc05b 100644 --- a/src/Controller/ReserverController.php +++ b/src/Controller/ReserverController.php @@ -1168,18 +1168,22 @@ class ReserverController extends AbstractController $startLat = 49.849; $startLon = 3.286; - // Formule de Haversine - $earthRadius = 6371; // km + // Calcul itinéraire via API Geoplateforme + $itineraireResponse = $client->request('GET', 'https://data.geopf.fr/navigation/itineraire', [ + 'query' => [ + 'resource' => 'bdtopo-osrm', + 'start' => $startLon . ',' . $startLat, + 'end' => $lon . ',' . $lat, + 'profile' => 'car', + 'optimization' => 'fastest', + 'distanceUnit' => 'kilometer', + 'geometryFormat' => 'geojson' + ] + ]); - $dLat = deg2rad($lat - $startLat); - $dLon = deg2rad($lon - $startLon); - - $a = sin($dLat / 2) * sin($dLat / 2) + - cos(deg2rad($startLat)) * cos(deg2rad($lat)) * - sin($dLon / 2) * sin($dLon / 2); - - $c = 2 * atan2(sqrt($a), sqrt(1 - $a)); - $distance = $earthRadius * $c; + $itineraire = $itineraireResponse->toArray(); + $distance = $itineraire['distance']; + $geometry = $itineraire['geometry'] ?? null; $rate = 0.50; $trips = 4; @@ -1209,7 +1213,8 @@ class ReserverController extends AbstractController return $this->render('revervation/estimate_delivery.twig', [ 'form' => $form->createView(), 'estimation' => $estimation, - 'details' => $details ?? null + 'details' => $details ?? null, + 'geometry' => $geometry ?? null ]); } } diff --git a/templates/revervation/estimate_delivery.twig b/templates/revervation/estimate_delivery.twig new file mode 100644 index 0000000..4dd3322 --- /dev/null +++ b/templates/revervation/estimate_delivery.twig @@ -0,0 +1,134 @@ +{% extends 'revervation/base.twig' %} + +{% block title %}Estimer les frais de livraison | Ludik Event{% endblock %} + +{% block stylesheets %} + {{ parent() }} + +{% endblock %} + +{% block body %} +
+
+

+ Estimer la livraison +

+ +
+

+ Renseignez l'adresse de votre événement pour obtenir une estimation des frais de livraison. +

+ + {{ form_start(form, {'attr': {'class': 'space-y-6', 'data-turbo': 'false'}}) }} + +
+ {{ form_label(form.address, 'Adresse complète', {'label_attr': {'class': 'text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1 mb-2 block'}}) }} + {{ form_widget(form.address, {'attr': {'class': 'w-full bg-white border border-slate-200 rounded-2xl px-5 py-4 text-slate-900 outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 transition-all', 'placeholder': 'Ex: 10 rue de la Paix'}}) }} +
+ +
+
+ {{ form_label(form.zipCode, 'Code Postal', {'label_attr': {'class': 'text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1 mb-2 block'}}) }} + {{ form_widget(form.zipCode, {'attr': {'class': 'w-full bg-white border border-slate-200 rounded-2xl px-5 py-4 text-slate-900 outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 transition-all', 'placeholder': '02000'}}) }} +
+
+ {{ form_label(form.city, 'Ville', {'label_attr': {'class': 'text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1 mb-2 block'}}) }} + {{ form_widget(form.city, {'attr': {'class': 'w-full bg-white border border-slate-200 rounded-2xl px-5 py-4 text-slate-900 outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 transition-all', 'placeholder': 'Laon'}}) }} +
+
+ +
+ +
+ + {{ form_end(form) }} + + {% if estimation is defined and estimation is not null %} +
+
+ Résultat de l'estimation + {% if details.isFree %} +
+ Offert ! + Zone gratuite +
+

Votre événement se trouve à moins de 10km de nos locaux.

+ {% else %} +

+ {{ estimation|format_currency('EUR') }} +

+ {% endif %} +
+ + {% if details is defined %} +
+

Détails du calcul

+ +
+ Distance réelle (Aller) + {{ details.distance|number_format(1, ',', ' ') }} km +
+ +
+ Franchise kilométrique + - 10.0 km (Offerts) +
+ +
+ +
+ Distance facturée + {{ details.chargedDistance|number_format(1, ',', ' ') }} km +
+ +
+ Nombre de trajets + {{ details.trips }} (2 A/R) +
+ +
+ Tarif kilométrique + {{ details.rate }} € / km +
+ + {% if not details.isFree %} +
+ + ({{ details.distance|number_format(1) }} - 10) x {{ details.trips }} x {{ details.rate }}€ = {{ estimation|number_format(2) }}€ + +
+ {% endif %} +
+ {% endif %} + + {% if geometry is defined and geometry is not null %} + + + {% endif %} + +
+

+ Cette estimation est donnée à titre indicatif et sera confirmée lors de la validation de votre devis. +

+
+
+ {% endif %} +
+
+
+{% endblock %} + +{% block javascripts %} + {{ parent() }} + {% if geometry is defined and geometry is not null %} + + {% endif %} +{% endblock %}