8 Commits
Marvin ... main

Author SHA1 Message Date
mepiphana2023
a7c12bf807 des trucs 2024-11-29 14:07:03 +01:00
mepiphana2023
7e94f90e2b des trucs 2024-11-29 14:04:13 +01:00
mepiphana2023
3f4018229c des trucs 2024-11-29 13:44:26 +01:00
mepiphana2023
bd9b98b0d1 des trucs 2024-11-29 13:32:01 +01:00
mepiphana2023
32556512eb des trucs 2024-11-29 13:32:01 +01:00
mepiphana2023
0c6ac2fa20 des trucs 2024-11-29 13:31:58 +01:00
mepiphana2023
921e7bfdea des trucs 2024-11-29 13:31:21 +01:00
Olivier PARPAILLON
5d3b63b343 sortie etat done 2024-11-28 09:44:27 +01:00
32 changed files with 327 additions and 162 deletions

View File

@@ -1,4 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,600;1,400;1,600&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,600;1,400;1,600&display=swap');
@import "button.css";
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;

91
assets/styles/button.css Normal file
View File

@@ -0,0 +1,91 @@
.btnPrimary {
@apply bg-[#2a8d57] text-white font-bold py-2 px-4 border-b-4 rounded transition-all duration-300 ease-in-out;
/* Bordure */
border-color: #1e7a43;
/* Survol */
&:hover {
@apply bg-[#1e7a43] border-[#155d2e];
}
/* Clique (État actif) */
&:active {
@apply bg-[#1b6b38] border-[#155d2e];
}
/* Focus (État focus) */
&:focus {
outline: none;
box-shadow: 0 0 0 4px rgba(42, 141, 87, 0.5);
}
}
.btnSecondary {
@apply bg-[#2f4858] text-white font-bold py-2 px-4 border-b-4 rounded transition-all duration-300 ease-in-out;
/* Bordure */
border-color: #1e3c46;
/* Survol */
&:hover {
@apply bg-[#1e3c46] border-[#16323a];
}
/* Clique (État actif) */
&:active {
@apply bg-[#193d49] border-[#16323a];
}
/* Focus (État focus) */
&:focus {
outline: none;
box-shadow: 0 0 0 4px rgba(47, 72, 88, 0.5);
}
}
.btnThird {
@apply bg-[#c0ab4d] text-white font-bold py-2 px-4 border-b-4 rounded transition-all duration-300 ease-in-out;
/* Bordure */
border-color: #b0a139;
/* Survol */
&:hover {
@apply bg-[#b0a139] border-[#9a8b2b];
}
/* Clique (État actif) */
&:active {
@apply bg-[#9a8b2b] border-[#6f6a1c];
}
/* Focus (État focus) */
&:focus {
outline: none;
box-shadow: 0 0 0 4px rgba(192, 171, 77, 0.5);
}
}
.btnAnnule {
@apply bg-[#d54f45] text-white font-bold py-2 px-4 border-b-4 rounded transition-all duration-300 ease-in-out;
/* Bordure */
border-color: #a73a2d;
/* Survol */
&:hover {
@apply bg-[#a73a2d] border-[#8b2d1e];
}
/* Clique (État actif) */
&:active {
@apply bg-[#90271e] border-[#8b2d1e];
}
/* Focus (État focus) */
&:focus {
outline: none;
box-shadow: 0 0 0 4px rgba(213, 79, 69, 0.5);
}
}

View File

@@ -28,5 +28,12 @@ services:
arguments: arguments:
$targetDirectory: '../public/upload/image/profile/' $targetDirectory: '../public/upload/image/profile/'
App\EventListener\UserProfileListener:
arguments:
- '@security.token_storage'
- '@twig'
tags:
- { name: 'kernel.event_subscriber' }
# add more service definitions when explicit configuration is needed # add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones # please note that last definitions always *replace* previous ones

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -44,16 +44,12 @@ class AdminController extends AbstractController
]); ]);
} }
//Gestion des utilisateurs
#[Route('/admin/user', name: 'app_adminUser')] #[Route('/admin/user', name: 'app_adminUser')]
public function adminUser(TokenStorageInterface $tokenStorage): Response public function adminUser(TokenStorageInterface $tokenStorage): Response
{ {
$token = $tokenStorage->getToken();
$userConnect = $token?->getUser();
$participants = $this->participantRepository->findAll(); $participants = $this->participantRepository->findAll();
$sites = $this->siteRepository->findAll(); $sites = $this->siteRepository->findAll();
return $this->render('admin/user.html.twig', [ return $this->render('admin/user.html.twig', [
'profile' => $userConnect,
'participants' => $participants, 'participants' => $participants,
'sites' => $sites, 'sites' => $sites,
'controller_name' => 'AdminController', 'controller_name' => 'AdminController',
@@ -62,68 +58,56 @@ class AdminController extends AbstractController
#[Route('/admin/user/disable/{id}', name: 'app_adminUserDisable')] #[Route('/admin/user/disable/{id}', name: 'app_adminUserDisable')]
public function disableParticipant(string $id, ParticipantRepository $participantRepository, EntityManagerInterface $entityManager): RedirectResponse public function disableParticipant(string $id, ParticipantRepository $participantRepository, EntityManagerInterface $entityManager): RedirectResponse
{ {
// Récupérer le participant à partir de l'id
$participant = $participantRepository->find($id); $participant = $participantRepository->find($id);
// Vérifier si le participant existe
if (!$participant) { if (!$participant) {
$this->addFlash('error', 'Le participant demandé n\'existe pas.'); $this->addFlash('error', 'Le participant demandé n\'existe pas.');
return $this->redirectToRoute('app_adminUser'); // Redirigez vers une liste ou une autre page return $this->redirectToRoute('app_adminUser');
} }
// Désactiver le participant (par exemple, définir une propriété "isActive" à false)
if ($participant->isActif()){ if ($participant->isActif()){
$participant->setActif(false); $participant->setActif(false);
}else{ }else{
$participant->setActif(true); $participant->setActif(true);
} }
// Sauvegarder la modification en base de données
$entityManager->persist($participant); $entityManager->persist($participant);
$entityManager->flush(); $entityManager->flush();
// Ajouter un message de succès et rediriger
$this->addFlash('success', 'Participant désactivé avec succès.'); $this->addFlash('success', 'Participant désactivé avec succès.');
return $this->redirectToRoute('app_adminUser'); return $this->redirectToRoute('app_adminUser');
} }
#[Route('/admin/user/delete/{id}', name: 'app_adminUserDelete')] #[Route('/admin/user/delete/{id}', name: 'app_adminUserDelete')]
public function deleteUser(string $id, ParticipantRepository $participantRepository, EntityManagerInterface $entityManager): RedirectResponse public function deleteUser(string $id, ParticipantRepository $participantRepository, EntityManagerInterface $entityManager): RedirectResponse
{ {
// Récupérer l'utilisateur ou le participant à partir de l'id
$participant = $participantRepository->find($id); $participant = $participantRepository->find($id);
// Vérifier si l'utilisateur existe
if (!$participant) { if (!$participant) {
$this->addFlash('error', 'L\'utilisateur demandé n\'existe pas.'); $this->addFlash('error', 'L\'utilisateur demandé n\'existe pas.');
return $this->redirectToRoute('app_adminUser'); // Redirigez vers une liste ou une autre page return $this->redirectToRoute('app_adminUser'); // Redirigez vers une liste ou une autre page
} }
// Supprimer l'utilisateur
$entityManager->remove($participant); $entityManager->remove($participant);
$entityManager->flush(); $entityManager->flush();
// Ajouter un message de succès et rediriger
$this->addFlash('success', 'Utilisateur supprimé avec succès.'); $this->addFlash('success', 'Utilisateur supprimé avec succès.');
return $this->redirectToRoute('app_adminUser'); // Redirigez vers la liste des utilisateurs return $this->redirectToRoute('app_adminUser');
} }
#[Route('/admin/user/add', name: 'app_adminUserAdd', methods: ['POST'])] #[Route('/admin/user/add', name: 'app_adminUserAdd', methods: ['POST'])]
public function userAdd(Request $request, EntityManagerInterface $entityManager, MailerInterface $mailer, UrlGeneratorInterface $urlGenerator): Response public function userAdd(Request $request, EntityManagerInterface $entityManager, MailerInterface $mailer, UrlGeneratorInterface $urlGenerator): Response
{ {
try { try {
// Récupérer les données envoyées par le formulaire
$nom = $request->request->get('nom'); $nom = $request->request->get('nom');
$prenom = $request->request->get('prenom'); $prenom = $request->request->get('prenom');
$pseudo = $request->request->get('pseudo'); $pseudo = $request->request->get('pseudo');
$telephone = $request->request->get('phone'); $telephone = $request->request->get('phone');
$mail = $request->request->get('mail'); $mail = $request->request->get('mail');
// Vérifier que les champs ne sont pas vides
if (!$mail || !$pseudo) { if (!$mail || !$pseudo) {
$this->addFlash('error', 'Tous les champs sont requis.'); $this->addFlash('error', 'Tous les champs sont requis.');
return $this->redirectToRoute('app_adminUser'); return $this->redirectToRoute('app_adminUser');
} }
// Vérifier que le mail est valide avec une regex
if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) { if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) {
$this->addFlash('error', 'L\'adresse e-mail n\'est pas valide.'); $this->addFlash('error', 'L\'adresse e-mail n\'est pas valide.');
return $this->redirectToRoute('app_adminUser'); return $this->redirectToRoute('app_adminUser');
@@ -143,10 +127,8 @@ class AdminController extends AbstractController
$entityManager->persist($participant); $entityManager->persist($participant);
// Générer un token unique
$token = bin2hex(random_bytes(32)); $token = bin2hex(random_bytes(32));
// Enregistrer le token dans la base de données
$passwordResetToken = new PasswordResetToken(); $passwordResetToken = new PasswordResetToken();
$passwordResetToken->setToken($token) $passwordResetToken->setToken($token)
->setEmail($participant->getEmail()) ->setEmail($participant->getEmail())
@@ -155,14 +137,12 @@ class AdminController extends AbstractController
$entityManager->persist($passwordResetToken); $entityManager->persist($passwordResetToken);
$entityManager->flush(); $entityManager->flush();
// Générer un lien de réinitialisation
$resetLink = $urlGenerator->generate( $resetLink = $urlGenerator->generate(
'app_password_reset', 'app_password_reset',
['token' => $token], ['token' => $token],
UrlGeneratorInterface::ABSOLUTE_URL UrlGeneratorInterface::ABSOLUTE_URL
); );
// Envoyer un email de notification
$email = (new Email()) $email = (new Email())
->from('contact@sortir.com') ->from('contact@sortir.com')
->to($participant->getEmail()) ->to($participant->getEmail())
@@ -208,10 +188,8 @@ class AdminController extends AbstractController
$participant->setPending(true); $participant->setPending(true);
$em->persist($participant); $em->persist($participant);
// Générer un token unique
$token = bin2hex(random_bytes(32)); $token = bin2hex(random_bytes(32));
// Enregistrer le token dans la base de données
$passwordResetToken = new PasswordResetToken(); $passwordResetToken = new PasswordResetToken();
$passwordResetToken->setToken($token) $passwordResetToken->setToken($token)
->setEmail($participant->getEmail()) ->setEmail($participant->getEmail())
@@ -219,14 +197,12 @@ class AdminController extends AbstractController
$em->persist($passwordResetToken); $em->persist($passwordResetToken);
// Générer un lien de réinitialisation
$resetLink = $urlGenerator->generate( $resetLink = $urlGenerator->generate(
'app_password_reset', 'app_password_reset',
['token' => $token], ['token' => $token],
UrlGeneratorInterface::ABSOLUTE_URL UrlGeneratorInterface::ABSOLUTE_URL
); );
// Envoyer un email de notification
$email = (new Email()) $email = (new Email())
->from('contact@sortir.com') ->from('contact@sortir.com')
->to($participant->getEmail()) ->to($participant->getEmail())
@@ -255,7 +231,6 @@ class AdminController extends AbstractController
// $participants = $participantRepository->findAll(); // $participants = $participantRepository->findAll();
$userList = $request->request->all('userList'); $userList = $request->request->all('userList');
if (empty($userList)) { if (empty($userList)) {
// Aucun utilisateur sélectionné
$this->addFlash('warning', 'Veuillez sélectionner au moins un utilisateur.'); $this->addFlash('warning', 'Veuillez sélectionner au moins un utilisateur.');
return $this->redirectToRoute('app_adminUser'); return $this->redirectToRoute('app_adminUser');
} }
@@ -282,7 +257,6 @@ class AdminController extends AbstractController
return $response; return $response;
} }
//Gestion des sites
#[Route('/admin/site', name: 'app_adminSite')] #[Route('/admin/site', name: 'app_adminSite')]
public function adminSite(SiteRepository $siteRepository, TokenStorageInterface $tokenStorage): Response public function adminSite(SiteRepository $siteRepository, TokenStorageInterface $tokenStorage): Response
{ {
@@ -298,19 +272,15 @@ class AdminController extends AbstractController
public function adminSiteAdd(Request $request, EntityManagerInterface $entityManager): Response public function adminSiteAdd(Request $request, EntityManagerInterface $entityManager): Response
{ {
try { try {
// Récupérer les données envoyées par le formulaire
$nom = $request->request->get('nom'); $nom = $request->request->get('nom');
// Vérifier que les champs ne sont pas vides
if (!$nom) { if (!$nom) {
return new Response('Tous les champs sont requis.', Response::HTTP_BAD_REQUEST); return new Response('Tous les champs sont requis.', Response::HTTP_BAD_REQUEST);
} }
// Créer une nouvelle entité City et définir ses propriétés
$site = new Site(); $site = new Site();
$site->setNom($nom); $site->setNom($nom);
// Enregistrer la ville dans la base de données
$entityManager->persist($site); $entityManager->persist($site);
$entityManager->flush(); $entityManager->flush();
$this->addFlash('success', "Site ajouté !"); $this->addFlash('success', "Site ajouté !");
@@ -323,17 +293,13 @@ class AdminController extends AbstractController
#[Route('/admin/site/delete/{id}', name: 'app_adminSiteDelete')] #[Route('/admin/site/delete/{id}', name: 'app_adminSiteDelete')]
public function adminSiteDelete(string $id, SiteRepository $siteRepository, EntityManagerInterface $entityManager): RedirectResponse public function adminSiteDelete(string $id, SiteRepository $siteRepository, EntityManagerInterface $entityManager): RedirectResponse
{ {
// Récupérer la ville à supprimer
$site = $siteRepository->find($id); $site = $siteRepository->find($id);
// Vérifier si la ville existe
if (!$site) { if (!$site) {
// Si la ville n'existe pas, rediriger avec un message d'erreur
$this->addFlash('error', 'Le site demandée n\'existe pas.'); $this->addFlash('error', 'Le site demandée n\'existe pas.');
return $this->redirectToRoute('app_adminSite'); // Rediriger vers la liste des villes return $this->redirectToRoute('app_adminSite'); // Rediriger vers la liste des villes
} }
// Supprimer la ville
$entityManager->remove($site); $entityManager->remove($site);
$entityManager->flush(); $entityManager->flush();

View File

@@ -20,10 +20,8 @@ class LoginController extends AbstractController
return $this->redirectToRoute('home'); // Redirection pour utilisateurs connectés return $this->redirectToRoute('home'); // Redirection pour utilisateurs connectés
} }
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError(); $error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername(); $lastUsername = $authenticationUtils->getLastUsername();
$user = new Participant(); $user = new Participant();
$formUser = $this->createForm(RegistrationFormType::class, $user); $formUser = $this->createForm(RegistrationFormType::class, $user);
@@ -38,7 +36,6 @@ class LoginController extends AbstractController
#[Route(path: '/logout', name: 'app_logout')] #[Route(path: '/logout', name: 'app_logout')]
public function logout(Security $security): ?Response public function logout(Security $security): ?Response
{ {
// throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
return $security->logout(false); return $security->logout(false);
} }
} }

View File

@@ -17,10 +17,7 @@ class MainController extends AbstractController
#[Route('/', name: 'home')] #[Route('/', name: 'home')]
public function home(SortieRepository $sortieRepository, TokenStorageInterface $tokenStorage,): Response public function home(SortieRepository $sortieRepository, TokenStorageInterface $tokenStorage,): Response
{ {
// Récupérer les 5 dernières sorties
$latestSorties = $sortieRepository->findBy([], ['dateHeureDebut' => 'DESC'], 5); $latestSorties = $sortieRepository->findBy([], ['dateHeureDebut' => 'DESC'], 5);
$token = $tokenStorage->getToken();
$userConnect = $token?->getUser();
$dateLimit = new \DateTime(); $dateLimit = new \DateTime();
$dateLimit->modify('-30 days'); // Date limite = il y a 30 jours $dateLimit->modify('-30 days'); // Date limite = il y a 30 jours
@@ -35,7 +32,6 @@ class MainController extends AbstractController
return $this->render('main/home.html.twig', [ return $this->render('main/home.html.twig', [
'lastSorties' => $latestSorties, 'lastSorties' => $latestSorties,
'profile' => $userConnect,
'pastEvents' => $pastEvents, 'pastEvents' => $pastEvents,
]); ]);
} }

View File

@@ -30,16 +30,13 @@ class PasswordResetController extends AbstractController
try{ try{
$email = $request->request->get('email'); $email = $request->request->get('email');
// Vérifier si l'email est valide
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->addFlash('error', 'Adresse e-mail invalide.'); $this->addFlash('error', 'Adresse e-mail invalide.');
return $this->redirectToRoute('app_login'); return $this->redirectToRoute('app_login');
} }
// Générer un token unique
$token = bin2hex(random_bytes(32)); $token = bin2hex(random_bytes(32));
// Enregistrer le token dans la base de données
$passwordResetToken = new PasswordResetToken(); $passwordResetToken = new PasswordResetToken();
$passwordResetToken->setToken($token) $passwordResetToken->setToken($token)
->setEmail($email) ->setEmail($email)
@@ -55,7 +52,6 @@ class PasswordResetController extends AbstractController
UrlGeneratorInterface::ABSOLUTE_URL UrlGeneratorInterface::ABSOLUTE_URL
); );
// Envoyer l'email avec le lien
$email = (new Email()) $email = (new Email())
->from('contact@sortir.com') ->from('contact@sortir.com')
->to($email) ->to($email)
@@ -79,7 +75,6 @@ class PasswordResetController extends AbstractController
Request $request, Request $request,
EntityManagerInterface $entityManager, EntityManagerInterface $entityManager,
): Response { ): Response {
// Vérifier si le token existe
$repository = $entityManager->getRepository(PasswordResetToken::class); $repository = $entityManager->getRepository(PasswordResetToken::class);
$passwordResetToken = $repository->findOneBy(['token' => $token]); $passwordResetToken = $repository->findOneBy(['token' => $token]);
@@ -88,14 +83,11 @@ class PasswordResetController extends AbstractController
return $this->redirectToRoute('app_login'); return $this->redirectToRoute('app_login');
} }
// Si la requête est POST, traiter le nouveau mot de passe
if ($request->isMethod('POST')) { if ($request->isMethod('POST')) {
$newPassword = $request->request->get('password'); $newPassword = $request->request->get('password');
$newPasswordConfirm = $request->request->get('passwordConfirm'); $newPasswordConfirm = $request->request->get('passwordConfirm');
// Vérifier si les mots de passe sont identiques
if ($newPassword !== $newPasswordConfirm) { if ($newPassword !== $newPasswordConfirm) {
// Les mots de passe ne correspondent pas
$this->addFlash('error', 'Les mots de passe ne correspondent pas.'); $this->addFlash('error', 'Les mots de passe ne correspondent pas.');
return $this->redirectToRoute('app_password_reset'); // Vous pouvez rediriger vers la page de réinitialisation return $this->redirectToRoute('app_password_reset'); // Vous pouvez rediriger vers la page de réinitialisation
} }
@@ -105,7 +97,6 @@ class PasswordResetController extends AbstractController
return $this->redirectToRoute('app_password_reset', ['token' => $token]); return $this->redirectToRoute('app_password_reset', ['token' => $token]);
} }
// Récupérer l'utilisateur et mettre à jour le mot de passe
$user = $entityManager->getRepository(Participant::class)->findOneBy(['email' => $passwordResetToken->getEmail()]); $user = $entityManager->getRepository(Participant::class)->findOneBy(['email' => $passwordResetToken->getEmail()]);
$user->setPassword(password_hash($newPassword, PASSWORD_BCRYPT)); $user->setPassword(password_hash($newPassword, PASSWORD_BCRYPT));

View File

@@ -27,7 +27,6 @@ class RegistrationController extends AbstractController
/** @var string $plainPassword */ /** @var string $plainPassword */
$plainPassword = $form->get('plainPassword')->getData(); $plainPassword = $form->get('plainPassword')->getData();
// encode the plain password
$user->setPassword($userPasswordHasher->hashPassword($user, $plainPassword)); $user->setPassword($userPasswordHasher->hashPassword($user, $plainPassword));
$user->setRoles(['ROLE_USER']); $user->setRoles(['ROLE_USER']);
$user->setActif(false); $user->setActif(false);
@@ -47,7 +46,6 @@ class RegistrationController extends AbstractController
return $this->redirectToRoute('app_register'); return $this->redirectToRoute('app_register');
} }
// do anything else you need here, like send an email
return $this->render('main/inscription.html.twig', ['fullName' => $fullName]); return $this->render('main/inscription.html.twig', ['fullName' => $fullName]);
// return $security->login($user, 'form_login', 'main'); // return $security->login($user, 'form_login', 'main');

View File

@@ -9,6 +9,7 @@ use App\Repository\EtatRepository;
use App\Repository\LieuRepository; use App\Repository\LieuRepository;
use App\Repository\ParticipantRepository; use App\Repository\ParticipantRepository;
use App\Repository\SortieRepository; use App\Repository\SortieRepository;
use App\Service\FileUploader;
use DateTime; use DateTime;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use OpenAI\Factory; use OpenAI\Factory;
@@ -23,7 +24,7 @@ use OpenAI\Client;
#[Route('/sortie', name: 'sortie_')] #[Route('/sortie', name: 'sortie_')]
class SortieController extends AbstractController class SortieController extends AbstractController
{ {
#[Route('/sortie/liste', name: 'list', methods: ['GET'])] #[Route('/liste', name: 'list', methods: ['GET'])]
public function index( public function index(
TokenStorageInterface $tokenStorage, TokenStorageInterface $tokenStorage,
SortieRepository $sortieRepository, SortieRepository $sortieRepository,
@@ -32,7 +33,6 @@ class SortieController extends AbstractController
$token = $tokenStorage->getToken(); $token = $tokenStorage->getToken();
$userConnect = $token?->getUser(); $userConnect = $token?->getUser();
// Récupérer les paramètres de filtre
$search = $request->query->get('search', ''); $search = $request->query->get('search', '');
$siteId = $request->query->get('site', ''); $siteId = $request->query->get('site', '');
$startDate = $request->query->get('start_date', ''); $startDate = $request->query->get('start_date', '');
@@ -42,9 +42,7 @@ class SortieController extends AbstractController
$nonInscrit = $request->query->get('non_inscrit', false); $nonInscrit = $request->query->get('non_inscrit', false);
$passees = $request->query->get('passees', false); $passees = $request->query->get('passees', false);
// Recherche par nom de sortie
$sorties = $sortieRepository->findWithFilters($search, $siteId, $startDate, $endDate, $organisateur, $inscrit, $nonInscrit, $passees, $userConnect); $sorties = $sortieRepository->findWithFilters($search, $siteId, $startDate, $endDate, $organisateur, $inscrit, $nonInscrit, $passees, $userConnect);
return $this->render('sortie/list.html.twig', [ return $this->render('sortie/list.html.twig', [
'profile' => $userConnect, 'profile' => $userConnect,
'sorties' => $sorties, 'sorties' => $sorties,
@@ -62,7 +60,6 @@ class SortieController extends AbstractController
ParticipantRepository $participantRepository, ParticipantRepository $participantRepository,
EtatRepository $etatRepository EtatRepository $etatRepository
): Response { ): Response {
// Vérifier si l'utilisateur est connecté
$userConnect = $tokenStorage->getToken()?->getUser(); $userConnect = $tokenStorage->getToken()?->getUser();
if (!$userConnect) { if (!$userConnect) {
$this->addFlash('error', 'Vous devez être connecté pour créer une sortie.'); $this->addFlash('error', 'Vous devez être connecté pour créer une sortie.');
@@ -81,20 +78,17 @@ class SortieController extends AbstractController
return $this->redirectToRoute('sortie_create'); return $this->redirectToRoute('sortie_create');
} }
// Vérifier le participant (organisateur)
$participant = $participantRepository->find($userConnect->getIdParticipant()); $participant = $participantRepository->find($userConnect->getIdParticipant());
if (!$participant) { if (!$participant) {
$this->addFlash('error', 'Impossible de trouver votre profil.'); $this->addFlash('error', 'Impossible de trouver votre profil.');
return $this->redirectToRoute('sortie_create'); return $this->redirectToRoute('sortie_create');
} }
// Gérer l'image (upload)
$imageFile = $form->get('imageFile')->getData(); $imageFile = $form->get('imageFile')->getData();
if ($imageFile) { if ($imageFile) {
$sortie->setImageFile($imageFile); $sortie->setImageFile($imageFile);
} }
// Définir les relations et l'état initial
$etat = $etatRepository->findOneBy(['libelle' => 'Créée']); $etat = $etatRepository->findOneBy(['libelle' => 'Créée']);
if (!$etat) { if (!$etat) {
$this->addFlash('error', 'Erreur interne : état introuvable.'); $this->addFlash('error', 'Erreur interne : état introuvable.');
@@ -110,11 +104,10 @@ class SortieController extends AbstractController
$entityManager->flush(); $entityManager->flush();
$this->addFlash('success', 'La sortie a été créée avec succès.'); $this->addFlash('success', 'La sortie a été créée avec succès.');
return $this->redirectToRoute('home'); return $this->redirectToRoute('sortie_view', ['id'=> $sortie->getIdSortie()]);
} }
return $this->render('sortie/create.html.twig', [ return $this->render('sortie/create.html.twig', [
'profile' => $userConnect,
'form' => $form->createView(), 'form' => $form->createView(),
'sortie' => $sortie, 'sortie' => $sortie,
]); ]);
@@ -124,7 +117,6 @@ class SortieController extends AbstractController
public function view( public function view(
string $id, string $id,
EntityManagerInterface $entityManager, EntityManagerInterface $entityManager,
TokenStorageInterface $tokenStorage
): Response { ): Response {
$sortie = $entityManager->getRepository(Sortie::class)->find($id); $sortie = $entityManager->getRepository(Sortie::class)->find($id);
if (!$sortie) { if (!$sortie) {
@@ -136,9 +128,16 @@ class SortieController extends AbstractController
return $this->redirectToRoute('home'); return $this->redirectToRoute('home');
} }
// Changer l'état si date de début commencé.
if($sortie->getDateLimiteInscription() < new DateTime()){
$sortie->setEtat($entityManager->getRepository(Etat::class)->findOneBy(['libelle' => 'Clôturée']));
}
if ($sortie->getDateHeureDebut() <= (new DateTime())) {
$sortie->setEtat($entityManager->getRepository(Etat::class)->findOneBy(['libelle' => 'En cours']));
}
return $this->render('sortie/view.html.twig', [ return $this->render('sortie/view.html.twig', [
'sortie' => $sortie, 'sortie' => $sortie,
'profile' => $tokenStorage->getToken()?->getUser(),
]); ]);
} }
@@ -232,10 +231,17 @@ class SortieController extends AbstractController
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
// Vérification et mise à jour du lieu
$lieu = $form->get('lieu')->getData(); $lieu = $form->get('lieu')->getData();
if ($lieu) { if ($lieu) {
$sortie->setLieu($lieu); $sortie->setLieu($lieu);
} }
$imageFile = $form->get('imageFile')->getData();
if ($imageFile) {
$sortie->setImageFile($imageFile);
}
$entityManager->flush(); $entityManager->flush();
$this->addFlash('success', 'Les modifications ont été enregistrées.'); $this->addFlash('success', 'Les modifications ont été enregistrées.');
@@ -280,4 +286,22 @@ class SortieController extends AbstractController
return $this->redirectToRoute('home'); return $this->redirectToRoute('home');
} }
#[Route('/confirm/{id}', name: 'confirm', methods: ['POST'])]
public function confirm(string $id, EntityManagerInterface $entityManager, TokenStorageInterface $tokenStorage)
{
$user = $tokenStorage->getToken()?->getUser();
$sortie = $entityManager->getRepository(Sortie::class)->find($id);
if (!$sortie || $sortie->getOrganisateur()->getIdParticipant() !== $user->getIdParticipant()) {
$this->addFlash('error', 'Vous n\'avez pas l\'autorisation de démarrer les inscriptions pour cette sortie.');
return $this->redirectToRoute('sortie_view', ['id' => $id]);
}
$sortie->setEtat($entityManager->getRepository(Etat::class)->findOneBy(['libelle' => 'Ouverte']));
$entityManager->flush();
$this->addFlash('success', 'Les inscriptions sont ouvertes pour cette sortie');
return $this->redirectToRoute('sortie_view', ['id' => $id]);
}
} }

View File

@@ -0,0 +1,40 @@
<?php
namespace App\EventListener;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Twig\Environment;
class UserProfileListener implements EventSubscriberInterface
{
private $tokenStorage;
private $twig;
public function __construct(TokenStorageInterface $tokenStorage, Environment $twig)
{
$this->tokenStorage = $tokenStorage;
$this->twig = $twig;
}
public function onKernelRequest(RequestEvent $event)
{
$token = $this->tokenStorage->getToken();
if ($token && $token->getUser()) {
$profile = $token->getUser();
$event->getRequest()->attributes->set('profile', $profile);
$this->twig->addGlobal('profile', $profile);
}
}
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => 'onKernelRequest',
];
}
}

View File

@@ -14,7 +14,7 @@
<!-- Actions: Ajouter --> <!-- Actions: Ajouter -->
<div class="mb-4"> <div class="mb-4">
<!-- Bouton pour ouvrir la modale --> <!-- Bouton pour ouvrir la modale -->
<button id="openModal" class="inline-block bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700"> <button id="openModal" class="btnPrimary">
Ajouter un site Ajouter un site
</button> </button>
</div> </div>

View File

@@ -14,7 +14,7 @@
<div class="flex flex-row mb-4 justify-between"> <div class="flex flex-row mb-4 justify-between">
<button <button
id="openModal" id="openModal"
class="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700 items-end justify-end"> class="btnPrimary">
Ajouter un utilisateur Ajouter un utilisateur
</button> </button>
</div> </div>
@@ -24,10 +24,10 @@
<div class="flex flex-row"> <div class="flex flex-row">
<button <button
id="openModalCsv" type="button" id="openModalCsv" type="button"
class="bg-green-500 text-white mb-4 mr-4 px-4 py-2 rounded hover:bg-green-600"> class="btnSecondary">
Importer CSV Importer CSV
</button> </button>
<button type="submit" class="bg-blue-500 text-white px-4 py-2 mb-4 rounded hover:bg-blue-600"> <button type="submit" class="btnThird">
<a href="{{ path('participant_export') }}">Exporter CSV</a> <a href="{{ path('participant_export') }}">Exporter CSV</a>
</button> </button>
</div> </div>
@@ -177,7 +177,7 @@
</div> </div>
<p>* Champ obligatoire</p> <p>* Champ obligatoire</p>
<div class="flex justify-end"> <div class="flex justify-end">
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700">Ajouter</button> <button type="submit" class="btnPrimary">Ajouter</button>
<button type="button" id="closeModal" class="ml-2 bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-700">Annuler</button> <button type="button" id="closeModal" class="ml-2 bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-700">Annuler</button>
</div> </div>
</form> </form>
@@ -205,7 +205,7 @@
<div class="flex flex-row"> <div class="flex flex-row">
<button <button
type="submit" type="submit"
class="bg-green-500 text-white mr-4 px-4 py-2 rounded hover:bg-green-600"> class="btnSecondary">
Importer CSV Importer CSV
</button> </button>
<button type="button" id="closeModalCsv" class="ml-2 bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-700">Annuler</button> <button type="button" id="closeModalCsv" class="ml-2 bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-700">Annuler</button>

View File

@@ -1,4 +1,5 @@
{% extends 'main/base.html.twig' %} {% extends 'main/base.html.twig' %}
{% block head %} {% block head %}
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
@@ -16,28 +17,29 @@
<form action="{{ path('app_login') }}" method="post"> <form action="{{ path('app_login') }}" method="post">
<label class="text-gray-700 font-bold" for="username">Email ou pseudo</label> <label class="text-gray-700 font-bold" for="username">Email ou pseudo</label>
<input class="w-full mb-4 px-4 py-2 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-blue-500" placeholder="Adresse e-mail ou pseudo" type="text" value="{{ last_username }}" name="username" id="username" autocomplete="email" required autofocus> <input class="w-full mb-4 px-4 py-2 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-blue-500" placeholder="Adresse e-mail ou pseudo" type="text" value="{{ last_username }}" name="username" id="username" autocomplete="email" required autofocus>
<div class="mb-4"> <div class="mb-4">
<label class="text-gray-700 font-bold" for="password">Mot de passe</label> <label class="text-gray-700 font-bold" for="password">Mot de passe</label>
<input class="w-full mb-2 px-4 py-2 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-blue-500" placeholder="Mot de passe" type="password" name="password" id="password" autocomplete="current-password" required> <input class="w-full mb-2 px-4 py-2 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-blue-500" placeholder="Mot de passe" type="password" name="password" id="password" autocomplete="current-password" required>
<a id="openModal" class="text-blue-500 text-sm hover:underline">Mot de passe oublié ?</a> <a id="openModal" class="text-blue-500 text-sm hover:underline">Mot de passe oublié ?</a>
</div> </div>
<label> <label>
<input type="checkbox" name="_remember_me" checked> <input type="checkbox" name="_remember_me" checked>
Se souvenir de moi Se souvenir de moi
</label> </label>
<input type="hidden" name="_csrf_token"
value="{{ csrf_token('authenticate') }}" <input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
>
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<button class=" text-white font-bold py-2 px-4 border-b-4 btnRegister rounded" type="submit">Se connecter</button> <button class="btnPrimary" type="submit">Se connecter</button>
<a href="{{ path('app_register') }}"> <a href="{{ path('app_register') }}">
<button class=" text-white font-bold py-2 px-4 border-b-4 btnRegister rounded mx-auto" type="button">S'inscrire</button> <button class="btnSecondary" type="button">S'inscrire</button>
</a> </a>
</div> </div>
</form> </form>
</div> </div>
<!-- Modale pour ajouter une ville -->
<div id="resetModal" class="fixed inset-0 z-50 hidden bg-gray-900 bg-opacity-50"> <div id="resetModal" class="fixed inset-0 z-50 hidden bg-gray-900 bg-opacity-50">
<div class="flex justify-center items-center min-h-screen"> <div class="flex justify-center items-center min-h-screen">
<div class="bg-white p-6 rounded shadow-md w-1/3"> <div class="bg-white p-6 rounded shadow-md w-1/3">
@@ -49,8 +51,8 @@
</div> </div>
<div class="flex justify-end"> <div class="flex justify-end">
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700">Envoyer</button> <button type="submit" class="btnPrimary">Envoyer</button>
<button type="button" id="closeModal" class="ml-2 bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-700">Annuler</button> <button type="button" id="closeModal" class="btnAnnule ml-2">Annuler</button>
</div> </div>
</form> </form>
</div> </div>
@@ -58,16 +60,13 @@
</div> </div>
<script> <script>
// Ouvrir la modale
document.getElementById('openModal').addEventListener('click', function() { document.getElementById('openModal').addEventListener('click', function() {
document.getElementById('resetModal').classList.remove('hidden'); document.getElementById('resetModal').classList.remove('hidden');
}); });
// Fermer la modale
document.getElementById('closeModal').addEventListener('click', function() { document.getElementById('closeModal').addEventListener('click', function() {
document.getElementById('resetModal').classList.add('hidden'); document.getElementById('resetModal').classList.add('hidden');
}); });
</script> </script>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -24,7 +24,7 @@
{{ form_row(registrationForm.email) }} {{ form_row(registrationForm.email) }}
{{ form_row(registrationForm.pseudo) }} {{ form_row(registrationForm.pseudo) }}
{{ form_row(registrationForm.plainPassword) }} {{ form_row(registrationForm.plainPassword) }}
<button class="btnRegister text-white font-bold py-2 px-4 border-b-4 rounded mx-auto" type="submit">S'inscrire</button> <button class="btnSecondary" type="submit">S'inscrire</button>
{{ form_end(registrationForm) }} {{ form_end(registrationForm) }}
</div> </div>
</div> </div>

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}&#128227; Sortie.com &#128266;{% endblock %}</title> <title>{% block title %}&#128227; Sortir.com &#128266;{% endblock %}</title>
<link rel="icon" href="{{ asset('favicon.ico') }}" type="image/x-icon"> <link rel="icon" href="{{ asset('favicon.ico') }}" type="image/x-icon">
{% block stylesheets %} {% block stylesheets %}
{{ encore_entry_link_tags('app') }} {{ encore_entry_link_tags('app') }}

View File

@@ -14,13 +14,11 @@
</head> </head>
<body class="bg-gray-100"> <body class="bg-gray-100">
<nav class="flex justify-between items-center py-4 px-6 shadow-md bg-gradient-to-r from-[#2A8D57] via-[#008167] to-[#1B5667]"> <nav class="flex justify-between items-center py-4 px-6 shadow-md bg-gradient-to-r from-[#2A8D57] via-[#008167] to-[#1B5667]">
<!-- Logo et Titre -->
<a href="/" class="flex items-center space-x-3"> <a href="/" class="flex items-center space-x-3">
<img src="{{ asset('img/logo.svg') }}" alt="Logo" class="h-10"> <img src="{{ asset('img/logo.svg') }}" alt="Logo" class="h-10">
<h3 class="text-3xl text-white font-bold">Sortie</h3> <h3 class="text-3xl text-white font-bold">Sortir</h3>
</a> </a>
<!-- Navigation principale -->
<ul class="hidden md:flex space-x-6 text-white font-medium"> <ul class="hidden md:flex space-x-6 text-white font-medium">
<li> <li>
<a href="{{ path('home') }}" <a href="{{ path('home') }}"
@@ -42,7 +40,6 @@
</li> </li>
</ul> </ul>
<!-- Profil utilisateur -->
<div class="relative"> <div class="relative">
{% if app.user %} {% if app.user %}
<button id="profile-menu" class="flex items-center space-x-2 focus:outline-none"> <button id="profile-menu" class="flex items-center space-x-2 focus:outline-none">
@@ -69,7 +66,6 @@
{% endif %} {% endif %}
</div> </div>
<!-- Menu mobile -->
<button id="mobile-menu-button" class="md:hidden text-white focus:outline-none"> <button id="mobile-menu-button" class="md:hidden text-white focus:outline-none">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
@@ -77,7 +73,6 @@
</button> </button>
</nav> </nav>
<!-- Menu mobile (hidden par défaut) -->
<div id="mobile-menu" class="hidden bg-gradient-to-r from-[#2A8D57] via-[#008167] to-[#1B5667] text-white py-4 md:hidden"> <div id="mobile-menu" class="hidden bg-gradient-to-r from-[#2A8D57] via-[#008167] to-[#1B5667] text-white py-4 md:hidden">
<ul class="space-y-4 px-6"> <ul class="space-y-4 px-6">
<li><a href="{{ path('home') }}" class="block hover:text-gray-200">Accueil</a></li> <li><a href="{{ path('home') }}" class="block hover:text-gray-200">Accueil</a></li>
@@ -89,9 +84,7 @@
{% block content %} {% block content %}
{% endblock %} {% endblock %}
<!-- Scripts -->
<script> <script>
// Toggle menu pour mobile
const mobileMenuButton = document.getElementById('mobile-menu-button'); const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu'); const mobileMenu = document.getElementById('mobile-menu');
@@ -99,7 +92,6 @@
mobileMenu.classList.toggle('hidden'); mobileMenu.classList.toggle('hidden');
}); });
// Toggle menu déroulant pour le profil
const profileMenuButton = document.getElementById('profile-menu'); const profileMenuButton = document.getElementById('profile-menu');
const dropdown = document.getElementById('dropdown'); const dropdown = document.getElementById('dropdown');
@@ -109,7 +101,6 @@
dropdown.classList.toggle('hidden'); dropdown.classList.toggle('hidden');
}); });
// Fermer le menu lorsqu'on clique ailleurs
document.addEventListener('click', (event) => { document.addEventListener('click', (event) => {
if (!dropdown.classList.contains('hidden')) { if (!dropdown.classList.contains('hidden')) {
const isClickInsideMenu = dropdown.contains(event.target) || profileMenuButton.contains(event.target); const isClickInsideMenu = dropdown.contains(event.target) || profileMenuButton.contains(event.target);

View File

@@ -10,7 +10,7 @@
<style> <style>
.swiper-container { .swiper-container {
width: 100%; width: 100%;
height: 500px; /* Ajustez selon vos besoins */ height: 500px;
} }
.swiper-slide { .swiper-slide {
@@ -56,7 +56,7 @@
} }
.swiper-pagination-bullet-active { .swiper-pagination-bullet-active {
background: #16a34a; /* Vert plus foncé pour le point actif */ background: #16a34a;
} }
</style> </style>
{% endblock %} {% endblock %}
@@ -65,7 +65,6 @@
{% block content %} {% block content %}
<div class="container mx-auto py-10"> <div class="container mx-auto py-10">
<!-- Section carrousel -->
<h2 class="text-4xl font-bold text-center mb-8">Dernières sorties</h2> <h2 class="text-4xl font-bold text-center mb-8">Dernières sorties</h2>
<div class="w-full relative"> <div class="w-full relative">
<div class="swiper default-carousel swiper-container"> <div class="swiper default-carousel swiper-container">
@@ -99,7 +98,6 @@
</div> </div>
</div> </div>
<!-- Section des événements terminés -->
<div class="mt-12"> <div class="mt-12">
<h2 class="text-3xl font-bold text-center mb-6">Événements terminés</h2> <h2 class="text-3xl font-bold text-center mb-6">Événements terminés</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@@ -123,7 +121,6 @@
</div> </div>
</div> </div>
<!-- Inclure les fichiers JavaScript Swiper -->
{% block javascripts %} {% block javascripts %}
<script src="https://unpkg.com/swiper/swiper-bundle.min.js"></script> <script src="https://unpkg.com/swiper/swiper-bundle.min.js"></script>
<script> <script>

View File

@@ -44,7 +44,7 @@
{{ form_row(formProfile.deleteImage) }} {{ form_row(formProfile.deleteImage) }}
<img class="mx-auto justify-items-center" alt="image de profile" src="{{ asset('/upload/image/profile/' ~ formProfile.vars.value.fileName) }}" height="128" width="128"> <img class="mx-auto justify-items-center" alt="image de profile" src="{{ asset('/upload/image/profile/' ~ formProfile.vars.value.fileName) }}" height="128" width="128">
{% endif %} {% endif %}
<button class="btnRegister text-white font-bold py-2 px-4 border-b-4 rounded mx-auto" type="submit">Actualiser les informations</button> <button class="btnPrimary" type="submit">Actualiser les informations</button>
{{ form_end(formProfile) }} {{ form_end(formProfile) }}
</div> </div>
</div> </div>

View File

@@ -1,13 +1,4 @@
{% extends 'main/base.html.twig' %} {% extends 'main/base.html.twig' %}
{% block head %}
<head>
<meta charset="UTF-8">
{% block title %}&#128227; Sortie.com {{ profile.pseudo }} &#128266;{% endblock %}
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
</head>
{% endblock %}
{% block content %} {% block content %}
<div class="relative mx-auto md:max-w-xl pt-24 pb-8 min-w-0 break-words"> <div class="relative mx-auto md:max-w-xl pt-24 pb-8 min-w-0 break-words">
@@ -25,30 +16,30 @@
<i class="fas fa-map-marker-alt mr-2 text-slate-400 opacity-75"></i>{{ profile.pseudo }} <i class="fas fa-map-marker-alt mr-2 text-slate-400 opacity-75"></i>{{ profile.pseudo }}
</div> </div>
{% if isActiveUser %} {% if isActiveUser %}
<div class="text-xs mt-0 mb-2 text-slate-400 font-bold uppercase"> <div class="text-xs mt-0 mb-2 text-slate-400 font-bold uppercase">
<i class="fas fa-map-marker-alt mr-2 text-slate-400 opacity-75"></i>{{ profile.telephone }} - {{ profile.email }} <i class="fas fa-map-marker-alt mr-2 text-slate-400 opacity-75"></i>{{ profile.telephone }} - {{ profile.email }}
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% if isActiveUser %} {% if isActiveUser %}
<div class="mt-6 py-6 border-t border-slate-200 text-center"> <div class="mt-6 py-6 border-t border-slate-200 text-center">
<div class="flex flex-wrap justify-center"> <div class="flex flex-wrap justify-center">
<div class="w-full px-4"> <div class="w-full px-4">
<a href="{{ path('profile_edit', {'uuid': app.user.idParticipant}) }}" class="text-slate-700 hover:text-slate-400"> <a href="{{ path('profile_edit', {'uuid': app.user.idParticipant}) }}" class="text-slate-700 hover:text-slate-400">
<button class="bg-green-500 px-4 py-2 rounded hover:bg-green-700"> <button class="btnThird">
Modifier Modifier
</button> </button>
</a> </a>
</div>
</div> </div>
</div> </div>
</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="flex flex-row flex-wrap mx-8"> <div class="flex flex-col lg:flex-row mx-8 gap-6">
{# Tableau des sorties dont je suis participant#} {# Tableau des sorties dont je suis participant #}
<div class="w-1/2 p-4 shadow-lg rounded-lg"> <div class="w-full lg:w-1/2 p-4 shadow-lg rounded-lg overflow-x-auto">
<h4 class="text-start bg-gray-50 items-start justify-start text-base p-2 font-bold uppercase">Mes participations</h4> <h4 class="text-start bg-gray-50 items-start justify-start text-base p-2 font-bold uppercase">Mes participations</h4>
<table class="min-w-full bg-white divide-y divide-gray-200"> <table class="min-w-full bg-white divide-y divide-gray-200">
<thead class="bg-gray-50"> <thead class="bg-gray-50">
@@ -70,7 +61,7 @@
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ sortie.dateHeureDebut|date('d/m/Y H:i') }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ sortie.dateHeureDebut|date('d/m/Y H:i') }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ sortie.duree }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ sortie.duree }}</td>
<td class="flex flex-row px-6 py-4 whitespace-nowrap text-base text-gray-900"> <td class="flex flex-row px-6 py-4 whitespace-nowrap text-base text-gray-900">
<a href={{ path('sortie_view', { id: sortie.idSortie }) }}>👁️</a> <a href="{{ path('sortie_view', { id: sortie.idSortie }) }}" class="text-green-500">👁️</a>
</td> </td>
</tr> </tr>
{% else %} {% else %}
@@ -82,8 +73,8 @@
</table> </table>
</div> </div>
{# Tableau des sorties dont je suis organisateur #} {# Tableau des sorties dont je suis organisateur #}
<div class="w-1/2 p-4 shadow-lg rounded-lg"> <div class="w-full lg:w-1/2 p-4 shadow-lg rounded-lg overflow-x-auto">
<h4 class="text-start bg-gray-50 items-start justify-start text-base p-2 font-bold uppercase">Mes organisations</h4> <h4 class="text-start bg-gray-50 items-start justify-start text-base p-2 font-bold uppercase">Mes organisations</h4>
<table class="min-w-full bg-white divide-y divide-gray-200"> <table class="min-w-full bg-white divide-y divide-gray-200">
<thead class="bg-gray-50"> <thead class="bg-gray-50">
@@ -105,7 +96,7 @@
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ sortie.dateHeureDebut|date('d/m/Y H:i') }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ sortie.dateHeureDebut|date('d/m/Y H:i') }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ sortie.duree }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ sortie.duree }}</td>
<td class="flex flex-row px-6 py-4 whitespace-nowrap text-base text-gray-900"> <td class="flex flex-row px-6 py-4 whitespace-nowrap text-base text-gray-900">
<a href={{ path('sortie_view', { id: sortie.idSortie }) }}>👁️</a> <a href="{{ path('sortie_view', { id: sortie.idSortie }) }}" class="text-green-500">👁️</a>
</td> </td>
</tr> </tr>
{% else %} {% else %}

View File

@@ -110,9 +110,6 @@
</div> </div>
{% endif %} {% endif %}
<button type="button" id="generate-image" class="modern-button mt-2">
Générer une image
</button>
</div> </div>
</div> </div>
@@ -121,10 +118,7 @@
<label for="sortie_lieu">Lieu</label> <label for="sortie_lieu">Lieu</label>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
{{ form_widget(form.lieu, { 'attr': { 'class': 'block w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500', 'id': 'lieu-select' } }) }} {{ form_widget(form.lieu, { 'attr': { 'class': 'block w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500', 'id': 'lieu-select' } }) }}
<button type="button" id="add-lieu-button" class="modern-button"> <button type="button" id="add-lieu-button" class="btnThird">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
Ajouter Ajouter
</button> </button>
</div> </div>
@@ -154,10 +148,10 @@
</div> </div>
<div class="mt-6 flex justify-center space-x-4"> <div class="mt-6 flex justify-center space-x-4">
<button type="submit" name="action" value="save" class="px-6 py-3 bg-green-500 text-white rounded-lg shadow hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-300"> <button type="submit" name="action" value="save" class="btnPrimary">
Enregistrer Enregistrer
</button> </button>
<a href="{{ path('home') }}" class="px-6 py-3 bg-gray-500 text-white rounded-lg shadow hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-300"> <a href="{{ path('home') }}" class="btnAnnule">
Annuler Annuler
</a> </a>
</div> </div>

View File

@@ -70,15 +70,51 @@
</div> </div>
</div> </div>
<div class="form-group">
<label for="sortie_imageFile">Image de la sortie</label>
{{ form_widget(form.imageFile, { 'attr': { 'class': 'block w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500', 'onchange': 'previewImage(event)' } }) }}
<small class="text-gray-500">Formats acceptés : JPG, PNG (max. 5 Mo).</small>
{% if sortie and sortie.image %}
<div class="mt-4">
<img id="image-preview" src="{{ asset('img/sortie/' ~ sortie.image) }}" alt="Image prévisualisée" class="max-w-xs h-auto rounded-lg border border-gray-300">
</div>
{% else %}
<div class="mt-4">
<img id="image-preview" src="" alt="Aucune image sélectionnée" class="max-w-xs h-auto rounded-lg border border-gray-300 hidden">
</div>
{% endif %}
</div>
<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="btnAnnule">
❌ Annuler ❌ Annuler
</a> </a>
<button type="submit" class="px-6 py-3 bg-blue-500 text-white rounded-lg shadow-md hover:bg-blue-600"> <button type="submit" class="btnPrimary">
💾 Enregistrer 💾 Enregistrer
</button> </button>
</div> </div>
{{ form_end(form) }} {{ form_end(form) }}
</div> </div>
{% block javascripts %}
<script>
function previewImage(event) {
const input = event.target;
const preview = document.getElementById('image-preview');
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = function (e) {
preview.src = e.target.result;
preview.classList.remove('hidden');
};
reader.readAsDataURL(input.files[0]);
} else {
preview.src = '';
preview.classList.add('hidden');
}
}
</script>
{% endblock %}
{% endblock %} {% endblock %}

View File

@@ -31,17 +31,14 @@
<div class="container mx-auto p-4 sm:p-6 bg-gray-50 rounded-lg shadow-lg mt-4 sm:mt-6"> <div class="container mx-auto p-4 sm:p-6 bg-gray-50 rounded-lg shadow-lg mt-4 sm:mt-6">
<h1 class="text-2xl sm:text-3xl font-bold text-center text-gray-800 mb-6 sm:mb-8">🎉 Liste des sorties</h1> <h1 class="text-2xl sm:text-3xl font-bold text-center text-gray-800 mb-6 sm:mb-8">🎉 Liste des sorties</h1>
<!-- Formulaire de recherche --> <form method="GET" action="{{ path('sortie_list') }}" 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 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6">
<!-- Recherche -->
<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', '') }}"
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"
placeholder="Nom de la sortie"> placeholder="Nom de la sortie">
</div> </div>
<!-- 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">
@@ -53,13 +50,11 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<!-- 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>
<!-- 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', '') }}"
@@ -67,7 +62,6 @@
</div> </div>
</div> </div>
<!-- Options supplémentaires -->
<div class="mt-4 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4"> <div class="mt-4 grid grid-cols-1 sm: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"
@@ -95,15 +89,13 @@
</div> </div>
</div> </div>
<!-- Bouton de filtrage -->
<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="btnPrimary">
🔎 Filtrer 🔎 Filtrer
</button> </button>
</div> </div>
</form> </form>
<!-- Liste des sorties -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6">
<a href="/sortie/creates" class="hidden sm:block"> <a href="/sortie/creates" class="hidden sm:block">
<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

@@ -11,6 +11,9 @@
#delete-modal { #delete-modal {
z-index: 1050; z-index: 1050;
} }
#confirm-modal {
z-index: 1050;
}
</style> </style>
{% endblock %} {% endblock %}
</head> </head>
@@ -74,9 +77,10 @@
{% endif %} {% endif %}
</div> </div>
{% if app.user and sortie.etat.libelle == 'Ouverte' and not sortie.participants.contains(app.user) and date(sortie.dateLimiteInscription) > date() %} {% if app.user and sortie.etat.libelle == 'Ouverte' and not sortie.participants.contains(app.user) and date(sortie.dateLimiteInscription) > date() %}
<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="btnPrimary">
✅ S'inscrire ✅ S'inscrire
</button> </button>
</form> </form>
@@ -85,7 +89,7 @@
<p class="text-green-600 font-bold">✅ Vous êtes déjà inscrit à cette sortie.</p> <p class="text-green-600 font-bold">✅ Vous êtes déjà inscrit à cette sortie.</p>
{% if sortie.etat.libelle == 'Ouverte' %} {% if sortie.etat.libelle == 'Ouverte' %}
<form action="{{ path('sortie_unsubscribe', { id: sortie.idSortie }) }}" method="post" class="mt-6"> <form action="{{ path('sortie_unsubscribe', { id: sortie.idSortie }) }}" method="post" class="mt-6">
<button type="submit" class="px-6 py-3 bg-red-500 text-white rounded-md shadow hover:bg-red-600"> <button type="submit" class="btnAnnule">
❌ Se désister ❌ Se désister
</button> </button>
</form> </form>
@@ -93,18 +97,32 @@
</div> </div>
{% endif %} {% endif %}
{% if app.user and app.user.idParticipant == sortie.organisateur.idParticipant and sortie.etat.libelle == 'Créée' %} {% if app.user and app.user.idParticipant == sortie.organisateur.idParticipant %}
<div class="mt-6 flex justify-end space-x-4"> <div class="mt-6 flex justify-end space-x-4">
{% if sortie.etat.libelle == 'Créée' or sortie.etat.libelle == 'Ouverte'%}
<button <button
type="button" type="button"
id="open-delete-modal" id="open-delete-modal"
class="px-6 py-3 bg-red-500 text-white rounded-md shadow hover:bg-red-600"> class="btnAnnule">
❌ Annuler la sortie ❌ Annuler la sortie
</button> </button>
<a href="{{ path('sortie_edit', { id: sortie.idSortie }) }}" {% endif %}
class="px-6 py-3 bg-yellow-500 text-white rounded-md shadow hover:bg-yellow-600"> {% if sortie.etat.libelle == 'Créée' %}
✏️ Modifier la sortie <a href="{{ path('sortie_edit', { id: sortie.idSortie }) }}"
</a> class="px-6 py-3 bg-yellow-500 text-white rounded-md shadow hover:bg-yellow-600">
✏️ Modifier la sortie
</a>
{# <a href="{{ path('sortie_open', { id: sortie.idSortie }) }}"#}
{# class="px-6 py-3 bg-yellow-500 text-white rounded-md shadow hover:bg-yellow-600">#}
{# 🚀 Démarrer les inscriptions#}
{# </a>#}
<button
type="button"
id="open-confirm-modal"
class="px-6 py-3 bg-green-500 text-white rounded-md shadow hover:bg-green-600">
🚀 Démarrer les inscriptions
</button>
{% endif %}
</div> </div>
{% endif %} {% endif %}
@@ -153,7 +171,7 @@
</button> </button>
<button <button
type="submit" type="submit"
class="px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600"> class="btnAnnule">
Confirmer l'annulation Confirmer l'annulation
</button> </button>
</div> </div>
@@ -161,6 +179,31 @@
</div> </div>
</div> </div>
{# Modale pour ouvrir les inscriptions d'une sortie#}
<div id="confirm-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-lg">
<h2 class="text-xl font-bold mb-4">Ouvrir les inscriptions</h2>
<form action="{{ path('sortie_confirm', {id: sortie.idSortie}) }}" method="post">
<div class="mb-4">
<p class="text-base">La modification de cette sortie ne sera pu possible une fois les inscriptions ouverte</p>
</div>
<div class="flex justify-end space-x-4">
<button
type="button"
id="cancel-confirm-modal"
class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600">
Annuler
</button>
<button
type="submit"
class="btnThird">
Démarrer les inscriptions
</button>
</div>
</form>
</div>
</div>
<div class="flex justify-center mt-8"> <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"> <a href="{{ path('home') }}" class="px-6 py-3 bg-blue-500 text-white rounded-md shadow hover:bg-blue-600">
🔙 Retour à l'accueil 🔙 Retour à l'accueil
@@ -191,16 +234,27 @@
.openPopup(); .openPopup();
const openModalButton = document.getElementById('open-delete-modal'); const openModalButton = document.getElementById('open-delete-modal');
const openModalConfirm = document.getElementById('open-confirm-modal');
const cancelModalButton = document.getElementById('cancel-modal'); const cancelModalButton = document.getElementById('cancel-modal');
const cancelModalConfirm = document.getElementById('cancel-confirm-modal')
const deleteModal = document.getElementById('delete-modal'); const deleteModal = document.getElementById('delete-modal');
const confirmModal = document.getElementById('confirm-modal');
openModalButton?.addEventListener('click', () => { openModalButton?.addEventListener('click', () => {
deleteModal.classList.remove('hidden'); deleteModal.classList.remove('hidden');
}); });
cancelModalButton?.addEventListener('click', () => { cancelModalButton?.addEventListener('click', () => {
deleteModal.classList.add('hidden'); deleteModal.classList.add('hidden');
}); });
openModalConfirm?.addEventListener('click', () => {
confirmModal.classList.remove('hidden');
})
cancelModalConfirm?.addEventListener('click', () => {
confirmModal.classList.add('hidden')
})
}); });
</script> </script>
{% endblock %} {% endblock %}