This commit is contained in:
marvin
2024-11-22 14:22:29 +01:00
parent cdfce3a639
commit 36db7da07d
11 changed files with 5 additions and 53 deletions

View File

@@ -73,7 +73,6 @@ class LieuController extends AbstractController
return new JsonResponse(['error' => 'Ville non trouvée'], Response::HTTP_NOT_FOUND); return new JsonResponse(['error' => 'Ville non trouvée'], Response::HTTP_NOT_FOUND);
} }
// Utilisez l'API Nominatim pour récupérer les bounds
$params = [ $params = [
'q' => $ville->getNom(), 'q' => $ville->getNom(),
'format' => 'json', 'format' => 'json',

View File

@@ -28,7 +28,6 @@ class SortieController extends AbstractController
): Response { ): Response {
$sortie = new Sortie(); $sortie = new Sortie();
// Récupérer l'utilisateur connecté
$token = $tokenStorage->getToken(); $token = $tokenStorage->getToken();
$userConnect = $token?->getUser(); $userConnect = $token?->getUser();
@@ -37,12 +36,10 @@ class SortieController extends AbstractController
return $this->redirectToRoute('app_login'); return $this->redirectToRoute('app_login');
} }
// Créer le formulaire
$form = $this->createForm(SortieType::class, $sortie); $form = $this->createForm(SortieType::class, $sortie);
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
// Récupérer l'ID du lieu depuis le formulaire
$lieuId = $form->get('lieu')->getData(); $lieuId = $form->get('lieu')->getData();
$lieu = $lieuRepository->find($lieuId); $lieu = $lieuRepository->find($lieuId);
@@ -51,10 +48,8 @@ class SortieController extends AbstractController
return $this->redirectToRoute('sortie_create'); return $this->redirectToRoute('sortie_create');
} }
// Associer le lieu à la sortie
$sortie->setLieu($lieu); $sortie->setLieu($lieu);
// Associer le site à partir de l'utilisateur connecté
$participant = $participantRepository->find($userConnect->getIdParticipant()); $participant = $participantRepository->find($userConnect->getIdParticipant());
if (!$participant) { if (!$participant) {
@@ -64,20 +59,16 @@ class SortieController extends AbstractController
$sortie->setSite($participant->getSite()); $sortie->setSite($participant->getSite());
// Récupérer l'état "en création" avec l'ID donné
$etat = $etatRepository->find('019349ba-38ca-7a39-93c3-62f046671525'); $etat = $etatRepository->find('019349ba-38ca-7a39-93c3-62f046671525');
if (!$etat) { if (!$etat) {
$this->addFlash('error', 'État non trouvé.'); $this->addFlash('error', 'État non trouvé.');
return $this->redirectToRoute('sortie_create'); return $this->redirectToRoute('sortie_create');
} }
// Associer l'état à la sortie
$sortie->setEtat($etat); $sortie->setEtat($etat);
// Associer l'organisateur à la sortie
$sortie->setOrganisateur($participant); $sortie->setOrganisateur($participant);
// Sauvegarder la sortie
$entityManager->persist($sortie); $entityManager->persist($sortie);
$entityManager->flush(); $entityManager->flush();
@@ -95,7 +86,6 @@ class SortieController extends AbstractController
#[Route('/view/{id}', name: 'view', methods: ['GET'])] #[Route('/view/{id}', name: 'view', methods: ['GET'])]
public function view(string $id, EntityManagerInterface $entityManager, TokenStorageInterface $tokenStorage): Response public function view(string $id, EntityManagerInterface $entityManager, TokenStorageInterface $tokenStorage): Response
{ {
// Récupérer l'utilisateur connecté
$token = $tokenStorage->getToken(); $token = $tokenStorage->getToken();
$userConnect = $token?->getUser(); $userConnect = $token?->getUser();
@@ -106,7 +96,6 @@ class SortieController extends AbstractController
return $this->redirectToRoute('home'); return $this->redirectToRoute('home');
} }
// Récupérer le profil de l'utilisateur connecté
$profile = $this->getUser(); $profile = $this->getUser();
return $this->render('sortie/view.html.twig', [ return $this->render('sortie/view.html.twig', [
@@ -118,7 +107,6 @@ class SortieController extends AbstractController
#[Route('/inscription/{id}', name: 'inscription', methods: ['POST'])] #[Route('/inscription/{id}', name: 'inscription', methods: ['POST'])]
public function inscription(string $id, EntityManagerInterface $entityManager, TokenStorageInterface $tokenStorage): Response public function inscription(string $id, EntityManagerInterface $entityManager, TokenStorageInterface $tokenStorage): Response
{ {
// Récupérer l'utilisateur connecté
$token = $tokenStorage->getToken(); $token = $tokenStorage->getToken();
$userConnect = $token?->getUser(); $userConnect = $token?->getUser();
@@ -127,7 +115,6 @@ class SortieController extends AbstractController
return $this->redirectToRoute('app_login'); return $this->redirectToRoute('app_login');
} }
// Récupérer la sortie
$sortie = $entityManager->getRepository(Sortie::class)->find($id); $sortie = $entityManager->getRepository(Sortie::class)->find($id);
if (!$sortie) { if (!$sortie) {
@@ -135,25 +122,21 @@ class SortieController extends AbstractController
return $this->redirectToRoute('home'); return $this->redirectToRoute('home');
} }
// Vérifier que la sortie est "Ouverte"
if ($sortie->getEtat()->getLibelle() !== 'Ouverte') { if ($sortie->getEtat()->getLibelle() !== 'Ouverte') {
$this->addFlash('error', 'Vous ne pouvez pas vous inscrire à cette sortie car elle n\'est pas ouverte.'); $this->addFlash('error', 'Vous ne pouvez pas vous inscrire à cette sortie car elle n\'est pas ouverte.');
return $this->redirectToRoute('sortie_view', ['id' => $id]); return $this->redirectToRoute('sortie_view', ['id' => $id]);
} }
// Vérifier si l'utilisateur est déjà inscrit
if ($sortie->getParticipants()->contains($userConnect)) { if ($sortie->getParticipants()->contains($userConnect)) {
$this->addFlash('error', 'Vous êtes déjà inscrit à cette sortie.'); $this->addFlash('error', 'Vous êtes déjà inscrit à cette sortie.');
return $this->redirectToRoute('sortie_view', ['id' => $id]); return $this->redirectToRoute('sortie_view', ['id' => $id]);
} }
// Vérifier le nombre maximum d'inscriptions
if ($sortie->getParticipants()->count() >= $sortie->getNbInscriptionsMax()) { if ($sortie->getParticipants()->count() >= $sortie->getNbInscriptionsMax()) {
$this->addFlash('error', 'Le nombre maximum d\'inscriptions a été atteint pour cette sortie.'); $this->addFlash('error', 'Le nombre maximum d\'inscriptions a été atteint pour cette sortie.');
return $this->redirectToRoute('sortie_view', ['id' => $id]); return $this->redirectToRoute('sortie_view', ['id' => $id]);
} }
// Ajouter l'utilisateur à la liste des participants
$sortie->addParticipant($userConnect); $sortie->addParticipant($userConnect);
$entityManager->flush(); $entityManager->flush();
@@ -170,7 +153,6 @@ class SortieController extends AbstractController
TokenStorageInterface $tokenStorage, TokenStorageInterface $tokenStorage,
LieuRepository $lieuRepository LieuRepository $lieuRepository
): Response { ): Response {
// Récupérer la sortie
$sortie = $entityManager->getRepository(Sortie::class)->find($id); $sortie = $entityManager->getRepository(Sortie::class)->find($id);
if (!$sortie) { if (!$sortie) {
@@ -178,7 +160,6 @@ class SortieController extends AbstractController
return $this->redirectToRoute('home'); return $this->redirectToRoute('home');
} }
// Vérifier si l'utilisateur est l'organisateur
$token = $tokenStorage->getToken(); $token = $tokenStorage->getToken();
$userConnect = $token?->getUser(); $userConnect = $token?->getUser();
@@ -187,12 +168,10 @@ class SortieController extends AbstractController
return $this->redirectToRoute('home'); return $this->redirectToRoute('home');
} }
// Créer le formulaire
$form = $this->createForm(SortieType::class, $sortie); $form = $this->createForm(SortieType::class, $sortie);
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
// Mettre à jour le lieu si modifié
$lieuId = $form->get('lieu')->getData(); $lieuId = $form->get('lieu')->getData();
$lieu = $lieuRepository->find($lieuId); $lieu = $lieuRepository->find($lieuId);
@@ -200,7 +179,6 @@ class SortieController extends AbstractController
$sortie->setLieu($lieu); $sortie->setLieu($lieu);
} }
// Sauvegarder les modifications
$entityManager->flush(); $entityManager->flush();
$this->addFlash('success', 'La sortie a été mise à jour avec succès.'); $this->addFlash('success', 'La sortie a été mise à jour avec succès.');

View File

@@ -16,14 +16,12 @@ class VilleController extends AbstractController
#[Route('/get-lieux/{id}', name: 'get_lieux', methods: ['GET'])] #[Route('/get-lieux/{id}', name: 'get_lieux', methods: ['GET'])]
public function getLieux(string $id, VilleRepository $villeRepository, LieuRepository $lieuRepository): JsonResponse public function getLieux(string $id, VilleRepository $villeRepository, LieuRepository $lieuRepository): JsonResponse
{ {
// Trouve la ville correspondant à l'ID (GUID)
$ville = $villeRepository->find($id); $ville = $villeRepository->find($id);
if (!$ville) { if (!$ville) {
return new JsonResponse(['error' => 'Ville non trouvée'], Response::HTTP_NOT_FOUND); return new JsonResponse(['error' => 'Ville non trouvée'], Response::HTTP_NOT_FOUND);
} }
// Trouve tous les lieux associés à la ville
$lieux = $lieuRepository->findBy(['ville' => $ville]); $lieux = $lieuRepository->findBy(['ville' => $ville]);
$response = []; $response = [];

View File

@@ -26,6 +26,6 @@ class EtatFixtures extends Fixture
$manager->persist($etat); $manager->persist($etat);
} }
$manager->flush(); // Enregistrement en base de données $manager->flush();
} }
} }

