Marvin Conflict
This commit is contained in:
@@ -1,23 +1,30 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
console.log("Script chargé");
|
||||
|
||||
const addLieuButton = document.getElementById("add-lieu-button");
|
||||
const addLieuModal = document.getElementById("add-lieu-modal");
|
||||
const cancelAddLieu = document.getElementById("cancel-add-lieu");
|
||||
const selectLocationButton = document.getElementById("select-location");
|
||||
const addressDisplay = document.getElementById("lieu-details");
|
||||
const saveLieuButton = document.getElementById("save-lieu");
|
||||
const lieuNomInput = document.getElementById("lieu-nom");
|
||||
const lieuNomError = document.getElementById("lieu-nom-error");
|
||||
const villeSelect = document.getElementById("sortie_ville");
|
||||
const lieuSelect = document.getElementById("sortie_lieu");
|
||||
|
||||
let map, marker, selectedAddress;
|
||||
let map, marker, selectedAddress, selectedRue, cityPolygon, cityBounds;
|
||||
|
||||
if (addLieuButton && addLieuModal && cancelAddLieu && selectLocationButton) {
|
||||
// Affiche la modal
|
||||
addLieuButton.addEventListener("click", () => {
|
||||
const villeId = document.getElementById("sortie_ville").value;
|
||||
// Ouvrir la modal
|
||||
document.getElementById("add-lieu-button").addEventListener("click", async () => {
|
||||
const villeId = villeSelect.value;
|
||||
|
||||
if (!villeId) {
|
||||
alert("Veuillez sélectionner une ville avant d'ajouter un lieu.");
|
||||
return;
|
||||
if (!villeId) {
|
||||
alert("Veuillez sélectionner une ville avant d'ajouter un lieu.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Récupérer les limites et le centre de la ville depuis le serveur
|
||||
const response = await fetch(`/get-bounds/${villeId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error("Erreur lors de la récupération des informations de la ville.");
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
addLieuModal.classList.remove("hidden");
|
||||
|
||||
@@ -28,155 +35,135 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
marker = null;
|
||||
}
|
||||
|
||||
// Récupère les limites de la ville
|
||||
fetch(`/get-bounds/${villeId}`)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error("Erreur lors de la récupération des limites de la ville");
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log("Bounds de la ville :", data);
|
||||
// Initialiser la carte
|
||||
map = L.map("map").setView([data.centerLat, data.centerLng], 13);
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
maxZoom: 19,
|
||||
minZoom: 13,
|
||||
}).addTo(map);
|
||||
|
||||
// Initialise la carte
|
||||
map = L.map("map").setView([data.centerLat, data.centerLng], 13);
|
||||
cityBounds = L.latLngBounds(
|
||||
[data.south, data.west],
|
||||
[data.north, data.east]
|
||||
);
|
||||
|
||||
// Ajout du fond de carte
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
maxZoom: 19,
|
||||
}).addTo(map);
|
||||
map.fitBounds(cityBounds);
|
||||
map.setMaxBounds(cityBounds);
|
||||
|
||||
// Limite le déplacement de la carte aux bounds
|
||||
const bounds = L.latLngBounds(
|
||||
[data.south, data.west],
|
||||
[data.north, data.east]
|
||||
);
|
||||
map.setMaxBounds(bounds);
|
||||
map.fitBounds(bounds);
|
||||
|
||||
// Empêche de dézoomer au-delà de la ville
|
||||
map.setMinZoom(map.getZoom());
|
||||
|
||||
// Gestion de la sélection d'un lieu sur la carte
|
||||
map.on("click", async (e) => {
|
||||
const { lat, lng } = e.latlng;
|
||||
|
||||
// Place un marqueur
|
||||
if (marker) {
|
||||
marker.setLatLng([lat, lng]);
|
||||
} else {
|
||||
marker = L.marker([lat, lng]).addTo(map);
|
||||
}
|
||||
|
||||
// Récupère l'adresse à partir des coordonnées
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${lat}&lon=${lng}`
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("Erreur lors de la récupération des informations du lieu.");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("Détails du lieu :", data);
|
||||
|
||||
selectedAddress = data.display_name;
|
||||
|
||||
// Affiche les détails du lieu
|
||||
const addressDetails = `
|
||||
<p><strong>Rue :</strong> ${data.address.road || "Non disponible"}</p>
|
||||
<p><strong>Ville :</strong> ${data.address.city || data.address.town || "Non disponible"}</p>
|
||||
<p><strong>Code postal :</strong> ${data.address.postcode || "Non disponible"}</p>
|
||||
<p><strong>Pays :</strong> ${data.address.country || "Non disponible"}</p>
|
||||
`;
|
||||
addressDisplay.innerHTML = addressDetails;
|
||||
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la récupération des informations :", error);
|
||||
addressDisplay.textContent = "Impossible de récupérer l'adresse.";
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Erreur :", error);
|
||||
});
|
||||
});
|
||||
|
||||
// Ferme la modal sans enregistrer
|
||||
cancelAddLieu.addEventListener("click", () => {
|
||||
addLieuModal.classList.add("hidden");
|
||||
addressDisplay.innerHTML = ""; // Réinitialise les détails de l'adresse
|
||||
});
|
||||
|
||||
// Sélectionne l'emplacement et envoie les données au serveur
|
||||
selectLocationButton.addEventListener("click", () => {
|
||||
if (marker) {
|
||||
const lat = marker.getLatLng().lat;
|
||||
const lng = marker.getLatLng().lng;
|
||||
const villeId = document.getElementById("sortie_ville").value;
|
||||
|
||||
if (!villeId) {
|
||||
alert("Veuillez sélectionner une ville avant d'ajouter un lieu.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedAddress) {
|
||||
alert("Veuillez sélectionner un emplacement sur la carte.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Demande le nom du lieu à l'utilisateur
|
||||
const nom = prompt("Nom du lieu ?");
|
||||
if (!nom) {
|
||||
alert("Le nom du lieu est obligatoire.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Envoie les données au serveur
|
||||
fetch("/lieu/set", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
// Ajouter le polygone de la ville si disponible
|
||||
if (data.polygon_geojson) {
|
||||
cityPolygon = L.geoJSON(data.polygon_geojson, {
|
||||
style: {
|
||||
color: "blue",
|
||||
weight: 2,
|
||||
fillOpacity: 0.3,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nom,
|
||||
rue: selectedAddress,
|
||||
latitude: lat,
|
||||
longitude: lng,
|
||||
villeId,
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error("Erreur lors de l'ajout du lieu.");
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
console.log("Lieu ajouté :", data);
|
||||
|
||||
// Ajoute le lieu à la liste déroulante
|
||||
const lieuSelect = document.getElementById("sortie_lieu");
|
||||
const option = document.createElement("option");
|
||||
option.value = data.id;
|
||||
option.textContent = data.nom;
|
||||
lieuSelect.appendChild(option);
|
||||
|
||||
// Sélectionne automatiquement le nouveau lieu
|
||||
lieuSelect.value = data.id;
|
||||
|
||||
// Ferme la modal
|
||||
addLieuModal.classList.add("hidden");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Erreur :", error);
|
||||
});
|
||||
} else {
|
||||
alert("Veuillez sélectionner un emplacement sur la carte.");
|
||||
}).addTo(map);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error("Éléments requis pour ajouter un lieu introuvables.");
|
||||
}
|
||||
|
||||
// Gérer les clics sur la carte
|
||||
map.on("click", async (e) => {
|
||||
const { lat, lng } = e.latlng;
|
||||
|
||||
if (!cityBounds.contains([lat, lng])) {
|
||||
alert("Vous ne pouvez pas sélectionner un emplacement en dehors de la ville.");
|
||||
return;
|
||||
}
|
||||
|
||||
selectedAddress = { lat, lng };
|
||||
|
||||
if (marker) {
|
||||
marker.setLatLng([lat, lng]);
|
||||
} else {
|
||||
marker = L.marker([lat, lng]).addTo(map);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${lat}&lon=${lng}`
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("Erreur lors de la récupération des informations de l'adresse.");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
selectedRue = data.address.road || "Rue inconnue";
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la recherche inversée :", error);
|
||||
selectedRue = null;
|
||||
alert("Impossible de récupérer l'adresse à partir de ces coordonnées.");
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Erreur :", error);
|
||||
alert("Impossible de récupérer les données de la ville.");
|
||||
}
|
||||
});
|
||||
|
||||
// Fermer la modal
|
||||
cancelAddLieu.addEventListener("click", () => {
|
||||
addLieuModal.classList.add("hidden");
|
||||
lieuNomInput.value = "";
|
||||
lieuNomError.textContent = "";
|
||||
lieuNomError.classList.add("hidden");
|
||||
});
|
||||
|
||||
// Enregistrer le lieu
|
||||
saveLieuButton.addEventListener("click", () => {
|
||||
const nom = lieuNomInput.value.trim();
|
||||
|
||||
if (!nom) {
|
||||
lieuNomError.textContent = "Le nom est obligatoire.";
|
||||
lieuNomError.classList.remove("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedAddress || !selectedRue) {
|
||||
alert("Veuillez sélectionner un emplacement valide sur la carte.");
|
||||
return;
|
||||
}
|
||||
|
||||
const villeId = villeSelect.value;
|
||||
if (!villeId) {
|
||||
alert("Veuillez sélectionner une ville.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Réinitialiser les erreurs
|
||||
lieuNomError.textContent = "";
|
||||
lieuNomError.classList.add("hidden");
|
||||
|
||||
// Envoyer les données au serveur
|
||||
fetch("/lieu/set", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nom,
|
||||
rue: selectedRue,
|
||||
latitude: selectedAddress.lat,
|
||||
longitude: selectedAddress.lng,
|
||||
villeId,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json().then((data) => ({ status: response.status, data })))
|
||||
.then(({ status, data }) => {
|
||||
if (status >= 200 && status < 300) {
|
||||
// Rafraîchir la page après l'ajout réussi
|
||||
location.reload();
|
||||
} else {
|
||||
if (data.error.includes("nom")) {
|
||||
lieuNomError.textContent = data.error;
|
||||
lieuNomError.classList.remove("hidden");
|
||||
} else {
|
||||
alert(data.error || "Une erreur est survenue.");
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Erreur :", error);
|
||||
alert("Une erreur inattendue s'est produite.");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,6 +34,14 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const option = document.createElement('option');
|
||||
option.value = lieu.id; // Utiliser l'ID pour la soumission
|
||||
option.textContent = lieu.nom; // Texte visible dans le menu
|
||||
|
||||
option.dataset.details = JSON.stringify({
|
||||
rue: lieu.rue,
|
||||
codePostal: lieu.codePostal,
|
||||
latitude: lieu.latitude,
|
||||
longitude: lieu.longitude
|
||||
});
|
||||
|
||||
lieuSelect.appendChild(option);
|
||||
});
|
||||
lieuSelect.disabled = false; // Activer le menu des lieux
|
||||
|
||||
@@ -30,17 +30,22 @@ class LieuController extends AbstractController
|
||||
): JsonResponse {
|
||||
$data = json_decode($request->getContent(), true);
|
||||
|
||||
|
||||
if (!isset($data['nom'], $data['rue'], $data['latitude'], $data['longitude'], $data['villeId'])) {
|
||||
return new JsonResponse(['error' => 'Données manquantes'], Response::HTTP_BAD_REQUEST);
|
||||
if (!isset($data['nom'], $data['latitude'], $data['longitude'], $data['villeId'])) {
|
||||
return new JsonResponse(['error' => 'Données manquantes.'], Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
|
||||
$ville = $villeRepository->find($data['villeId']);
|
||||
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);
|
||||
}
|
||||
|
||||
$existingLieuByName = $entityManager->getRepository(Lieu::class)->findOneBy([
|
||||
'nom' => $data['nom'],
|
||||
'ville' => $ville,
|
||||
]);
|
||||
if ($existingLieuByName) {
|
||||
return new JsonResponse(['error' => "Un lieu avec le nom '{$data['nom']}' existe déjà."], Response::HTTP_CONFLICT);
|
||||
}
|
||||
|
||||
$lieu = new Lieu();
|
||||
$lieu->setNom($data['nom']);
|
||||
@@ -49,20 +54,16 @@ class LieuController extends AbstractController
|
||||
$lieu->setLongitude($data['longitude']);
|
||||
$lieu->setVille($ville);
|
||||
|
||||
|
||||
$entityManager->persist($lieu);
|
||||
$entityManager->flush();
|
||||
|
||||
|
||||
return new JsonResponse([
|
||||
'id' => $lieu->getIdLieu(),
|
||||
'nom' => $lieu->getNom(),
|
||||
'rue' => $lieu->getRue(),
|
||||
'latitude' => $lieu->getLatitude(),
|
||||
'longitude' => $lieu->getLongitude(),
|
||||
], Response::HTTP_CREATED);
|
||||
}
|
||||
|
||||
|
||||
#[Route('/get-bounds/{villeId}', name: 'get_bounds', methods: ['GET'])]
|
||||
public function getBounds(VilleRepository $villeRepository, string $villeId): JsonResponse
|
||||
{
|
||||
|
||||
@@ -8,23 +8,40 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class MainController extends AbstractController
|
||||
{
|
||||
#[Route('/', name: 'home')]
|
||||
public function index(TokenStorageInterface $tokenStorage, SortieRepository $sortieRepository, SiteRepository $siteRepository): Response
|
||||
{
|
||||
public function index(
|
||||
TokenStorageInterface $tokenStorage,
|
||||
SortieRepository $sortieRepository,
|
||||
Request $request
|
||||
): Response {
|
||||
$token = $tokenStorage->getToken();
|
||||
$userConnect = $token?->getUser();
|
||||
$sorties = $sortieRepository->findAll();
|
||||
$sites = $siteRepository->findAll();
|
||||
|
||||
// Récupérer les paramètres de filtre
|
||||
$search = $request->query->get('search', '');
|
||||
$siteId = $request->query->get('site', '');
|
||||
$startDate = $request->query->get('start_date', '');
|
||||
$endDate = $request->query->get('end_date', '');
|
||||
$organisateur = $request->query->get('organisateur', false);
|
||||
$inscrit = $request->query->get('inscrit', false);
|
||||
$nonInscrit = $request->query->get('non_inscrit', false);
|
||||
$passees = $request->query->get('passees', false);
|
||||
|
||||
$sorties = $sortieRepository->findWithFilters($search, $siteId, $startDate, $endDate, $organisateur, $inscrit, $nonInscrit, $passees, $userConnect);
|
||||
|
||||
return $this->render('main/index.html.twig', [
|
||||
'sites' => $sites,
|
||||
'profile' => $userConnect,
|
||||
'sorties' => $sorties,
|
||||
'sites' => $sortieRepository->findAllSites(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[Route('/inscription', name: 'inscription')]
|
||||
public function inscription(TokenStorageInterface $tokenStorage): Response
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ class SortieController extends AbstractController
|
||||
TokenStorageInterface $tokenStorage,
|
||||
LieuRepository $lieuRepository,
|
||||
ParticipantRepository $participantRepository,
|
||||
EtatRepository $etatRepository // Ajout du repository pour les états
|
||||
EtatRepository $etatRepository
|
||||
): Response {
|
||||
$sortie = new Sortie();
|
||||
|
||||
@@ -63,7 +63,6 @@ class SortieController extends AbstractController
|
||||
}
|
||||
|
||||
$sortie->setSite($participant->getSite());
|
||||
$sortie->setParticipant($participant);
|
||||
|
||||
// Récupérer l'état "en création" avec l'ID donné
|
||||
$etat = $etatRepository->find('019349ba-38ca-7a39-93c3-62f046671525');
|
||||
@@ -75,6 +74,9 @@ class SortieController extends AbstractController
|
||||
// Associer l'état à la sortie
|
||||
$sortie->setEtat($etat);
|
||||
|
||||
// Associer l'organisateur à la sortie
|
||||
$sortie->setOrganisateur($participant);
|
||||
|
||||
// Sauvegarder la sortie
|
||||
$entityManager->persist($sortie);
|
||||
$entityManager->flush();
|
||||
@@ -89,4 +91,128 @@ class SortieController extends AbstractController
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/view/{id}', name: 'view', methods: ['GET'])]
|
||||
public function view(string $id, EntityManagerInterface $entityManager, TokenStorageInterface $tokenStorage): Response
|
||||
{
|
||||
// Récupérer l'utilisateur connecté
|
||||
$token = $tokenStorage->getToken();
|
||||
$userConnect = $token?->getUser();
|
||||
|
||||
$sortie = $entityManager->getRepository(Sortie::class)->find($id);
|
||||
|
||||
if (!$sortie) {
|
||||
$this->addFlash('error', 'La sortie demandée n\'existe pas.');
|
||||
return $this->redirectToRoute('home');
|
||||
}
|
||||
|
||||
// Récupérer le profil de l'utilisateur connecté
|
||||
$profile = $this->getUser();
|
||||
|
||||
return $this->render('sortie/view.html.twig', [
|
||||
'sortie' => $sortie,
|
||||
'profile' => $userConnect,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/inscription/{id}', name: 'inscription', methods: ['POST'])]
|
||||
public function inscription(string $id, EntityManagerInterface $entityManager, TokenStorageInterface $tokenStorage): Response
|
||||
{
|
||||
// Récupérer l'utilisateur connecté
|
||||
$token = $tokenStorage->getToken();
|
||||
$userConnect = $token?->getUser();
|
||||
|
||||
if (!$userConnect) {
|
||||
$this->addFlash('error', 'Vous devez être connecté pour vous inscrire.');
|
||||
return $this->redirectToRoute('app_login');
|
||||
}
|
||||
|
||||
// Récupérer la sortie
|
||||
$sortie = $entityManager->getRepository(Sortie::class)->find($id);
|
||||
|
||||
if (!$sortie) {
|
||||
$this->addFlash('error', 'La sortie demandée n\'existe pas.');
|
||||
return $this->redirectToRoute('home');
|
||||
}
|
||||
|
||||
// Vérifier que la sortie est "Ouverte"
|
||||
if ($sortie->getEtat()->getLibelle() !== '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]);
|
||||
}
|
||||
|
||||
// Vérifier si l'utilisateur est déjà inscrit
|
||||
if ($sortie->getParticipants()->contains($userConnect)) {
|
||||
$this->addFlash('error', 'Vous êtes déjà inscrit à cette sortie.');
|
||||
return $this->redirectToRoute('sortie_view', ['id' => $id]);
|
||||
}
|
||||
|
||||
// Vérifier le nombre maximum d'inscriptions
|
||||
if ($sortie->getParticipants()->count() >= $sortie->getNbInscriptionsMax()) {
|
||||
$this->addFlash('error', 'Le nombre maximum d\'inscriptions a été atteint pour cette sortie.');
|
||||
return $this->redirectToRoute('sortie_view', ['id' => $id]);
|
||||
}
|
||||
|
||||
// Ajouter l'utilisateur à la liste des participants
|
||||
$sortie->addParticipant($userConnect);
|
||||
$entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'Vous êtes inscrit à la sortie avec succès !');
|
||||
|
||||
return $this->redirectToRoute('sortie_view', ['id' => $id]);
|
||||
}
|
||||
|
||||
#[Route('/edit/{id}', name: 'edit', methods: ['GET', 'POST'])]
|
||||
public function edit(
|
||||
string $id,
|
||||
Request $request,
|
||||
EntityManagerInterface $entityManager,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
LieuRepository $lieuRepository
|
||||
): Response {
|
||||
// Récupérer la sortie
|
||||
$sortie = $entityManager->getRepository(Sortie::class)->find($id);
|
||||
|
||||
if (!$sortie) {
|
||||
$this->addFlash('error', 'La sortie demandée n\'existe pas.');
|
||||
return $this->redirectToRoute('home');
|
||||
}
|
||||
|
||||
// Vérifier si l'utilisateur est l'organisateur
|
||||
$token = $tokenStorage->getToken();
|
||||
$userConnect = $token?->getUser();
|
||||
|
||||
if ($userConnect->getIdParticipant() !== $sortie->getOrganisateur()->getIdParticipant()) {
|
||||
$this->addFlash('error', 'Vous n\'avez pas l\'autorisation de modifier cette sortie.');
|
||||
return $this->redirectToRoute('home');
|
||||
}
|
||||
|
||||
// Créer le formulaire
|
||||
$form = $this->createForm(SortieType::class, $sortie);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
// Mettre à jour le lieu si modifié
|
||||
$lieuId = $form->get('lieu')->getData();
|
||||
$lieu = $lieuRepository->find($lieuId);
|
||||
|
||||
if ($lieu) {
|
||||
$sortie->setLieu($lieu);
|
||||
}
|
||||
|
||||
// Sauvegarder les modifications
|
||||
$entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'La sortie a été mise à jour avec succès.');
|
||||
|
||||
return $this->redirectToRoute('sortie_view', ['id' => $sortie->getIdSortie()]);
|
||||
}
|
||||
|
||||
return $this->render('sortie/edit.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'sortie' => $sortie,
|
||||
'profile' => $userConnect,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ class SortieType extends AbstractType
|
||||
'label' => 'Date et heure de début',
|
||||
'widget' => 'single_text',
|
||||
'html5' => true,
|
||||
'input' => 'datetime_immutable',
|
||||
'attr' => ['class' => 'form-control'],
|
||||
])
|
||||
->add('duree', IntegerType::class, [
|
||||
@@ -41,6 +42,7 @@ class SortieType extends AbstractType
|
||||
'label' => "Date limite d'inscription",
|
||||
'widget' => 'single_text',
|
||||
'html5' => true,
|
||||
'input' => 'datetime_immutable',
|
||||
'attr' => ['class' => 'form-control'],
|
||||
])
|
||||
->add('nbInscriptionsMax', IntegerType::class, [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Site;
|
||||
use App\Entity\Sortie;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
@@ -16,6 +17,64 @@ class SortieRepository extends ServiceEntityRepository
|
||||
parent::__construct($registry, Sortie::class);
|
||||
}
|
||||
|
||||
public function findWithFilters($search, $siteId, $startDate, $endDate, $organisateur, $inscrit, $nonInscrit, $passees, $user)
|
||||
{
|
||||
$qb = $this->createQueryBuilder('s');
|
||||
|
||||
if ($search) {
|
||||
$qb->andWhere('s.nom LIKE :search')
|
||||
->setParameter('search', '%' . $search . '%');
|
||||
}
|
||||
|
||||
if ($siteId) {
|
||||
$qb->andWhere('s.site = :siteId')
|
||||
->setParameter('siteId', $siteId);
|
||||
}
|
||||
|
||||
if ($startDate) {
|
||||
$qb->andWhere('s.dateHeureDebut >= :startDate')
|
||||
->setParameter('startDate', new \DateTime($startDate));
|
||||
}
|
||||
|
||||
if ($endDate) {
|
||||
$qb->andWhere('s.dateHeureDebut <= :endDate')
|
||||
->setParameter('endDate', new \DateTime($endDate));
|
||||
}
|
||||
|
||||
if ($organisateur) {
|
||||
$qb->andWhere('s.organisateur = :user')
|
||||
->setParameter('user', $user);
|
||||
}
|
||||
|
||||
if ($inscrit) {
|
||||
$qb->andWhere(':user MEMBER OF s.participants')
|
||||
->setParameter('user', $user);
|
||||
}
|
||||
|
||||
if ($nonInscrit) {
|
||||
$qb->andWhere(':user NOT MEMBER OF s.participants')
|
||||
->setParameter('user', $user);
|
||||
}
|
||||
|
||||
if ($passees) {
|
||||
$qb->andWhere('s.dateHeureDebut < :now')
|
||||
->setParameter('now', new \DateTime());
|
||||
} else {
|
||||
$qb->andWhere('s.dateHeureDebut >= :now')
|
||||
->setParameter('now', new \DateTime());
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findAllSites()
|
||||
{
|
||||
return $this->getEntityManager()
|
||||
->getRepository(Site::class)
|
||||
->findAll();
|
||||
}
|
||||
|
||||
|
||||
// /**
|
||||
// * @return Sortie[] Returns an array of Sortie objects
|
||||
// */
|
||||
|
||||
@@ -10,71 +10,89 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto p-4">
|
||||
<h1 class="text-2xl font-bold mb-6 text-center">Liste des sorties</h1>
|
||||
<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>
|
||||
|
||||
<!-- Section de filtre -->
|
||||
<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">
|
||||
<!-- Filtre par nom -->
|
||||
<div>
|
||||
<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', '') }}"
|
||||
class="block w-full mt-2 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="Nom de la sortie">
|
||||
</div>
|
||||
|
||||
<!-- Filtres -->
|
||||
<form method="GET" action="" class="mb-6 bg-white rounded-lg shadow-md p-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
||||
<!-- Filtre par site -->
|
||||
<div>
|
||||
<label for="site" class="block text-sm font-medium text-gray-700">Site</label>
|
||||
<select id="site" name="site" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm">
|
||||
<option value="">Tous</option>
|
||||
<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">
|
||||
<option value="">Tous les sites</option>
|
||||
{% for site in sites %}
|
||||
<option value="{{ site.idSite }}">{{ site.nom }}</option>
|
||||
<option value="{{ site.idSite }}" {% if site.idSite == app.request.query.get('site') %}selected{% endif %}>
|
||||
{{ site.nom }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Filtre par nom de sortie -->
|
||||
<div>
|
||||
<label for="nomSortie" class="block text-sm font-medium text-gray-700">Nom de la sortie</label>
|
||||
<input type="text" id="nomSortie" name="nomSortie" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm" placeholder="Rechercher...">
|
||||
</div>
|
||||
|
||||
<!-- Filtre par date de début -->
|
||||
<div>
|
||||
<label for="dateDebut" class="block text-sm font-medium text-gray-700">Date début</label>
|
||||
<input type="date" id="dateDebut" name="dateDebut" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm">
|
||||
<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', '') }}"
|
||||
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>
|
||||
|
||||
<!-- Filtre par date de fin -->
|
||||
<div>
|
||||
<label for="dateFin" class="block text-sm font-medium text-gray-700">Date fin</label>
|
||||
<input type="date" id="dateFin" name="dateFin" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm">
|
||||
<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', '') }}"
|
||||
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>
|
||||
|
||||
<!-- Cases à cocher -->
|
||||
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<input type="checkbox" id="organisateur" name="organisateur" class="mr-2">
|
||||
<label for="organisateur" class="text-sm font-medium text-gray-700">Sorties où je suis organisateur</label>
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" name="organisateur" id="organisateur" value="1"
|
||||
{% if app.request.query.get('organisateur') %}checked{% endif %}
|
||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500">
|
||||
<label for="organisateur" class="ml-2 text-sm text-gray-700">Sorties dont je suis l'organisateur/trice</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="inscrit" name="inscrit" class="mr-2">
|
||||
<label for="inscrit" class="text-sm font-medium text-gray-700">Sorties où je suis inscrit</label>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" name="inscrit" id="inscrit" value="1"
|
||||
{% if app.request.query.get('inscrit') %}checked{% endif %}
|
||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500">
|
||||
<label for="inscrit" class="ml-2 text-sm text-gray-700">Sorties auxquelles je suis inscrit/e</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="nonInscrit" name="nonInscrit" class="mr-2">
|
||||
<label for="nonInscrit" class="text-sm font-medium text-gray-700">Sorties où je ne suis pas inscrit</label>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" name="non_inscrit" id="non_inscrit" value="1"
|
||||
{% if app.request.query.get('non_inscrit') %}checked{% endif %}
|
||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500">
|
||||
<label for="non_inscrit" class="ml-2 text-sm text-gray-700">Sorties auxquelles je ne suis pas inscrit/e</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="passe" name="passe" class="mr-2">
|
||||
<label for="passe" class="text-sm font-medium text-gray-700">Sorties passées</label>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" name="passees" id="passees" value="1"
|
||||
{% if app.request.query.get('passees') %}checked{% endif %}
|
||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500">
|
||||
<label for="passees" class="ml-2 text-sm text-gray-700">Sorties passées</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bouton de soumission -->
|
||||
<div class="mt-4 flex justify-end">
|
||||
<button type="submit" class="px-4 py-2 bg-blue-500 text-white rounded-md shadow-sm hover:bg-blue-700">
|
||||
Rechercher
|
||||
<!-- Bouton soumettre -->
|
||||
<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">
|
||||
🔎 Filtrer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 lg:grid-cols-6 gap-6">
|
||||
<!-- Affichage des sorties -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-6">
|
||||
<a href="/sortie/creates">
|
||||
<div class="bg-white rounded-lg shadow-md p-4 text-center">
|
||||
<img src="/img/add.svg" alt="Créer une sortie" class="w-40 h-40 mx-auto mb-4">
|
||||
@@ -83,7 +101,7 @@
|
||||
</a>
|
||||
{% for sortie in sorties %}
|
||||
<div class="bg-white rounded-lg shadow-md p-4">
|
||||
<h2 class="text-lg font-semibold">{{ sortie.nom }}</h2>
|
||||
<h2 class="text-lg font-semibold text-gray-800">{{ sortie.nom }}</h2>
|
||||
<p class="mt-2 text-gray-600">
|
||||
<strong>Date de début :</strong> {{ sortie.dateHeureDebut|date('d/m/Y H:i') }}<br>
|
||||
<strong>Durée :</strong> {{ sortie.duree }} minutes<br>
|
||||
@@ -91,9 +109,11 @@
|
||||
<strong>Infos :</strong> {{ sortie.infosSortie }}
|
||||
</p>
|
||||
<div class="mt-4 flex justify-end">
|
||||
<a href="#" class="text-blue-500 hover:text-blue-700 font-medium">Voir plus</a>
|
||||
<a href="{{ path('sortie_view', { id: sortie.idSortie }) }}" class="text-blue-500 hover:text-blue-700 font-medium">Voir plus</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-gray-600 text-center col-span-6">Aucune sortie ne correspond à vos critères.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,6 +46,10 @@
|
||||
.modern-button svg {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
#add-lieu-modal .bg-white {
|
||||
max-width: 80%;
|
||||
max-height: 90%;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
</head>
|
||||
@@ -58,6 +62,7 @@
|
||||
|
||||
{{ form_start(form, { 'attr': { 'class': 'space-y-6' } }) }}
|
||||
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="space-y-4">
|
||||
<div class="form-group">
|
||||
@@ -137,24 +142,31 @@
|
||||
Annuler
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{ form_rest(form) }}
|
||||
{{ form_end(form, { 'render_rest': false }) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div id="add-lieu-modal" class="fixed inset-0 flex items-center justify-center bg-gray-800 bg-opacity-50 hidden">
|
||||
<div class="bg-white p-6 rounded-lg shadow-lg w-full max-w-4xl aspect-w-16 aspect-h-9">
|
||||
<div class="bg-white p-6 rounded-lg shadow-lg w-full max-w-4xl">
|
||||
<h2 class="text-xl font-bold mb-4">Ajouter un lieu</h2>
|
||||
<div id="map" class="w-full h-full mb-4 rounded-lg"></div>
|
||||
<div id="lieu-details" class="mb-4 text-gray-700"></div>
|
||||
|
||||
<div class="form-group mb-4">
|
||||
<label for="lieu-nom" class="block text-sm font-medium text-gray-700">Nom du lieu</label>
|
||||
<input type="text" id="lieu-nom" class="block w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="Nom du lieu" />
|
||||
<div id="lieu-nom-error" class="text-sm text-red-500 mt-1 hidden"></div>
|
||||
</div>
|
||||
|
||||
<div id="map" class="w-full h-96 mb-4 rounded-lg"></div>
|
||||
|
||||
<div class="flex justify-end space-x-4">
|
||||
<button type="button" id="cancel-add-lieu" class="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-300">Annuler</button>
|
||||
<button type="button" id="select-location" class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-300">Sélectionner</button>
|
||||
<button type="button" id="save-lieu" class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-300">Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% block javascripts %}
|
||||
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
|
||||
<script src="https://unpkg.com/leaflet-geosearch/dist/geosearch.umd.js"></script>
|
||||
|
||||
95
templates/sortie/edit.html.twig
Normal file
95
templates/sortie/edit.html.twig
Normal file
@@ -0,0 +1,95 @@
|
||||
{% extends 'main/base.html.twig' %}
|
||||
|
||||
{% block head %}
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Modifier la sortie</title>
|
||||
{% block stylesheets %}
|
||||
{{ encore_entry_link_tags('app') }}
|
||||
{% endblock %}
|
||||
</head>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto p-8 bg-white rounded-lg shadow-lg mt-8">
|
||||
<h1 class="text-3xl font-extrabold text-center text-gray-800 mb-10">📝 Modifier la sortie</h1>
|
||||
|
||||
{{ form_start(form, { 'attr': { 'class': 'space-y-8' } }) }}
|
||||
|
||||
<!-- Informations principales -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<label for="nom" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
🎉 Nom de la sortie
|
||||
</label>
|
||||
{{ form_row(form.nom, { 'attr': { 'class': 'w-full rounded-md border-gray-300 shadow focus:ring-blue-500 focus:border-blue-500' } }) }}
|
||||
</div>
|
||||
<div>
|
||||
<label for="dateHeureDebut" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
🕒 Date et heure de début
|
||||
</label>
|
||||
{{ form_row(form.dateHeureDebut, { 'attr': { 'class': 'w-full rounded-md border-gray-300 shadow focus:ring-blue-500 focus:border-blue-500' } }) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Durée et date limite d'inscription -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<label for="duree" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
⏳ Durée (minutes)
|
||||
</label>
|
||||
{{ form_row(form.duree, { 'attr': { 'class': 'w-full rounded-md border-gray-300 shadow focus:ring-blue-500 focus:border-blue-500' } }) }}
|
||||
</div>
|
||||
<div>
|
||||
<label for="dateLimiteInscription" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
📆 Date limite d'inscription
|
||||
</label>
|
||||
{{ form_row(form.dateLimiteInscription, { 'attr': { 'class': 'w-full rounded-md border-gray-300 shadow focus:ring-blue-500 focus:border-blue-500' } }) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nombre maximum et informations complémentaires -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<label for="nbInscriptionsMax" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
👥 Nombre maximum d'inscriptions
|
||||
</label>
|
||||
{{ form_row(form.nbInscriptionsMax, { 'attr': { 'class': 'w-full rounded-md border-gray-300 shadow focus:ring-blue-500 focus:border-blue-500' } }) }}
|
||||
</div>
|
||||
<div>
|
||||
<label for="infosSortie" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
📝 Informations complémentaires
|
||||
</label>
|
||||
{{ form_row(form.infosSortie, { 'attr': { 'class': 'w-full rounded-md border-gray-300 shadow focus:ring-blue-500 focus:border-blue-500' } }) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ville et lieu -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<label for="ville" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
🏙️ Ville
|
||||
</label>
|
||||
{{ form_row(form.ville, { 'attr': { 'class': 'w-full rounded-md border-gray-300 shadow focus:ring-blue-500 focus:border-blue-500' } }) }}
|
||||
</div>
|
||||
<div>
|
||||
<label for="lieu" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
📍 Lieu
|
||||
</label>
|
||||
{{ form_row(form.lieu, { 'attr': { 'class': 'w-full rounded-md border-gray-300 shadow focus:ring-blue-500 focus:border-blue-500' } }) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Boutons -->
|
||||
<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">
|
||||
❌ Annuler
|
||||
</a>
|
||||
<button type="submit" class="px-6 py-3 bg-blue-500 text-white rounded-lg shadow-md hover:bg-blue-600">
|
||||
💾 Enregistrer
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
133
templates/sortie/view.html.twig
Normal file
133
templates/sortie/view.html.twig
Normal file
@@ -0,0 +1,133 @@
|
||||
{% extends 'main/base.html.twig' %}
|
||||
|
||||
{% block head %}
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Détails de la sortie</title>
|
||||
{% block stylesheets %}
|
||||
{{ encore_entry_link_tags('app') }}
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
|
||||
{% endblock %}
|
||||
</head>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto p-6 bg-gray-50 rounded-lg shadow-lg mt-6">
|
||||
<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">
|
||||
<!-- Informations principales -->
|
||||
<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>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<p class="text-lg flex items-start">
|
||||
<span class="text-2xl mr-2">🕒</span>
|
||||
<span><strong>Date de début :</strong> {{ sortie.dateHeureDebut|date('d/m/Y H:i') }}</span>
|
||||
</p>
|
||||
<p class="text-lg flex items-start">
|
||||
<span class="text-2xl mr-2">⏳</span>
|
||||
<span><strong>Durée :</strong> {{ sortie.duree ? sortie.duree ~ ' minutes' : 'Non renseignée' }}</span>
|
||||
</p>
|
||||
<p class="text-lg flex items-start">
|
||||
<span class="text-2xl mr-2">📆</span>
|
||||
<span><strong>Date limite :</strong> {{ sortie.dateLimiteInscription|date('d/m/Y H:i') }}</span>
|
||||
</p>
|
||||
<p class="text-lg flex items-start">
|
||||
<span class="text-2xl mr-2">👥</span>
|
||||
<span><strong>Inscriptions max :</strong> {{ sortie.nbInscriptionsMax }}</span>
|
||||
</p>
|
||||
<p class="text-lg flex items-start">
|
||||
<span class="text-2xl mr-2">🎓</span>
|
||||
<span><strong>Organisateur :</strong> {{ sortie.organisateur.prenom }} {{ sortie.organisateur.nom }}</span>
|
||||
</p>
|
||||
<p class="text-lg flex items-start">
|
||||
<span class="text-2xl mr-2">📌</span>
|
||||
<span><strong>État :</strong> {{ sortie.etat.libelle }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="bg-gray-100 rounded-lg p-4 mt-6">
|
||||
<h3 class="text-xl font-semibold text-gray-700">📝 Description</h3>
|
||||
<p class="text-gray-800 text-lg mt-2">
|
||||
{{ sortie.infosSortie ? sortie.infosSortie : 'Aucune description disponible.' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Participants -->
|
||||
<div class="bg-gray-100 rounded-lg p-4 mt-6">
|
||||
<h3 class="text-xl font-semibold text-gray-700">👥 Participants inscrits</h3>
|
||||
{% if sortie.participants is not empty %}
|
||||
<ul class="list-disc pl-6 text-gray-800 mt-2">
|
||||
{% for participant in sortie.participants %}
|
||||
<li>{{ participant.nom }} {{ participant.prenom }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-gray-600">Aucun participant inscrit pour le moment.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Bouton S'inscrire -->
|
||||
{% 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">
|
||||
<button type="submit" class="px-6 py-3 bg-green-500 text-white rounded-md shadow hover:bg-green-600">
|
||||
✅ S'inscrire
|
||||
</button>
|
||||
</form>
|
||||
{% elseif app.user and sortie.participants.contains(app.user) %}
|
||||
<div class="mt-6">
|
||||
<p class="text-green-600 font-bold">✅ Vous êtes déjà inscrit à cette sortie.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Modifier (visible uniquement pour l'organisateur) -->
|
||||
{% if app.user and app.user.idParticipant == sortie.organisateur.idParticipant %}
|
||||
<div class="mt-6 flex justify-end">
|
||||
<a href="{{ path('sortie_edit', { id: sortie.idSortie }) }}"
|
||||
class="px-6 py-3 bg-yellow-500 text-white rounded-md shadow hover:bg-yellow-600">
|
||||
✏️ Modifier la sortie
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Carte -->
|
||||
<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>
|
||||
<div id="map" class="w-full h-96 rounded-lg shadow"></div>
|
||||
<ul class="mt-4 text-gray-700 space-y-2">
|
||||
<li><strong>🏢 Nom :</strong> {{ sortie.lieu.nom }}</li>
|
||||
<li><strong>📍 Rue :</strong> {{ sortie.lieu.rue }}</li>
|
||||
<li><strong>🏙️ Ville :</strong> {{ sortie.lieu.ville.nom }}</li>
|
||||
<li><strong>✉️ Code postal :</strong> {{ sortie.lieu.ville.codePostal }}</li>
|
||||
<li><strong>🌍 Latitude :</strong> {{ sortie.lieu.latitude }}</li>
|
||||
<li><strong>🌍 Longitude :</strong> {{ sortie.lieu.longitude }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center mt-8">
|
||||
<a href="{{ path('home') }}" class="px-6 py-3 bg-blue-500 text-white rounded-md shadow hover:bg-blue-600">
|
||||
🔙 Retour à l'accueil
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block javascripts %}
|
||||
{{ encore_entry_script_tags('app') }}
|
||||
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const map = L.map('map').setView([{{ sortie.lieu.latitude }}, {{ sortie.lieu.longitude }}], 15);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
}).addTo(map);
|
||||
|
||||
L.marker([{{ sortie.lieu.latitude }}, {{ sortie.lieu.longitude }}]).addTo(map)
|
||||
.bindPopup("{{ sortie.lieu.nom }}")
|
||||
.openPopup();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user