Prechádzať zdrojové kódy

travaux interface publique

garthh 1 deň pred
rodič
commit
ed7cdade0c

+ 45 - 0
assets/controllers/dropdown_controller.js

@@ -0,0 +1,45 @@
+// assets/controllers/dropdown_controller.js
+import { Controller } from '@hotwired/stimulus'
+
+export default class extends Controller {
+  static targets = ['menu']
+
+  toggle(event) {
+    event.stopPropagation()
+
+    const menu = this.menuTarget
+
+    if (menu.classList.contains('is-active')) {
+      // Fermeture avec délai pour laisser la transition jouer
+      menu.classList.remove('is-active')
+      setTimeout(() => {
+        menu.style.display = 'none'
+      }, 200) // correspond à la durée CSS
+    } else {
+      menu.style.display = 'block'
+      // force reflow pour permettre l’animation
+      void menu.offsetWidth
+      menu.classList.add('is-active')
+    }
+  }
+
+  closeOnOutsideClick = (event) => {
+    if (!this.element.contains(event.target)) {
+      const menu = this.menuTarget
+      if (menu.classList.contains('is-active')) {
+        menu.classList.remove('is-active')
+        setTimeout(() => {
+          menu.style.display = 'none'
+        }, 200)
+      }
+    }
+  }
+
+  connect() {
+    document.addEventListener('click', this.closeOnOutsideClick)
+  }
+
+  disconnect() {
+    document.removeEventListener('click', this.closeOnOutsideClick)
+  }
+}

+ 14 - 1
assets/styles/app.css

@@ -21,6 +21,19 @@
   text-overflow: ellipsis;  
 }
 
+.dropdown-menu {
+ /* display: none; */
+  opacity: 0;
+  transform: translateY(-10px);
+  transition: opacity 0.2s ease, transform 0.2s ease;
+}
+
+.dropdown-menu.is-active {
+ /* display: block; */
+  opacity: 1;
+  transform: translateY(0);
+}
+
 /* Styles pour les plannings */
 
 /* Style par défaut pour les cellules
@@ -85,7 +98,7 @@ body.is-dark-mode .planning-cell {
 .planning-cell {
   display: flex;
   align-items: center;
-  justify-content: center;
+  /*justify-content: center;*/
   height: 4rem;
   padding: 0.5rem;
   box-sizing: border-box;

+ 70 - 2
src/Controller/MainController.php

@@ -4,15 +4,82 @@ namespace App\Controller;
 
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Requirement\Requirement;
 use Symfony\Component\Routing\Attribute\Route;
 
