diff --git a/assets/admin.js b/assets/admin.js index bf73389..34c0022 100644 --- a/assets/admin.js +++ b/assets/admin.js @@ -1,3 +1,2 @@ import './admin.scss' -import * as Turbo from "@hotwired/turbo" diff --git a/config/packages/vich_uploader.yaml b/config/packages/vich_uploader.yaml index 9584a85..4d763c2 100644 --- a/config/packages/vich_uploader.yaml +++ b/config/packages/vich_uploader.yaml @@ -52,6 +52,28 @@ vich_uploader: inject_on_load: true delete_on_update: true delete_on_remove: true + product: + uri_prefix: /storage/product + upload_destination: '%kernel.project_dir%/public/storage/product' + namer: Vich\UploaderBundle\Naming\UniqidNamer # Replaced namer + inject_on_load: true + delete_on_update: true + delete_on_remove: true + events: + uri_prefix: /storage/events + upload_destination: '%kernel.project_dir%/public/storage/events' + namer: Vich\UploaderBundle\Naming\UniqidNamer # Replaced namer + inject_on_load: true + delete_on_update: true + delete_on_remove: true + event_picture: + uri_prefix: /event_picture/events + upload_destination: '%kernel.project_dir%/public/storage/event_picture' + namer: Vich\UploaderBundle\Naming\UniqidNamer # Replaced namer + directory_namer: App\VichUploader\DirectoryNamer\EventName + inject_on_load: true + delete_on_update: true + delete_on_remove: true #mappings: # products: # uri_prefix: /images/products diff --git a/migrations/Version20251202200532.php b/migrations/Version20251202200532.php new file mode 100644 index 0000000..df74426 --- /dev/null +++ b/migrations/Version20251202200532.php @@ -0,0 +1,35 @@ +addSql('CREATE TABLE event (id SERIAL NOT NULL, title VARCHAR(255) NOT NULL, start_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, end_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, location VARCHAR(255) NOT NULL, organizer VARCHAR(255) NOT NULL, events_file_name VARCHAR(255) DEFAULT NULL, events_dimensions JSON DEFAULT NULL, events_size VARCHAR(255) DEFAULT NULL, events_mine_type VARCHAR(255) DEFAULT NULL, events_original_name VARCHAR(255) DEFAULT NULL, update_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('COMMENT ON COLUMN event.start_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN event.end_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN event.update_at IS \'(DC2Type:datetime_immutable)\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP TABLE event'); + } +} diff --git a/src/Controller/Admin/AdminController.php b/src/Controller/Admin/AdminController.php index 1764e90..9e03cd1 100644 --- a/src/Controller/Admin/AdminController.php +++ b/src/Controller/Admin/AdminController.php @@ -14,17 +14,21 @@ use App\Entity\Ag\MainMember; use App\Entity\Ag\MainOrder; use App\Entity\Ag\MainSigned; use App\Entity\Ag\MainVote; +use App\Entity\Event; use App\Entity\Members; use App\Entity\MembersCotisations; use App\Entity\Products; +use App\Form\EventType; use App\Form\MembersType; use App\Form\ProductsType; use App\Form\RequestPasswordConfirmType; use App\Form\RequestPasswordRequestType; use App\Repository\Ag\MainRepository; +use App\Repository\EventRepository; use App\Repository\MembersCotisationsRepository; use App\Repository\MembersRepository; use App\Repository\ProductsRepository; +use App\Service\Events\AdvertEvent; use App\Service\Mailer\Mailer; use App\Service\Payments\PaymentClient; use App\Service\Pdf\AgAdh; @@ -242,9 +246,43 @@ class AdminController extends AbstractController } #[Route(path: '/admin/events', name: 'admin_events', options: ['sitemap' => false], methods: ['GET'])] - public function adminEvents(): Response + public function adminEvents(EventRepository $eventRepository): Response { - return $this->render('admin/dashboard.twig', [ + return $this->render('admin/events.twig', [ + 'events' => $eventRepository->findBy([],['id' => 'DESC']), + ]); + } + #[Route(path: '/admin/events/delete', name: 'admin_events_delete', options: ['sitemap' => false], methods: ['GET','POST'])] + public function adminEventDelete(?Event $event,EntityManagerInterface $entityManager): Response + { + + } + #[Route(path: '/admin/events/add', name: 'admin_events_create', options: ['sitemap' => false], methods: ['GET','POST'])] + #[Route(path: '/admin/events/{id}', name: 'admin_events_edit', options: ['sitemap' => false], methods: ['GET','POST'])] + public function adminEventAdd(?Event $event,Request $request,EntityManagerInterface $entityManager,EventDispatcherInterface $eventDispatcher): Response + { + $isNew = false; + if(is_null($event)) { + $event = new Event(); + $isNew = true; + } + $form = $this->createForm(EventType::class,$event); + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $event->setAffiche($request->files->all()['event']['affiche']); + $event->setUpdateAt(new \DateTimeImmutable('now')); + if($isNew) { + $sendEvent = new AdvertEvent($event); + $eventDispatcher->dispatch($sendEvent); + } + $entityManager->persist($event); + $entityManager->flush(); + + return $this->redirectToRoute('admin_events'); + } + return $this->render('admin/events/add.twig', [ + 'form' => $form->createView(), + 'event' => $event ]); } diff --git a/src/Controller/EventsController.php b/src/Controller/EventsController.php index 18cfc95..1eb8ee7 100644 --- a/src/Controller/EventsController.php +++ b/src/Controller/EventsController.php @@ -4,8 +4,10 @@ namespace App\Controller; use App\Entity\Account; use App\Entity\AccountResetPasswordRequest; +use App\Entity\Event; use App\Form\RequestPasswordConfirmType; use App\Form\RequestPasswordRequestType; +use App\Repository\EventRepository; use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent; use App\Service\ResetPassword\Event\ResetPasswordEvent; use Doctrine\ORM\EntityManagerInterface; @@ -23,17 +25,21 @@ class EventsController extends AbstractController { #[Route(path: '/events', name: 'app_events', options: ['sitemap' => true], methods: ['GET'])] - public function index(): Response + public function index(EventRepository $eventRepository): Response { return $this->render('event.twig',[ - 'events' => [ - - ] + 'events' => $eventRepository->findBy([],['startAt' => 'DESC']), ]); } #[Route(path: '/events/{id}', name: 'app_event_details', options: ['sitemap' => false], methods: ['GET'])] - public function eventDetails(): Response + public function eventDetails(?Event $event): Response { + if(is_null($event)) { + return $this->redirectToRoute('app_events'); + } + return $this->render('event_view.twig',[ + 'event' => $event, + ]); } } diff --git a/src/Entity/Event.php b/src/Entity/Event.php new file mode 100644 index 0000000..88ee93b --- /dev/null +++ b/src/Entity/Event.php @@ -0,0 +1,226 @@ +id; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): static + { + $this->title = $title; + + return $this; + } + + public function getStartAt(): ?\DateTimeImmutable + { + return $this->startAt; + } + + public function setStartAt(\DateTimeImmutable $startAt): static + { + $this->startAt = $startAt; + + return $this; + } + + public function getEndAt(): ?\DateTimeImmutable + { + return $this->endAt; + } + + public function setEndAt(\DateTimeImmutable $endAt): static + { + $this->endAt = $endAt; + + return $this; + } + + public function getLocation(): ?string + { + return $this->location; + } + + public function setLocation(string $location): static + { + $this->location = $location; + + return $this; + } + + public function getOrganizer(): ?string + { + return $this->organizer; + } + + public function setOrganizer(string $organizer): static + { + $this->organizer = $organizer; + + return $this; + } + + /** + * @return \DateTimeImmutable|null + */ + public function getUpdateAt(): ?\DateTimeImmutable + { + return $this->updateAt; + } + + /** + * @return File|null + */ + public function getAffiche(): ?File + { + return $this->affiche; + } + + /** + * @return array|null + */ + public function getEventsDimensions(): ?array + { + return $this->eventsDimensions; + } + + /** + * @return string|null + */ + public function getEventsFileName(): ?string + { + return $this->eventsFileName; + } + + /** + * @return string|null + */ + public function getEventsMineType(): ?string + { + return $this->eventsMineType; + } + + /** + * @return string|null + */ + public function getEventsOriginalName(): ?string + { + return $this->eventsOriginalName; + } + + /** + * @return string|null + */ + public function getEventsSize(): ?string + { + return $this->eventsSize; + } + + /** + * @param \DateTimeImmutable|null $updateAt + */ + public function setUpdateAt(?\DateTimeImmutable $updateAt): void + { + $this->updateAt = $updateAt; + } + + /** + * @param File|null $affiche + */ + public function setAffiche(?File $affiche): void + { + $this->affiche = $affiche; + } + + /** + * @param array|null $eventsDimensions + */ + public function setEventsDimensions(?array $eventsDimensions): void + { + $this->eventsDimensions = $eventsDimensions; + } + + /** + * @param string|null $eventsFileName + */ + public function setEventsFileName(?string $eventsFileName): void + { + $this->eventsFileName = $eventsFileName; + } + + /** + * @param string|null $eventsMineType + */ + public function setEventsMineType(?string $eventsMineType): void + { + $this->eventsMineType = $eventsMineType; + } + + /** + * @param string|null $eventsOriginalName + */ + public function setEventsOriginalName(?string $eventsOriginalName): void + { + $this->eventsOriginalName = $eventsOriginalName; + } + + /** + * @param string|null $eventsSize + */ + public function setEventsSize(?string $eventsSize): void + { + $this->eventsSize = $eventsSize; + } +} diff --git a/src/EventSubscriber/SitemapSubscriber.php b/src/EventSubscriber/SitemapSubscriber.php index 0b6e2ef..d39f47b 100644 --- a/src/EventSubscriber/SitemapSubscriber.php +++ b/src/EventSubscriber/SitemapSubscriber.php @@ -2,6 +2,7 @@ namespace App\EventSubscriber; +use App\Repository\EventRepository; use App\Repository\ProductsRepository; use Cocur\Slugify\Slugify; use Liip\ImagineBundle\Imagine\Cache\CacheManager; @@ -16,7 +17,7 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; #[AsEventListener(event: SitemapPopulateEvent::class, method: 'onSitemapPopulate', priority: 10)] class SitemapSubscriber { - public function __construct(private readonly ProductsRepository $productsRepository,private CacheManager $cacheManager) + public function __construct(private readonly EventRepository $eventRepository,private readonly ProductsRepository $productsRepository,private CacheManager $cacheManager) { } @@ -116,6 +117,17 @@ class SitemapSubscriber $urlContainer->addUrl($decoratedUrlAbout, 'default'); $s = new Slugify(); + foreach ($this->eventRepository->findAll() as $eventItem) { + $urlAbout = new UrlConcrete($urlGenerator->generate('app_event_details', ['id'=>$eventItem->getid()], UrlGeneratorInterface::ABSOLUTE_URL)); + $decoratedUrlAbout = new GoogleImageUrlDecorator($urlAbout); + $decoratedUrlAbout->addImage(new GoogleImage($this->cacheManager->resolve('assets/images/logo.jpg','webp'))); + $decoratedUrlAbout->addImage(new GoogleImage($this->cacheManager->resolve($eventItem->getAffiche(),'webp'))); + $decoratedUrlAbout = new GoogleMultilangUrlDecorator($decoratedUrlAbout); + foreach ($langs as $lang) { + $decoratedUrlAbout->addLink($urlGenerator->generate('app_event_details',['id'=>$eventItem->getid()], UrlGeneratorInterface::ABSOLUTE_URL), $lang); + } + $urlContainer->addUrl($decoratedUrlAbout, 'events'); + } foreach ($this->productsRepository->findAll() as $product) { $slug = $s->slugify($product->getName()."-".$product->getId()); $urlAbout = new UrlConcrete($urlGenerator->generate('app_product_show', ['slug'=>$slug], UrlGeneratorInterface::ABSOLUTE_URL)); diff --git a/src/Form/EventType.php b/src/Form/EventType.php new file mode 100644 index 0000000..80b3533 --- /dev/null +++ b/src/Form/EventType.php @@ -0,0 +1,62 @@ +add('affiche', FileType::class, [ // Renommé pour plus de clarté + 'label' => 'Affiche de l\'événement (Max 2Mo)', + 'required' => false, + 'mapped' => false, + 'attr' => [ + 'placeholder' => 'Choisir un fichier...', + ] + ]) + ->add('title', TextType::class, [ + 'label' => 'Titre', + 'required' => true, + ]) + ->add('location', TextType::class, [ + 'label' => 'Location', + 'required' => true, + ]) + ->add('organizer', TextType::class, [ + 'label' => 'Organisateur', + 'required' => true, + ]) + ->add('startAt',DateType::class,[ + 'label' => 'Date de début', // Correction du libellé + 'required' => true, + 'widget' => 'single_text', // Affiche un champ de date simple au lieu d'une liste déroulante + 'input' => 'datetime', + ]) + ->add('endAt',DateType::class,[ + 'label' => 'Date de rejoint', // Correction du libellé + 'required' => true, + 'widget' => 'single_text', // Affiche un champ de date simple au lieu d'une liste déroulante + 'input' => 'datetime', + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => Event::class, + ]); + } +} diff --git a/src/Repository/EventRepository.php b/src/Repository/EventRepository.php new file mode 100644 index 0000000..ec63e8e --- /dev/null +++ b/src/Repository/EventRepository.php @@ -0,0 +1,43 @@ + + */ +class EventRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Event::class); + } + + // /** + // * @return Event[] Returns an array of Event objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('e') + // ->andWhere('e.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('e.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Event + // { + // return $this->createQueryBuilder('e') + // ->andWhere('e.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/src/Service/Events/AdvertEvent.php b/src/Service/Events/AdvertEvent.php new file mode 100644 index 0000000..4e0c23a --- /dev/null +++ b/src/Service/Events/AdvertEvent.php @@ -0,0 +1,23 @@ +event = $event; + } + + /** + * @return Event + */ + public function getEvent(): Event + { + return $this->event; + } +} diff --git a/src/Service/Events/EventSubscriber.php b/src/Service/Events/EventSubscriber.php new file mode 100644 index 0000000..b22200e --- /dev/null +++ b/src/Service/Events/EventSubscriber.php @@ -0,0 +1,34 @@ +getEvent(); + foreach ($this->membersRepository->findAll() as $member) { + $this->mailer->send($member->getEmail(),$member->getPseudo(),"[E-Cosplay] - Nouveaux événement","mails/event/new.twig",[ + 'event' => $event, + 'url' => $this->urlGenerator->generate('app_event_details',['id'=>$event->getId()],UrlGeneratorInterface::ABSOLUTE_URL), + ]); + } + + } +} diff --git a/templates/admin/events.twig b/templates/admin/events.twig new file mode 100644 index 0000000..0b71482 --- /dev/null +++ b/templates/admin/events.twig @@ -0,0 +1,104 @@ +{% extends 'admin/base.twig' %} + +{% block title 'Événement(s)' %} +{% block page_title 'Liste des Événements' %} + +{% block body %} + + + +
| + Titre + | + + ++ Début + | + ++ Actions + | +
|---|---|---|
| + {{ event.title }} + | + + + ++ {{ event.startAt|date('d/m/Y') }} + | + + ++ + Modifier + + + + | +
+ Sélectionnez une nouvelle affiche. Le fichier actuel sera remplacé. (Max 2Mo). +
+ {# Affichage des erreurs spécifiques au champ image #} + {{ form_errors(form.affiche) }} +- -
- -
- -
+ +
+ +
+ +
{{ 'events.details.date'|trans|default('Date') }}
++ {{ event.startAt|date('d/m/Y') }} + {% if event.startAt|date('Ymd') != event.endAt|date('Ymd') %} + - {{ event.endAt|date('d/m/Y') }} + {% else %} + - {{ event.endAt|date('H:i') }} + {% endif %} +
+{{ 'events.details.location'|trans|default('Lieu') }}
+{{ event.location }}
+{{ 'events.details.organizer'|trans|default('Organisateur') }}
+{{ event.organizer }}
+