Pārlūkot izejas kodu

génération des slots et affichage des plannings avec un composant twig dédié

garthh 20 stundas atpakaļ
vecāks
revīzija
98d52bd5e8

+ 124 - 0
assets/styles/app.css

@@ -12,4 +12,128 @@
 .small-icon-in-text {
     height: 1rem;
     
+}
+
+/* Styles pour les plannings */
+
+/* Style par défaut pour les cellules
+.planning-cell {
+  display: flex;
+  align-items: center;       
+  justify-content: center;  
+  height: 3rem;             
+  text-align: center;
+  padding: 0.5rem;
+  box-sizing: border-box;
+  overflow: hidden;        
+  white-space: nowrap;       
+  text-overflow: ellipsis;  
+}
+
+/* Style pour les cellules d'entête 
+.planning-cell-heading {
+    background: black;
+    color: white;
+    font-weight: bold;
+    border-bottom: 1px solid white;
+}
+
+/* Style pour les cellules pleine largeur (ex. en-tête de périodes)
+.planning-cell-wide {
+    background: lightgray;
+    border-bottom: 1px solid white;
+
+}
+
+/* Style pour les cellules disponibles 
+.planning-cell-free {
+    border-bottom: 1px solid lightgray;
+
+}
+
+/* Style pour les cellules vérouillées 
+.planning-cell-locked {
+    background-color: gray;
+    color: white;
+    border-bottom: 1px solid lightgray;
+
+}
+
+/* Style pour les cellules masquées 
+.planning-cell-hidden {
+    background-color: none;
+    border-bottom: 1px solid lightgray;
+}
+
+
+
+
+/* Base styles for all planning cells */
+
+body.is-dark-mode .planning-cell {
+  background-color: #1e1e1e;
+  color: #f5f5f5;
+}
+
+.planning-cell {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 3rem;
+  padding: 0.5rem;
+  box-sizing: border-box;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  text-align: center;
+  border-bottom: 1px solid #dbdbdb;
+  /*background-color: #ffffff;*/
+  font-size: 0.95rem;
+  transition: background-color 0.2s ease;
+}
+
+/* Header cells (e.g., "Espaces") */
+.planning-cell-heading {
+  /* background-color: #00d1b2; /* Bulma primary */
+  /* color: #ffffff;*/
+  font-weight: 600;
+  border-bottom: 1px solid #00b89c;
+}
+
+/* Wide header cells (e.g., time columns) */
+.planning-cell-wide {
+  background-color: #f5f5f5; /* Bulma grey-light */
+  color: #363636;
+  font-weight: 500;
+  border-bottom: 1px solid #ccc;
+}
+
+/* Free (available) slot */
+.planning-cell-free {
+  background-color: #effaf5;     /* Bulma success-light */
+  color: #0f8763;
+  border-bottom: 1px solid #ccc;
+}
+
+.planning-cell-free:hover {
+  background-color: #d0f0e6;
+}
+
+/* Locked slot */
+.planning-cell-locked {
+  background-color: #dbdbdb; /* Bulma gray */
+  color: #7a7a7a;
+  border-bottom: 1px solid #ccc;
+}
+
+/* Hidden slot */
+.planning-cell-hidden {
+  background-color: transparent;
+  color: transparent;
+  border-bottom: 1px solid #ccc;
+}
+
+/* Optional: highlight on hover globally */
+.planning-cell:hover:not(.planning-cell-hidden):not(.planning-cell-heading):not(.planning-cell-wide) {
+  filter: brightness(0.98);
 }