+use App\Repository\EventRepository;
+use App\Entity\Event;
+
 final class MainController extends AbstractController
 {
     #[Route('/', name: 'app_main')]
-    public function index(): Response
+    public function index(EventRepository $repository): Response
     {
+        // Est-ce qu'un utilisateur est connecté et qu'il est au moins "staff" ?
+        $user = $this->getUser();
+        $onlyPublicAccess = true;
+        if ($user) {
+            $roles = $user->getRoles();
+            if (in_array('ROLE_STAFF', $roles) || in_array('ROLE_MANAGER', $roles) || in_array('ROLE_ADMIN', $roles)) {
+                $onlyPublicAccess = false;
+            }
+        }
+        // Récupérer la liste des événements visibles
+        $events = $repository->findEventsToCome($onlyPublicAccess);
+
         return $this->render('main/index.html.twig', [
-            'controller_name' => 'MainController',
+            'events' => $events,
+        ]);
+    }
+
+    #[Route('/booking', name: 'app_main_booking_main')]
+    public function bookingMain(EventRepository $repository): Response
+    {
+
+        // Est-ce qu'un utilisateur est connecté et qu'il est au moins "staff" ?
+        $user = $this->getUser();
+        $onlyPublicAccess = true;
+        if ($user) {
+            $roles = $user->getRoles();
+            if (in_array('ROLE_STAFF', $roles) || in_array('ROLE_MANAGER', $roles) || in_array('ROLE_ADMIN', $roles)) {
+                $onlyPublicAccess = false;
+            }
+        }
+        // Récupérer la liste des événements visibles
+        $events = $repository->findEventsToCome($onlyPublicAccess);
+        $event = $events[0];
+        
+
+        return $this->render('main/booking.html.twig', [
+            'event' => $event,
+            'events' => $events
+        ]);
+    }
+
+    #[Route('/booking/{id}', name: 'app_main_booking', requirements: ['id' => Requirement::UUID_V7], methods: ['GET', 'POST'])]
+    public function booking(?Event $event, EventRepository $repository): Response
+    {
+        // Contrôler qu'un événement est bien ok
+        if (!$event) {
+            $this->addFlash('danger', 'Événement inconnu !');
+            $this->redirectToRoute('app_main');
+        }
+
+        // Est-ce qu'un utilisateur est connecté et qu'il est au moins "staff" ?
+        $user = $this->getUser();
+        $onlyPublicAccess = true;
+        if ($user) {
+            $roles = $user->getRoles();
+            if (in_array('ROLE_STAFF', $roles) || in_array('ROLE_MANAGER', $roles) || in_array('ROLE_ADMIN', $roles)) {
+                $onlyPublicAccess = false;
+            }
+        }
+        // Récupérer la liste des événements visibles
+        $events = $repository->findEventsToCome($onlyPublicAccess);
+
+        return $this->render('main/booking.html.twig', [
+            'event' => $event,
+            'events' => $events
         ]);
     }
 
@@ -52,6 +119,7 @@ final class MainController extends AbstractController
     #[Route('/contact', name: 'app_contact')]
     public function contact(): Response
     {
+        // @todo: formulaire de contact avec envoi d'un mail
         // Lire le contenu de la charte des animations depuis un fichier
         $codeContent = file_get_contents(__DIR__ . '/../../public/pages/legal.md');
 

+ 15 - 9
src/Controller/PartyController.php

@@ -50,9 +50,9 @@ final class PartyController extends AbstractController
         } else {
             $this->addFlash('danger', 'Seuls les admins peuvent supprimer une partie.');
         }
-            //return $this->redirectToRoute('app_main'); // @todo: à modifier !
-            $referer = $request->headers->get('referer'); 
-            return $this->redirect($referer);  
+        //return $this->redirectToRoute('app_main'); // @todo: à modifier !
+        $referer = $request->headers->get('referer'); 
+        return $this->redirect($referer);  
     }
 
     // valider une partie proposée par un MJ
@@ -90,8 +90,10 @@ final class PartyController extends AbstractController
 
         $party = $slot->getParty();
         if (!$party) {
-            $this->addFlash('danger', 'Pas de MJ ou de jeu sélectionné.');
-            $this->redirectToRoute('app_main'); // @todo: à modifier
+            $this->addFlash('danger', 'Pas de partie trouvée.');
+            //return $this->redirectToRoute('app_main'); // @todo: à modifier !
+            $referer = $request->headers->get('referer'); 
+            return $this->redirect($referer);
         }
 
         $roles = $user->getRoles();
@@ -103,7 +105,9 @@ final class PartyController extends AbstractController
             if ($party->gamemaster != $gamemasters[0]) {
                 // Alors dégage !
                 $this->addFlash('danger', 'Un MJ ne peut éditer que ses parties.');
-                $this->redirectToRoute('app_main'); // @todo: à modifier               
+                //return $this->redirectToRoute('app_main'); // @todo: à modifier !
+                $referer = $request->headers->get('referer'); 
+                return $this->redirect($referer);              
             }
         } else {
             $gamemasters = $slot->getEvent()->getGamemastersAssigned();
@@ -147,7 +151,7 @@ final class PartyController extends AbstractController
 
                 // @todo: si c'est une partie non validée, envoyer un mail aux admin+gestionnaires pour validation
 
-                $this->addFlash('success', 'Partie ajoutée au planning.');
+                $this->addFlash('success', 'Partie modifiée.');
             } else {
                 $this->addFlash('danger', 'Pas de MJ ou de jeu sélectionné.');
             }