View File

@@ -14,7 +14,6 @@ class LieuFixtures extends Fixture implements DependentFixtureInterface
{ {
$faker = Factory::create(); $faker = Factory::create();
// Exemple : Références pour des villes déjà créées dans VilleFixtures
$villes = [ $villes = [
$this->getReference('ville_Paris'), $this->getReference('ville_Paris'),
$this->getReference('ville_Lyon'), $this->getReference('ville_Lyon'),
@@ -22,13 +21,13 @@ class LieuFixtures extends Fixture implements DependentFixtureInterface
]; ];
foreach ($villes as $ville) { foreach ($villes as $ville) {
for ($i = 0; $i < 5; $i++) { // Génère 5 lieux par ville for ($i = 0; $i < 5; $i++) {
$lieu = new Lieu(); $lieu = new Lieu();
$lieu->setNom($faker->streetName()); $lieu->setNom($faker->streetName());
$lieu->setRue($faker->streetAddress()); $lieu->setRue($faker->streetAddress());
$lieu->setLatitude($faker->latitude()); $lieu->setLatitude($faker->latitude());
$lieu->setLongitude($faker->longitude()); $lieu->setLongitude($faker->longitude());
$lieu->setVille($ville); // Associe le lieu à une ville $lieu->setVille($ville);
$manager->persist($lieu); $manager->persist($lieu);
} }
} }
@@ -39,7 +38,7 @@ class LieuFixtures extends Fixture implements DependentFixtureInterface
public function getDependencies(): array public function getDependencies(): array
{ {
return [ return [
VilleFixtures::class, // Assure que VilleFixtures est chargée en premier VilleFixtures::class,
]; ];
} }
} }