+ 37 - 0
src/Controller/Admin/EventConfig/GamemasterController.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace App\Controller\Admin\EventConfig;
+
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Requirement\Requirement;
+use Symfony\Component\Routing\Attribute\Route;
+use Doctrine\ORM\EntityManagerInterface;
+
+use App\Entity\Gamemaster;
+use App\Repository\GamemasterRepository;
+
+final class GamemasterController extends AbstractController
+{
+    #[Route('/admin/event/{id}/configure/gamemaster', name: 'app_admin_event_config_gamemaster')]
+    public function index(GamemasterRepository $gamemaster): Response
+    {
+        $gamematers = $repository->findAll();
+        return $this->render('gamemaster/index.html.twig', [
+            'controller_name' => 'GamemasterController',
+        ]);
+    }
+
+    #[Route('/admin/event/{id}/configure/gamemaster/toggle/', name: 'app_admin_event_config_gamemaster')]
+    public function toggle(GamemasterRepository $gamemaster): Response
+    {
+        $gamematers = $repository->findAll();
+        return $this->render('gamemaster/index.html.twig', [
+            'controller_name' => 'GamemasterController',
+        ]);
+    }
+
+    // Gérer les dispo de MJ par AJAX
+
+}

+ 1 - 1
src/Controller/Admin/EventConfig/PeriodController.php

@@ -42,10 +42,10 @@ final class PeriodController extends AbstractController
     #[Route('/admin/period/{id}/delete', name: 'app_admin_period_delete', requirements: ['id' => '\d+'], methods: ['GET'])]
     public function delete(?Period $period, EntityManagerInterface $manager): Response
     {
+        $event = $period->getEvent();
         if ($period->isLocked()) {
             $this->addFlash('danger', 'Cette période est utilisée dans les slots ! Supprimez les slots avant de supprimer la période !');
         } else {
-            $event = $period->getEvent();
             $manager->remove($period);
             $manager->flush();
         }

+ 3 - 3
src/Controller/Admin/EventConfig/SlotController.php

@@ -71,11 +71,11 @@ final class SlotController extends AbstractController
                         $newSlot->setPeriod($period);
                         $newSlot->setUnavailable(false);
                         // Ajout de la date de début = Index
-                        $newSlot->setStartOn($startIndex);
+                        $newSlot->setStartOn(clone $startIndex);
                         // on incrémente l'index de 30 minutes pour la suite
                         $startIndex->add(new \DateInterval('PT30M'));
                         // et on l'utilise aussi pour la fin
-                        $newSlot->setEndOn($startIndex);
+                        $newSlot->setEndOn(clone $startIndex);
                         $event->addSlot($newSlot);
                         // Hop, dans la BDD en attente de sauvegarde
                         $manager->persist($newSlot);
@@ -91,7 +91,7 @@ final class SlotController extends AbstractController
             }
             $manager->flush();
             $controlValue = count($event->getSlots());
-            $this->addFlash('info', $countGenSlot.' slots générés. '.$controlValue.' slots enregistés en base');
+            $this->addFlash('info', $countGenSlot.' slots générés. '.$controlValue.' slots enregistrés dans la base de données.');
         }
 
         // On retourne à la page de configuration

+ 1 - 1
src/Controller/Admin/EventConfig/SpaceController.php

@@ -42,10 +42,10 @@ final class SpaceController extends AbstractController
     #[Route('/admin/space/{id}/delete', name: 'app_admin_space_delete', requirements: ['id' => '\d+'], methods: ['GET'])]
     public function delete(?Space $space, EntityManagerInterface $manager): Response
     {
+        $event = $space->getEvent();
         if ($space->isLocked()) {
             $this->addFlash('danger', 'Cet espace est utilisé dans les slots ! Supprimez les slots avant de supprimer l\'espace !');
         } else {
-            $event = $space->getEvent();
             $manager->remove($space);
             $manager->flush();
         }

+ 38 - 0
src/Controller/Admin/EventController.php

@@ -155,4 +155,42 @@ final class EventController extends AbstractController
         ]);
     }
 