@@ -168,14 +172,16 @@ final class PartyController extends AbstractController
         ]);
     }
 
-    // afficher les détails d'une partie
+    // afficher les détails d'une partie à partir d'un slot
     #[Route('/party/view/{id}', name: 'app_party_view', requirements: ['id' => '\d+'], methods: ['GET', 'POST'])]
     public function view(?Slot $slot, Request $request, SlotRepository $slotRepository, EntityManagerInterface $manager): Response
     {
         $party = $slot->getParty();
         if (!$party) {
             $this->addFlash('danger', 'Aucune partie associée à ce slot !');
-            $this->redirectToRoute('app_main'); // @todo: à déterminer
+            //return $this->redirectToRoute('app_main'); // @todo: à modifier !
+            $referer = $request->headers->get('referer'); 
+            return $this->redirect($referer);
         }
 
         return $this->render('party/view.html.twig', [

+ 18 - 10
src/Repository/EventRepository.php

@@ -22,21 +22,29 @@ class EventRepository extends ServiceEntityRepository
      * - published = true
      * - endOn > now
      */
-    public function findAllPublicToGo(): array
+    public function findEventsToCome(bool $public=true): array
     {
-        
-    }
+        // Et il faut la date du moment
+        $dateNow = new \DateTime('now');
 
-    /*
-     * Extraire les événements :
-     * - published = true
-     * - endOn > now
-     */
-    public function findAllPrivateToGo(): array 
-    {
+        $qb = $this->createQueryBuilder('e')
+            ->where('e.published = :published')
+            ->andWhere('e.endOn > :dateNow')
+            ->setParameter('published', true)
+            ->setParameter('dateNow', $dateNow);
 
+        // Prise en compte public / privé
+        if ($public) {
+            $qb->andWhere('(e.private = false OR e.private IS NULL)');
+            }
+        
+        $query = $qb->orderBy('e.startOn', 'ASC')
+           ->getQuery();
+
+        return $query->getResult();
     }
 
+
     //    /**
     //     * @return Event[] Returns an array of Event objects
     //     */

+ 11 - 0
src/Twig/Components/Modal.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace App\Twig\Components;
+
+use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
+
+#[AsTwigComponent]
+class Modal
+{
+
+}

+ 2 - 5
templates/admin/event/config/party.html.twig

@@ -13,11 +13,8 @@
       </ul>
     </nav>
 
-    <div id="modal" class="modal" {{ stimulus_controller('bulma_modal')}}>
-      <div class="modal-background" data-action="click->modal#close"></div>
-      <div class="modal-content" id="modal-content"></div>
-      <button class="modal-close is-large" aria-label="close" data-action="click->modal#close"></button>
-    </div>
+    {{ component('Modal') }}
+
 
     <div class="box has-text-centered">
       <strong class="has-text-primary">{{ event.name }}</strong> du {{ event.startOn|date('d/m/Y H:i', app_timezone) }} au {{ event.endOn|date('d/m/Y H:i', app_timezone) }}

+ 10 - 10
templates/bulma.html.twig

@@ -50,16 +50,10 @@
           <div id="navbarMain" class="navbar-menu">
             <div class="navbar-start">
               <a class="navbar-item" href="{{ path('app_main') }}">Accueil</a>
-              {% block evtmenu %}{#
-              <div class="navbar-item has-dropdown is-hoverable">
-              <a class="navbar-link">Événement</a>
-                <div class="navbar-dropdown is-left">
-                  <a class="navbar-item" href="#">Lien 1</a>
-                  <a class="navbar-item" href="#">Lien 2</a>
-                  <a class="navbar-item"></a>
-                </div>
-              </div>#}
-              {% endblock %}
+              <a class="navbar-item" href="{{ path('app_main_booking_main') }}">Réserver</a>
+              {% if is_granted('ROLE_ADMIN') or is_granted('ROLE_MANAGER') %}
+              <a class="navbar-item" href="#">Gérer</a>
+              {% endif %}
             </div>
 
             <div class="navbar-end">
@@ -123,6 +117,12 @@
                     Application <a href="https://git.portes-imaginaire.org/portes-imaginaire_org/orgasso">diffusée</a> sous licence <a href="https://opensource.org/license/gpl-3-0">GPL 3.0</a>.
                 </p>
                 <p><a href="{{ path('app_code_of_conduct') }}">Charte animations</a> • <a href="{{ path('app_terms') }}">Conditions générales d'utilisation</a> • <a href="{{ path('app_legal') }}">Mentions légales</a> • <a href="{{ path('app_contact') }}">Contacts</a></p>
+                <a href="https://bulma.io">
+                <img src="https://bulma.io/assets/images/made-with-bulma.png"
+                  alt="Made with Bulma"
+                  width="128"
+                  height="24">
+              </a>
             </div>
         </footer>
     </body>

+ 5 - 0
templates/components/Modal.html.twig

@@ -0,0 +1,5 @@
+    <div id="modal" class="modal" {{ stimulus_controller('bulma_modal')}}>
+      <div class="modal-background" data-action="click->bulma-modal#close"></div>
+      <div class="modal-content" id="modal-content"></div>
+      <button class="modal-close is-large" aria-label="close" data-action="click->bulma-modal#close"></button>
+    </div>

+ 68 - 0
templates/main/booking.html.twig

@@ -0,0 +1,68 @@
+{% extends 'bulma.html.twig' %}
+
+{% block title %}Réservations pour {{ event.name }}{% endblock %}
+
+{% block content %}
+
+        
+          {{ component('Modal')}}
+        
+<div class="columns">
+  <div class="column is-one-third">
+<div class="card" {{ stimulus_controller('dropdown')}} >
+  <div class="card-image">
+    <figure class="image is-3by1">
+      {% if event.picture %}
+        <img src="/images/events/{{ event.picture }}" />
+      {% else %}
+        <img src="/images/events/placeholder.webp" />
+      {% endif %}
+    </figure>
+  </div>
+
+  <div class="card-footer">
+    <div class="dropdown card-footer-item is-flex is-justify-content-space-between is-align-items-center" data-action="click->dropdown#toggle">
+      <button>
+        <span><strong>{{ event.name }}</strong></span>        
+      </button>
+      <span class="icon is-small">
+        <twig:ux:icon name="bi:chevron-down" />&nbsp;
+      </span>
+      <div data-dropdown-target="menu" class="dropdown-menu">
+        <div class="dropdown-content">
+          {% for evt in events %}
+          {% if event.id != evt.id %}
+          <a href="{{ path('app_main_booking', {id: evt.id})}}" class="dropdown-item"><strong>{{ evt.name }}</strong><small> du {{ evt.startOn|date('d/m/y H:i', app_timezone) }} au {{ evt.endOn|date('d/m/y H:i', app_timezone)}}</small></a>
+          {% endif %}
+          {% endfor %}
+        </div>
+      </div>
+    </div>
+  </div>
+
+</div>
+
+</div>
+<div class="column">
+  <div class="hero is-small">
+    <div class="hero-body">
+      <h2 class="title is-2">{{ event.name }}</h2>
+      <p class="subtitle is-4">du {{ event.startOn|date('d/m/y H:i', app_timezone) }} au {{ event.endOn|date('d/m/y H:i', app_timezone)}}</p>
+      <article>
+        <p>{{ event.description }}</p>
+      </article>
+    </div>
+  </div>
+</div>
+</div>
+
+{% if not event.isHiddenPlanning %}
+<section>
+  <hr />
+  <div id="planning">
+    {{ component('Planning', {event: event,pathFullSlot: 'app_party_view'}) }}
+  </div>
+</section>
+{% endif %}
+
+{% endblock %}

+ 46 - 15
templates/main/index.html.twig

@@ -1,20 +1,51 @@
 {% extends 'bulma.html.twig' %}
 
-{% block title %}Hello MainController!{% endblock %}
+{% block title %}Bienvenue !{% endblock %}
 
 {% block content %}
-<style>
-    .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
-    .example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
-</style>
-
-<div class="example-wrapper">
-    <h1>Hello {{ controller_name }}! ✅</h1>
-
-    This friendly message is coming from:
-    <ul>
-        <li>Your controller at <code>/Users/garthh/Developpement/orgasso/src/Controller/MainController.php</code></li>
-        <li>Your template at <code>/Users/garthh/Developpement/orgasso/templates/main/index.html.twig</code></li>
-    </ul>
-</div>
+<div class="fixed-grid has-3-cols-fullhd has-3-cols-widescreen has-2-cols-desktop has-2-cols-tablet has-1-cols-mobile">
+        <div class="grid is-col-min-12">
+          {% for event in events %}
+
+          <div class="cell">
+            
+            <div class="card">
+              <div class="card-image">
+              <figure class="image is-3by1">
+
+                  {% if event.picture %}
+                  <img src="/images/events/{{ event.picture }}"  />
+                  {% else %}
+                  <img src="/images/events/placeholder.webp"  />
+                  {% endif %}
+
+              </figure>
+              </div>
+              <div class="card-content">
+                <div class="content text-limit-height">
+                  <p>
+                    <strong class="title is-4">{{ event.name }}</strong>{% if event.isPrivate %}&nbsp;<span class="tag">Privé</span>{% endif %}
+                    <br/>
+                    <span class="subtitle is-6"><span class="icon is-16x16"><twig:ux:icon name="bi:clock" />&nbsp;</span>du {{ event.startOn|date('d/m/y', app_timezone) }} à {{ event.startOn|date('h:i', app_timezone) }} au {{ event.endOn|date('d/m/y', app_timezone) }} à {{ event.endOn|date('h:i', app_timezone) }}</span>
+
+                    
+                    
+                  </p>
+                </div>
+
+
+              </div>
+              <div class="card-footer">
+                <a class="card-footer-item" href="{{ path('app_main_booking', {id: event.id}) }}">Réserver une place</a>
+                {% if is_granted('ROLE_MANAGER') or is_granted('ROLE_ADMIN') %}
+                <a class="card-footer-item" href="#">Gérer</a>
+                {% endif %} 
+              </div>
+            </div>
+            
+          </div>
+          {% endfor %}
+        </div>
+      </div>
+
 {% endblock %}

+ 8 - 3
templates/modal.html.twig

@@ -3,7 +3,12 @@
         <h1 class="title is-3">{% block title %}{% endblock %}</h1>
     </div>
     <section class="modal-card-body">
-{% block content %}
-{% endblock %}
-</section>
+        {% block content %}
+        {% endblock %}
+    </section>
+    {% block footer %}
+    <section class="modal-card-footer">
+        
+    </section>
+    {% endblock %}
 </div>

+ 9 - 2
templates/party/view.html.twig

@@ -1,4 +1,4 @@
-{% extends 'bulma.html.twig' %}
+{% extends 'modal.html.twig' %}
 
 {% block title %}{{ party.game.name }}{% endblock %}
 
@@ -17,4 +17,11 @@
     
 
 
-{% endblock %}
+{% endblock %}
+
+ {% block footer %}
+
+    <footer class="modal-card-footer">
+        M'inscrire
+    </footer>
+    {% endblock %}