View File

@@ -10,7 +10,6 @@ class VilleFixtures extends Fixture
{ {
public function load(ObjectManager $manager): void public function load(ObjectManager $manager): void
{ {
// Tableau associatif contenant les noms des villes et leurs codes postaux
$villes = [ $villes = [
['nom' => 'Paris', 'codePostal' => '75000'], ['nom' => 'Paris', 'codePostal' => '75000'],
['nom' => 'Lyon', 'codePostal' => '69000'], ['nom' => 'Lyon', 'codePostal' => '69000'],
@@ -23,7 +22,6 @@ class VilleFixtures extends Fixture
$ville->setCodePostal($villeData['codePostal']); // Définit le code postal $ville->setCodePostal($villeData['codePostal']); // Définit le code postal
$manager->persist($ville); $manager->persist($ville);
// Ajoute une référence pour les fixtures de lieux
$this->addReference('ville_' . $villeData['nom'], $ville); $this->addReference('ville_' . $villeData['nom'], $ville);
} }

View File

@@ -57,7 +57,7 @@ class SortieType extends AbstractType
->add('ville', EntityType::class, [ ->add('ville', EntityType::class, [
'class' => Ville::class, 'class' => Ville::class,
'choice_label' => 'nom', 'choice_label' => 'nom',
'mapped' => false, // Non lié à l'entité Sortie 'mapped' => false,
'label' => 'Ville', 'label' => 'Ville',
'placeholder' => 'Sélectionnez une ville', 'placeholder' => 'Sélectionnez une ville',
'attr' => ['id' => 'ville-select'], 'attr' => ['id' => 'ville-select'],

View File

@@ -17,7 +17,6 @@
<img src="{{ asset('img/logo.svg') }}" alt="Logo" class="h-10"> <img src="{{ asset('img/logo.svg') }}" alt="Logo" class="h-10">
</div> </div>
</a> </a>
<!-- Liens de navigation -->
<div class="relative"> <div class="relative">
{% if app.user %} {% if app.user %}
<div id="menu-button" class="w-full flex justify-center"> <div id="menu-button" class="w-full flex justify-center">

View File

@@ -13,10 +13,8 @@
<div class="container mx-auto p-6 bg-gray-50 rounded-lg shadow-lg mt-6"> <div class="container mx-auto p-6 bg-gray-50 rounded-lg shadow-lg mt-6">
<h1 class="text-3xl font-bold text-center text-gray-800 mb-8">🎉 Liste des sorties</h1> <h1 class="text-3xl font-bold text-center text-gray-800 mb-8">🎉 Liste des sorties</h1>
<!-- Section de filtre -->
<form method="GET" action="{{ path('home') }}" class="bg-white rounded-lg shadow-md p-4 mb-6"> <form method="GET" action="{{ path('home') }}" class="bg-white rounded-lg shadow-md p-4 mb-6">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<!-- Filtre par nom -->
<div> <div>
<label for="search" class="block text-sm font-medium text-gray-700">🔍 Recherche</label> <label for="search" class="block text-sm font-medium text-gray-700">🔍 Recherche</label>
<input type="text" name="search" id="search" value="{{ app.request.query.get('search', '') }}" <input type="text" name="search" id="search" value="{{ app.request.query.get('search', '') }}"
@@ -24,7 +22,6 @@
placeholder="Nom de la sortie"> placeholder="Nom de la sortie">
</div> </div>
<!-- Filtre par site -->
<div> <div>
<label for="site" class="block text-sm font-medium text-gray-700">📍 Site</label> <label for="site" class="block text-sm font-medium text-gray-700">📍 Site</label>
<select name="site" id="site" class="block w-full mt-2 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"> <select name="site" id="site" class="block w-full mt-2 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
@@ -37,14 +34,12 @@
</select> </select>
</div> </div>
<!-- Filtre par date de début -->
<div> <div>
<label for="start_date" class="block text-sm font-medium text-gray-700">📅 Date de début</label> <label for="start_date" class="block text-sm font-medium text-gray-700">📅 Date de début</label>
<input type="date" name="start_date" id="start_date" value="{{ app.request.query.get('start_date', '') }}" <input type="date" name="start_date" id="start_date" value="{{ app.request.query.get('start_date', '') }}"
class="block w-full mt-2 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"> class="block w-full mt-2 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div> </div>
<!-- Filtre par date de fin -->
<div> <div>
<label for="end_date" class="block text-sm font-medium text-gray-700">📅 Date de fin</label> <label for="end_date" class="block text-sm font-medium text-gray-700">📅 Date de fin</label>
<input type="date" name="end_date" id="end_date" value="{{ app.request.query.get('end_date', '') }}" <input type="date" name="end_date" id="end_date" value="{{ app.request.query.get('end_date', '') }}"
@@ -52,7 +47,6 @@
</div> </div>
</div> </div>
<!-- Cases à cocher -->
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> <div class="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="flex items-center"> <div class="flex items-center">
<input type="checkbox" name="organisateur" id="organisateur" value="1" <input type="checkbox" name="organisateur" id="organisateur" value="1"
@@ -83,7 +77,6 @@
</div> </div>
</div> </div>
<!-- Bouton soumettre -->
<div class="mt-6 flex justify-end"> <div class="mt-6 flex justify-end">
<button type="submit" class="px-6 py-2 bg-blue-500 text-white rounded-md shadow hover:bg-blue-600"> <button type="submit" class="px-6 py-2 bg-blue-500 text-white rounded-md shadow hover:bg-blue-600">
🔎 Filtrer 🔎 Filtrer
@@ -91,7 +84,6 @@
</div> </div>
</form> </form>
<!-- Affichage des sorties -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-6">
<a href="/sortie/creates"> <a href="/sortie/creates">
<div class="bg-white rounded-lg shadow-md p-4 text-center"> <div class="bg-white rounded-lg shadow-md p-4 text-center">

View File

@@ -16,7 +16,6 @@
{{ form_start(form, { 'attr': { 'class': 'space-y-8' } }) }} {{ form_start(form, { 'attr': { 'class': 'space-y-8' } }) }}
<!-- Informations principales -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div> <div>
<label for="nom" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="nom" class="block text-sm font-semibold text-gray-700 mb-2">
@@ -32,7 +31,6 @@
</div> </div>
</div> </div>
<!-- Durée et date limite d'inscription -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div> <div>
<label for="duree" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="duree" class="block text-sm font-semibold text-gray-700 mb-2">
@@ -48,7 +46,6 @@
</div> </div>
</div> </div>
<!-- Nombre maximum et informations complémentaires -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div> <div>
<label for="nbInscriptionsMax" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="nbInscriptionsMax" class="block text-sm font-semibold text-gray-700 mb-2">
@@ -64,7 +61,6 @@
</div> </div>
</div> </div>
<!-- Ville et lieu -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div> <div>
<label for="ville" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="ville" class="block text-sm font-semibold text-gray-700 mb-2">
@@ -80,7 +76,6 @@
</div> </div>
</div> </div>
<!-- Boutons -->
<div class="flex justify-end mt-8 space-x-4"> <div class="flex justify-end mt-8 space-x-4">
<a href="{{ path('sortie_view', { id: sortie.getIdSortie() }) }}" class="px-6 py-3 bg-gray-500 text-white rounded-lg shadow-md hover:bg-gray-600"> <a href="{{ path('sortie_view', { id: sortie.getIdSortie() }) }}" class="px-6 py-3 bg-gray-500 text-white rounded-lg shadow-md hover:bg-gray-600">
❌ Annuler ❌ Annuler

View File

@@ -16,7 +16,6 @@
<h1 class="text-4xl font-bold text-center text-gray-800 mb-8">🎉 Détails de la sortie</h1> <h1 class="text-4xl font-bold text-center text-gray-800 mb-8">🎉 Détails de la sortie</h1>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8"> <div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
<!-- Informations principales -->
<div class="lg:col-span-7 bg-white rounded-lg shadow p-6"> <div class="lg:col-span-7 bg-white rounded-lg shadow p-6">
<h2 class="text-2xl font-bold text-gray-700 border-b pb-4 mb-6">📋 Informations principales</h2> <h2 class="text-2xl font-bold text-gray-700 border-b pb-4 mb-6">📋 Informations principales</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
@@ -46,7 +45,6 @@
</p> </p>
</div> </div>
<!-- Description -->
<div class="bg-gray-100 rounded-lg p-4 mt-6"> <div class="bg-gray-100 rounded-lg p-4 mt-6">
<h3 class="text-xl font-semibold text-gray-700">📝 Description</h3> <h3 class="text-xl font-semibold text-gray-700">📝 Description</h3>
<p class="text-gray-800 text-lg mt-2"> <p class="text-gray-800 text-lg mt-2">
@@ -54,7 +52,6 @@
</p> </p>
</div> </div>
<!-- Participants -->
<div class="bg-gray-100 rounded-lg p-4 mt-6"> <div class="bg-gray-100 rounded-lg p-4 mt-6">
<h3 class="text-xl font-semibold text-gray-700">👥 Participants inscrits</h3> <h3 class="text-xl font-semibold text-gray-700">👥 Participants inscrits</h3>
{% if sortie.participants is not empty %} {% if sortie.participants is not empty %}
@@ -68,7 +65,6 @@
{% endif %} {% endif %}
</div> </div>
<!-- Bouton S'inscrire -->
{% if app.user and sortie.etat.libelle == 'Ouverte' and not sortie.participants.contains(app.user) %} {% if app.user and sortie.etat.libelle == 'Ouverte' and not sortie.participants.contains(app.user) %}
<form action="{{ path('sortie_inscription', { id: sortie.idSortie }) }}" method="post" class="mt-6"> <form action="{{ path('sortie_inscription', { id: sortie.idSortie }) }}" method="post" class="mt-6">
<button type="submit" class="px-6 py-3 bg-green-500 text-white rounded-md shadow hover:bg-green-600"> <button type="submit" class="px-6 py-3 bg-green-500 text-white rounded-md shadow hover:bg-green-600">
@@ -81,7 +77,6 @@
</div> </div>
{% endif %} {% endif %}
<!-- Modifier (visible uniquement pour l'organisateur) -->
{% if app.user and app.user.idParticipant == sortie.organisateur.idParticipant %} {% if app.user and app.user.idParticipant == sortie.organisateur.idParticipant %}
<div class="mt-6 flex justify-end"> <div class="mt-6 flex justify-end">
<a href="{{ path('sortie_edit', { id: sortie.idSortie }) }}" <a href="{{ path('sortie_edit', { id: sortie.idSortie }) }}"
@@ -92,7 +87,6 @@
{% endif %} {% endif %}
</div> </div>
<!-- Carte -->
<div class="lg:col-span-5 bg-white rounded-lg shadow p-6"> <div class="lg:col-span-5 bg-white rounded-lg shadow p-6">
<h2 class="text-2xl font-bold text-gray-700 border-b pb-4 mb-6">📍 Localisation</h2> <h2 class="text-2xl font-bold text-gray-700 border-b pb-4 mb-6">📍 Localisation</h2>
<div id="map" class="w-full h-96 rounded-lg shadow"></div> <div id="map" class="w-full h-96 rounded-lg shadow"></div>