+    #[Route('/admin/event/{id}/publish', name: 'app_admin_event_config_publish', requirements: ['id' => Requirement::UUID_V7], methods: ['GET'])]
+    public function publish(?Event $event, EntityManagerInterface $manager): Response
+    {
+        if ($event->isPublished()) {
+            $this->addFlash('danger', 'Événement déjà publié !');
+        } else {
+            // Respecte les conditions de publications. Dispose de slots, au moins un MJ et un jeu...
+            if (count($event->getSlots()) > 0) {
+                $event->setPublished(true);
+                $manager->persist($event);
+                $manager->flush();
+                $this->addFlash('success', 'Événement publié.');
+            } else {
+                $this->addFlash('danger', 'Un événement doit disposer d\'au moins un slot, d\'un(e) meneur(euse) de jeu et d\'un jeu.');
+            }
+        }
+        return $this->redirectToRoute('app_admin_event_config', [
+            'id' => $event->getId()
+        ]);
+    }
+
+    #[Route('/admin/event/{id}/unpublish', name: 'app_admin_event_config_unpublish', requirements: ['id' => Requirement::UUID_V7], methods: ['GET'])]
+    public function unpublish(?Event $event, EntityManagerInterface $manager): Response
+    {
+        if ($event->isPublished()) {
+            $event->setPublished(false);
+            $manager->persist($event);
+            $manager->flush();
+            $this->addFlash('success', 'Événement dépublié !');
+        } else {
+            $this->addFlash('danger', 'Cet événement n\'est pas publié.');
+        }
+        return $this->redirectToRoute('app_admin_event_config', [
+            'id' => $event->getId()
+        ]);
+    }
+
+
 }

+ 1 - 0
src/Entity/Slot.php

@@ -110,4 +110,5 @@ class Slot
 
         return $this;
     }
+
 }

+ 1 - 1
src/Form/PeriodType.php

@@ -22,7 +22,7 @@ class PeriodType extends AbstractType
                 'view_timezone' => $_ENV['APP_TZ'],
                 'html5' => true,
                 'label_attr' => ['class' => 'label'],
-                'attr' => ['class' => 'input'],
+                'attr' => ['class' => 'input', 'autofocus' => true],
                 'row_attr' => ['class' => 'field'],
                 'help_attr' => ['class' => 'help'],   
             ])

+ 1 - 1
src/Form/SpaceType.php

@@ -19,7 +19,7 @@ class SpaceType extends AbstractType
                 'required' => true,
                 'help' => 'Entrez un nom pour cet espace.',
                 'label_attr' => ['class' => 'label'],
-                'attr' => ['class' => 'input'],
+                'attr' => ['class' => 'input', 'autofocus' => true],
                 'row_attr' => ['class' => 'field'],
                 'help_attr' => ['class' => 'help'],
             ])

+ 13 - 0
src/Repository/SlotRepository.php

@@ -16,6 +16,19 @@ class SlotRepository extends ServiceEntityRepository
         parent::__construct($registry, Slot::class);
     }
 
+    public function findASlot($date_ref, $space, $period): ?Slot
+    {
+        $qb = $this->createQueryBuilder('s')
+            ->where('s.startOn = :date_ref')
+            ->andWhere('s.space = :space')
+            ->andWhere('s.period = :period')
+            ->setParameter('date_ref', $date_ref)
+            ->setParameter('period', $period)
+            ->setParameter('space', $space)
+            ->getQuery();
+        return $qb->getOneOrNullResult();
+    }
+
     public function findNext($slot): ?Slot
     {
         $qb = $this->createQueryBuilder('s')

+ 58 - 0
src/Twig/Components/Planning.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Twig\Components;
+
+use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
+use App\Entity\Event;
+use App\Entity\Period;
+use App\Entity\Space;
+use App\Entity\Slot;
+use App\Service\DateTimeHelper;
+use App\Repository\SlotRepository;
+
+#[AsTwigComponent]
+class Planning
+{
+    public ?string $pathEmptySlot = null;
+    public ?string $pathFullSlot = null;
+    public ?string $pathLockedSlot = null;
+    public Event $event;
+    public bool $onlyUserSlot = false;
+    public bool $onlyEmptyParty = false;
+    public bool $displayLocked = false;
+
+    private DateTimeHelper $dateTimeHelper;
+    private SlotRepository $repository;
+
+    public function __construct(DateTimeHelper $dateTimeHelper, SlotRepository $repository)
+    {
+        $this->dateTimeHelper = $dateTimeHelper;
+        $this->repository = $repository;
+    }
+
+    public function getDateOrdered(Period $period): array
+    {
+        $dates = array();
+        $cursorDate = $this->dateTimeHelper->roundUpToNextSlot($period->getStartOn());
+        // Première date
+        while ($cursorDate < $period->getEndOn()) {
+            $dates[] = clone $cursorDate;
+            $cursorDate->add(new \DateInterval('PT30M'));
+        }
+
+        return $dates;
+    }
+
+    public function getThisSlot(\Datetime $dateRef, Space $space, Period $period): ?Slot
+    {
+        $slot = $this->repository->findASlot($dateRef, $space, $period);
+        return $slot;
+    }
+
+    public function timeTag(): bool
+    {
+        
+        return $timeTag;
+    }
+
+}

+ 54 - 8
templates/admin/event/config/index.html.twig

@@ -19,7 +19,7 @@
 
     <div class="tabs is-boxed">
         <ul>
-            <li class="is-active"><a>Aide</a></li>
+            <li class="is-active"><a>Accueil</a></li>
             <li><a href="{{ path('app_admin_event_config_space', {'id': event.id}) }}">Espaces</a></li>
             <li><a href="{{ path('app_admin_event_config_period', {'id': event.id}) }}">Périodes</a></li>
             <li><a href="{{ path('app_admin_event_config_slot', {'id': event.id}) }}">Slots</a></li>
@@ -29,14 +29,60 @@
     </div>
 
     <div id="tabs-content">
+      <div class="box">
+        <div class="block">
+          <div class="level">
+            <div class="level-item has-text-centered">
+              {% if event.getSpaces()|length > 0 %}
+                <div>
+                  <p class="title">{{ event.getSpaces()|length }}</p>
+                  <p class="heading">espaces</p>
+                  </div>
+              {% else %}
+                <p class="heading has-text-danger">Aucun espace</p>
+              {% endif %}
+            </div>
+            <div class="level-item has-text-centered">
+              {% if event.getPeriods()|length > 0 %}
+              <div>
+                <p class="title">{{ event.getPeriods()|length }}</p>
+                <p class="heading">périodes</p>
+              </div>
+              {% else %}
+                <p class="heading has-text-danger">Aucune période</p>
+              {% endif %}
+            </div>
+            <div class="level-item has-text-centered">
+              {% if event.getSlots()|length > 0 %}
+              <div>
+                <p class="title">{{ event.getSlots()|length }}</p>
+                <p class="heading">slots</p>
+                </div>
+              {% else %}
+                <p class="heading has-text-danger">Aucun slot</p>
+              {% endif %}
+            </div>
+          </div>
+        </div>
+      </div>
+      <article class="section">
+        <p>Déterminez des <strong>espaces</strong> et des <strong>périodes</strong> pour générer les <strong>slots</strong> permettant de modéliser le planning.
+          Les <strong>slots</strong> peuvent être régénérés à tout moment, mais peuvent entraîner la destruction de parties déjà enregistrées pour l'événement.
+          Les <strong>jeux</strong> sont sélectionnés à partir des <strong>MJ</strong> affectés à l'événement. Seul les <strong>jeux</strong> et <strong>MJ</strong> sélectionnés pourront être proposés pour créer le planning.
+          Les <strong>MJ</strong> disposant d'un compte pourront ajouter eux-mêmes leurs parties et définir leurs disponibilités.
+          Les <strong>périodes off</strong> permettent de neutraliser des <strong>slots</strong> dans le planning où aucune partie ne pourra être enregistrer dans l'<strong>espace spécifié</strong>.</p>
 
-  <div class="block">
-      <p>Déterminez des <strong>espaces</strong> et des <strong>périodes</strong> pour générer les <strong>slots</strong> permettant de modéliser le planning.
-      Les <strong>slots</strong> peuvent être régénérés à tout moment, mais peuvent entraîner la destruction de parties déjà enregistrées pour l'événement.
-      Les <strong>jeux</strong> sont sélectionnés à partir des <strong>MJ</strong> affectés à l'événement. Seul les <strong>jeux</strong> et <strong>MJ</strong> sélectionnés pourront être proposés pour créer le planning.
-      Les <strong>MJ</strong> disposant d'un compte pourront ajouter eux-mêmes leurs parties et définir leurs disponibilités.
-      Les <strong>périodes off</strong> permettent de neutraliser des <strong>slots</strong> dans le planning où aucune partie ne pourra être enregistrer dans l'<strong>espace spécifié</strong>.</p>
-    </div>
+        <p>La publication de l'événement permettra aux participants de s'inscrire. Si l'événement est publié, il ne sera plus possible de modifier les slots.</p>
+      </article>
+          <div class="block has-text-centered">
+        {% if event.getSlots()|length > 0 and not event.isPublished %}
+          <a href="{{ path('app_admin_event_config_publish', {id: event.id}) }}" class="button is-primary" data-turbo="false">Publier</a>
+        {% elseif event.isPublished %}
+          <a href="{{ path('app_admin_event_config_unpublish', {id: event.id}) }}" class="button is-danger" data-turbo="false">Dépublier</a>
+        {% else %}
+          <button href="#" class="button" disabled>Publier</button>
+        {% endif %}
+      </div>
 
 
     </div>

+ 1 - 1
templates/admin/event/config/period.html.twig

@@ -19,7 +19,7 @@
 
     <div class="tabs is-boxed">
         <ul>
-            <li><a href="{{ path('app_admin_event_config', {'id': event.id}) }}">Aide</a></li>
+            <li><a href="{{ path('app_admin_event_config', {'id': event.id}) }}">Accueil</a></li>
             <li><a href="{{ path('app_admin_event_config_space', {'id': event.id}) }}">Espaces</a></li>
             <li class="is-active"><a>Périodes</a></li>
             <li><a href="{{ path('app_admin_event_config_slot', {'id': event.id}) }}">Slots</a></li>

+ 52 - 49
templates/admin/event/config/slot.html.twig

@@ -19,7 +19,7 @@
 
     <div class="tabs is-boxed">
         <ul>
-            <li><a href="{{ path('app_admin_event_config', {'id': event.id}) }}">Aide</a></li>
+            <li><a href="{{ path('app_admin_event_config', {'id': event.id}) }}">Accueil</a></li>
             <li><a href="{{ path('app_admin_event_config_space', {'id': event.id}) }}">Espaces</a></li>
             <li><a href="{{ path('app_admin_event_config_period', {'id': event.id}) }}">Périodes</a></li>
             <li class="is-active"><a>Slots</a></li>
@@ -30,77 +30,80 @@
 
     <div id="tabs-content">
       <div class="container">
+        <div class="block">
+          <h3 class="title is-3">Configuration des slots</h3>
+        </div>
         <div class="columns">
           <div class="column">
-            <div class="block">
-              <h3 class="title is-3">Configuration des slots</h3>
-              <p>Les <strong>slot</strong> représentent des unités de temps de 30 minutes qui vont accueillir les animations afin de calculer simplement les disponibilités des espaces et/ou des MJ. Ils sont générés à partir des espaces et des périodes. Si vous modifiez, ajoutez ou supprimez des espaces ou des périodes après génération des slots, vous devrez les supprimer, puis relancer leur génération ensuite.</p>
-            </div>
+            <p>Les <strong>slot</strong> représentent des unités de temps de 30 minutes qui vont accueillir les animations afin de calculer simplement les disponibilités des espaces et/ou des MJ. Ils sont générés à partir des espaces et des périodes. Si vous modifiez, ajoutez ou supprimez des espaces ou des périodes après génération des slots, vous devrez les supprimer, puis relancer leur génération ensuite.</p>
+          </div>
+          <div class="column">
             <div class="box">
-              <div class="columns">
-                <div class="column">
+              <div class="level">
+                <div class="level-item has-text-centered">
                   {% if event.getSpaces()|length > 0 %}
-                    {{ event.getSpaces()|length }} espaces
+                    <p class="title">{{ event.getSpaces()|length }}</p>&nbsp;
+                    <p class="heading">espaces</p>
                   {% else %}
-                    Aucun espace
+                    <p class="heading">Aucun espace</p>
                   {% endif %}
                 </div>
-                <div class="column">
+                <div class="level-item has-text-centered">
                   {% if event.getPeriods()|length > 0 %}
-                    {{ event.getPeriods()|length }} périodes
+                    <p class="title">{{ event.getPeriods()|length }}</p>&nbsp;
+                    <p class="heading">périodes</p>
                   {% else %}
-                    Aucune période
+                    <p class="heading">Aucune période</p>
                   {% endif %}
                 </div>
-                <div class="column">
+                <div class="level-item has-text-centered">
                   {% if event.getSlots()|length > 0 %}
-                    {{ event.getSlots()|length }} slots
+                    <p class="title">{{ event.getSlots()|length }}</p>&nbsp;
+                    <p class="heading">slots</p>
                   {% else %}
-                    Aucun slot
-                  {% endif %}
-                </div>
-                <div class="column">
-                  {% if event.getPeriods()|length > 0 and  event.getSpaces()|length > 0 and event.getSlots()|length < 1 %}
-                    {#<a href="#" data-id="{{ path('app_main_event_config_slot_generate', {id: event.getId })}}" class="button is-primary"  {{ stimulus_controller('admin_confirm') }}>Générer les slots</a>#}
-                    <a href="{{ path('app_main_event_config_slot_generate', {id: event.getId })}}" data-turbo="false"  class="button is-primary">Générer les slots</a>
-                    
-                  {% elseif event.getSlots()|length > 0 %}
-                    <a href="#" data-id="{{ path('app_main_event_config_slot_delete', {id: event.getId })}}" class="button is-danger" {{ stimulus_controller('admin_confirm') }}>Supprimer les slots</a>
-                  {% else %}
-                    <button href="#" class="button" disabled>Générer les slots</button>
+                    <p class="heading">Aucun slot</p>
                   {% endif %}
                 </div>
               </div>
+              <div class="block has-text-centered">
+                {% if event.getPeriods()|length > 0 and  event.getSpaces()|length > 0 and event.getSlots()|length < 1 %}
+                  <a href="{{ path('app_main_event_config_slot_generate', {id: event.getId })}}" data-turbo="false"  class="button is-primary">Générer les slots</a>                   
+                {% elseif event.getSlots()|length > 0 and not event.isPublished %}
+                  <a href="#" data-id="{{ path('app_main_event_config_slot_delete', {id: event.getId })}}" class="button is-danger" {{ stimulus_controller('admin_confirm') }}>Supprimer les slots</a>
+                {% elseif event.getSlots()|length > 0 and event.isPublished %}
+                  <button href="#" class="button" disabled>Supprimer les slots</button>
+                {% else %}
+                  <button href="#" class="button" disabled>Générer les slots</button>
+                {% endif %}
+              </div>
             </div>
           </div>
-          <div class="column">
-            <div class="block">
-              <h3 class="title is-3">Aperçu du planning</h3>
-              <p>Cliquez sur les <strong>slots</strong> qui sont indisponibles.</p>
-            </div>
-            {% if event.getSlots()|length > 0 %}
-              CA ARRRIVE
-            {% else %}
-              <article class="message is-danger">
-                  <div class="message-header">
-                    <p>Aucun slot généré</p>
-                    <button class="delete" aria-label="delete"></button>
-                  </div>
-                  <div class="message-body">
-                      Le planning n'est pas encore consultable. Générez les slots à l'aide du bouton à gauche de l'écran.
-                  </div>
-              </article>
-            {% endif %}
-          </div>
-
-
+        </div>
+        <div class="block">
+          <h3 class="title is-3">Aperçu du planning</h3>
+          <p>Cliquez sur les <strong>slots</strong> pour les basculer <span class="icon"><twig:ux:icon name="bi:lock-fill"/></span>&nbsp;<em>indisponible</em> (aucune partie ne pourra être organisé sur ce slot) ou <em>disponible</em>. <em class="has-text-danger">Fonctionnalité en cours de développement.</em></p>
+        </div>
+        {% if event.getSlots()|length > 0 %}
+        <div id="planning">
+          {{ component('Planning', {event: event, displayLocked: true}) }}
+        </div>
+        {% else %}
+          <article class="message is-danger">
+              <div class="message-header">
+                <p>Aucun slot généré</p>
+                <button class="delete" aria-label="delete"></button>
+              </div>
+              <div class="message-body">
+                  Le planning n'est pas encore consultable. Générez les slots à l'aide du bouton à gauche de l'écran.
+              </div>
+          </article>
+        {% endif %}
+      </div>
 
 
-        </div>
 
 
 
-      </div>
     </div>
 
 

+ 1 - 1
templates/admin/event/config/space.html.twig

@@ -19,7 +19,7 @@
 
     <div class="tabs is-boxed">
         <ul>
-            <li><a href="{{ path('app_admin_event_config', {'id': event.id}) }}">Aide</a></li>
+            <li><a href="{{ path('app_admin_event_config', {'id': event.id}) }}">Accueil</a></li>
             <li class="is-active"><a>Espaces</a></li>
             <li><a href="{{ path('app_admin_event_config_period', {'id': event.id}) }}">Périodes</a></li>
             <li><a href="{{ path('app_admin_event_config_slot', {'id': event.id}) }}">Slots</a></li>

+ 73 - 0
templates/components/Planning.html.twig

@@ -0,0 +1,73 @@
+{# Template pour les plannings #}
+
+{% set cols = event.getSpaces()|length +1 %}
+
+{# début du block de grille #}
+<div class="block">
+    {# Nombre de colonnes = nombre d'espaces + colonne d'intro #}
+    <div class="fixed-grid has-{{ cols }}-cols">
+        <div class="grid is-gap-0">
+
+            {# en-tête avec les noms des espaces #}
+            <div class="cell planning-cell planning-cell-heading has-background-primary-light">
+                Espaces
+            </div>
+            {% for space in event.getSpaces() %}
+            <div class="cell planning-cell planning-cell-heading has-background-primary-light">
+                {{ space.name }}
+            </div>
+            {% endfor %}
+
+            {# On ajoute les horaires période par période #}
+            {% for period in event.getPeriods() %}
+                <div class="cell planning-cell planning-cell-wide is-col-span-{{ cols }}">
+                    Période du {{ period.startOn|date('d/m/Y H:i', app_timezone) }} à {{ period.endOn|date('H:i', app_timezone) }}
+                </div>
+
+                {# On affiche tous les slots de la période, space par space #}
+                {% for dateRef in this.getDateOrdered(period) %}
+                    <div class="cell planning-cell planning-cell-heading has-background-primary-light">
+                        {{ dateRef|date('H:i', app_timezone) }}
+                    </div>
+                    {% for space in event.getSpaces() %}
+                        {# extraction du slot de cet espace, ce moment et cette période #}
+                        {% set thisSlot = this.getThisSlot(dateRef, space, period) %}
+                        
+                        {# si le slot est Indisponible #}
+                        {% if thisSlot.unavailable %}
+                            {% if displayLocked %}
+                            <div class="cell planning-cell planning-cell-locked" data-id="thisSlot.id">
+                                <div class="icon"><twig:ux:icon name="bi:lock-fill" /></div>
+                            </div>
+                            {% else %}
+                            <div class="cell planning-cell planning-cell-hidden">
+                            </div>
+                            {% endif %}
+                        {% endif %}
+                        {# si une partie est sur le slot #}
+                        {# TODO : à compléter quand les parties seront ajoutées #}
+                        {# si le slot est disponible et sans partie #}
+                        {% if not thisSlot.unavailable %}
+                            {% if pathEmptySlot %}
+                            <a href="{{ path(pathEmptySlot, {id: thisSlot.id}) }}">
+                            <div class="cell planning-cell planning-cell-free" data-id="thisSlot.id">
+                                <div class="icon"><twig:ux:icon name="bi:plus-circle" /></div>
+                            </div>
+                            </a>
+                            {% else %}
+                            <div class="cell planning-cell planning-cell-free" data-id="thisSlot.id">
+                                
+                            </div>                            
+                            {% endif %}
+
+                        {% endif %}               
+
+                    {% endfor %}
+                {% endfor %}
+
+            {% endfor %}
+
+{# fin du block de grille #}
+        </div>
+    </div>
+</div>