Browse Source

gestion MJ/jeux d'un événement

garthh 17 hours ago
parent
commit
8d6c1643d1

+ 71 - 0
assets/controllers/gamemaster-toggle_controller.js

@@ -0,0 +1,71 @@
+import { Controller } from '@hotwired/stimulus';
+
+export default class extends Controller {
+  static values = {
+    eventId: String,       // UUIDv7 de l'événement
+    gamemasterId: String   // UUIDv7 du MJ
+  }
+
+  connect() {
+    console.log("Stimulus: toggle gamemaster actif");
+    this.element.addEventListener('click', this.toggleGamemaster.bind(this));
+  }
+
+  async toggleGamemaster(event) {
+    event.preventDefault();
+
+    const eventId = this.eventIdValue;
+    const gamemasterId = this.gamemasterIdValue;
+
+    if (!eventId || !gamemasterId) {
+      console.error("UUID manquant pour event ou gamemaster");
+      return;
+    }
+
+    const url = `/admin/event/${eventId}/configure/gamemaster/toggle/`;
+    console.log(gamemasterId);
+
+    try {
+      const response = await fetch(url, {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+          'X-Requested-With': 'XMLHttpRequest',
+          //'X-CSRF-TOKEN': this.getCsrfToken()
+        },
+        body: JSON.stringify({ gamemasterId: gamemasterId })
+      });
+
+      if (!response.ok) {
+        console.error("Erreur HTTP :", response.status);
+        return;
+      }
+
+      const data = await response.json();
+      const article = this.element.querySelector('article.media');
+
+      if (!article) {
+        console.error("Élément <article> introuvable");
+        return;
+      }
+
+      if (data.status === "DISABLE") {
+        article.style.filter = "grayscale(1)";
+        article.style.opacity = "0.3";
+      } else if (data.status === "ENABLE") {
+        article.style.filter = "none";
+        article.style.opacity = "1";
+      } else {
+        console.warn("Statut de réponse inattendu :", data.status);
+      }
+
+    } catch (error) {
+      console.error("Erreur réseau :", error);
+    }
+  }
+
+  getCsrfToken() {
+    const token = document.querySelector('meta[name="csrf-token"]');
+    return token ? token.content : '';
+  }
+}

+ 7 - 0
assets/styles/app.css

@@ -14,6 +14,13 @@
     
 }
 
+.text-limit-height {
+  height: 5rem;
+  overflow: hidden;        
+   
+  text-overflow: ellipsis;  
+}
+
 /* Styles pour les plannings */
 
 /* Style par défaut pour les cellules

+ 41 - 0
migrations/Version20250802172752.php

@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DoctrineMigrations;
+
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\Migrations\AbstractMigration;
+
+/**
+ * Auto-generated Migration: Please modify to your needs!
+ */
+final class Version20250802172752 extends AbstractMigration
+{
+    public function getDescription(): string
+    {
+        return '';
+    }
+
+    public function up(Schema $schema): void
+    {
+        // this up() migration is auto-generated, please modify it to your needs
+        $this->addSql('CREATE TABLE event_gamemaster (event_id BINARY(16) NOT NULL COMMENT \'(DC2Type:uuid)\', gamemaster_id BINARY(16) NOT NULL COMMENT \'(DC2Type:uuid)\', INDEX IDX_CDECEFEA71F7E88B (event_id), INDEX IDX_CDECEFEA96376157 (gamemaster_id), PRIMARY KEY(event_id, gamemaster_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
+        $this->addSql('CREATE TABLE event_game (event_id BINARY(16) NOT NULL COMMENT \'(DC2Type:uuid)\', game_id BINARY(16) NOT NULL COMMENT \'(DC2Type:uuid)\', INDEX IDX_3CE07D2771F7E88B (event_id), INDEX IDX_3CE07D27E48FD905 (game_id), PRIMARY KEY(event_id, game_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
+        $this->addSql('ALTER TABLE event_gamemaster ADD CONSTRAINT FK_CDECEFEA71F7E88B FOREIGN KEY (event_id) REFERENCES event (id) ON DELETE CASCADE');
+        $this->addSql('ALTER TABLE event_gamemaster ADD CONSTRAINT FK_CDECEFEA96376157 FOREIGN KEY (gamemaster_id) REFERENCES gamemaster (id) ON DELETE CASCADE');
+        $this->addSql('ALTER TABLE event_game ADD CONSTRAINT FK_3CE07D2771F7E88B FOREIGN KEY (event_id) REFERENCES event (id) ON DELETE CASCADE');
+        $this->addSql('ALTER TABLE event_game ADD CONSTRAINT FK_3CE07D27E48FD905 FOREIGN KEY (game_id) REFERENCES game (id) ON DELETE CASCADE');
+    }
+
+    public function down(Schema $schema): void
+    {
+        // this down() migration is auto-generated, please modify it to your needs
+        $this->addSql('ALTER TABLE event_gamemaster DROP FOREIGN KEY FK_CDECEFEA71F7E88B');
+        $this->addSql('ALTER TABLE event_gamemaster DROP FOREIGN KEY FK_CDECEFEA96376157');
+        $this->addSql('ALTER TABLE event_game DROP FOREIGN KEY FK_3CE07D2771F7E88B');
+        $this->addSql('ALTER TABLE event_game DROP FOREIGN KEY FK_3CE07D27E48FD905');
+        $this->addSql('DROP TABLE event_gamemaster');
+        $this->addSql('DROP TABLE event_game');
+    }
+}

+ 40 - 0
src/Controller/Admin/EventConfig/GameController.php

@@ -0,0 +1,40 @@
+<?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\Event;
+use App\Entity\Game;
+use App\Repository\GameRepository;
+
+final class GameController extends AbstractController
+{
+    #[Route('/admin/event/{id}/configure/game', name: 'app_admin_event_config_game', requirements: ['id' => Requirement::UUID_V7], methods: ['GET', 'POST'])]
+    public function index(?Event $event, GameRepository $repository): Response
+    {
+        $gamesPlayed = $event->getGameAssigned();
+        $gamesNotPlayed = array();
+
+        // lister tous les jeux de tous les MJ pour trouver ceux qui ne sont pas assignés
+        foreach($event->getGamemastersAssigned() as $gamemaster) {
+            foreach($gamemaster->getGamesCanMaster() as $game) {
+                if (!in_array($game, $gamesPlayed->toArray())) {
+                    $gamesNotPlayed[] = $game;
+                }
+            }
+        }
+
+        return $this->render('admin/event/config/game.html.twig', [
+            'gamesPlayed' => $gamesPlayed,
+            'gamesNotPlayed' => $gamesNotPlayed,
+            'event' => $event,
+        ]);
+    }
+
+}

+ 56 - 13
src/Controller/Admin/EventConfig/GamemasterController.php

@@ -5,33 +5,76 @@ namespace App\Controller\Admin\EventConfig;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\Routing\Requirement\Requirement;
 use Symfony\Component\Routing\Attribute\Route;
 use Doctrine\ORM\EntityManagerInterface;
 
+use App\Entity\Event;
 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
+    #[Route('/admin/event/{id}/configure/gamemaster', name: 'app_admin_event_config_gamemaster', requirements: ['id' => Requirement::UUID_V7], methods: ['GET', 'POST'])]
+    public function index(?Event $event, GamemasterRepository $repository): Response
     {
-        $gamematers = $repository->findAll();
-        return $this->render('gamemaster/index.html.twig', [
-            'controller_name' => 'GamemasterController',
+        $gamemasters = $repository->findAll();
+        return $this->render('admin/event/config/gamemaster.html.twig', [
+            'gamemasters' => $gamemasters,
+            'event' => $event,
         ]);
     }
 
-    #[Route('/admin/event/{id}/configure/gamemaster/toggle/', name: 'app_admin_event_config_gamemaster')]
-    public function toggle(GamemasterRepository $gamemaster): Response
+    #[Route('/admin/event/{id}/configure/gamemaster/toggle/', name: 'app_admin_event_config_gamemaster_toggle', requirements: ['id' => Requirement::UUID_V7], methods: ['POST'])]
+    public function toggle(?Event $event, Request $request, GamemasterRepository $repository, EntityManagerInterface $entityManager): JsonResponse
     {
-        $gamematers = $repository->findAll();
-        return $this->render('gamemaster/index.html.twig', [
-            'controller_name' => 'GamemasterController',
-        ]);
-    }
+        // Récupération du MJ
+        $data = json_decode($request->getContent(), true);
+        $gamemasterID = $data['gamemasterId'] ?? null;
+        if (!$gamemasterID) {
+            return new JsonResponse(['success' => false]);
+        }
+        $gamemaster = $repository->findByStrID($gamemasterID);
+        
+        if (in_array($gamemaster, $event->getGamemastersAssigned()->toArray()))
+        {
+            // Enlever le MJ de la liste
+            $event->removeGamemastersAssigned($gamemaster);
+            // Enlever les jeux spécifiques à ce MJ des jeux de l'événement
+            foreach($gamemaster->getGamesCanMaster() as $game) {
+                $notFound = true;
+                foreach($event->getGamemastersAssigned() as $otherGM) {
+                    if (in_array($game, $otherGM->getGamesCanMaster()->toArray())) {
+                        $notFound = false;
+                    }
+                }
+                if ($notFound) {
+                    $event->removeGameAssigned($game);
+                }
+            }
+
+            // marquer la réponse
+            $state='DISABLE';
+
+        } else {
+            // Ajouter le MJ à la liste
+            $event->addGamemastersAssigned($gamemaster);
+            // Ajouter les jeux spécifiques à ce MJ dans les jeux de l'événement
+            foreach($gamemaster->getGamesCanMaster() as $game) {
+                if (!in_array($game, $event->getGameAssigned()->toArray())) {
+                    $event->addGameAssigned($game);
+                }
+            }
+            // marquer la réponse
+            $state='ENABLE';
 
-    // Gérer les dispo de MJ par AJAX
+        }
+        $entityManager->persist($event);
+        $entityManager->flush();
+
+        return new JsonResponse(['success' => true, 'status' => $state]);
+
+    }
 
 }

+ 24 - 0
src/Controller/Admin/EventConfig/PartyController.php

@@ -0,0 +1,24 @@
+<?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\Event;
+
+final class PartyController extends AbstractController
+{
+    #[Route('/admin/event/{id}/configure/party', name: 'app_admin_event_config_party', requirements: ['id' => Requirement::UUID_V7], methods: ['GET', 'POST'])]
+    public function index(?Event $event): Response
+    {
+        return $this->render('admin/event/config/party.html.twig', [
+            'event' => $event,
+        ]);
+    }
+
+}

+ 62 - 0
src/Entity/Event.php

@@ -71,11 +71,25 @@ class Event
     #[ORM\OneToMany(targetEntity: Slot::class, mappedBy: 'event', orphanRemoval: true)]
     private Collection $slots;
 
+    /**
+     * @var Collection<int, Gamemaster>
+     */
+    #[ORM\ManyToMany(targetEntity: Gamemaster::class, inversedBy: 'eventsAssignedTo')]
+    private Collection $gamemastersAssigned;
+
+    /**
+     * @var Collection<int, Game>
+     */
+    #[ORM\ManyToMany(targetEntity: Game::class, inversedBy: 'eventsAssignedTo')]
+    private Collection $gameAssigned;
+
     public function __construct()
     {
         $this->spaces = new ArrayCollection();
         $this->periods = new ArrayCollection();
         $this->slots = new ArrayCollection();
+        $this->gamemastersAssigned = new ArrayCollection();
+        $this->gameAssigned = new ArrayCollection();
     }
 
     public function getId(): ?Uuid
@@ -304,4 +318,52 @@ class Event
 
         return $this;
     }
+
+    /**
+     * @return Collection<int, Gamemaster>
+     */
+    public function getGamemastersAssigned(): Collection
+    {
+        return $this->gamemastersAssigned;
+    }
+
+    public function addGamemastersAssigned(Gamemaster $gamemastersAssigned): static
+    {
+        if (!$this->gamemastersAssigned->contains($gamemastersAssigned)) {
+            $this->gamemastersAssigned->add($gamemastersAssigned);
+        }
+
+        return $this;
+    }
+
+    public function removeGamemastersAssigned(Gamemaster $gamemastersAssigned): static
+    {
+        $this->gamemastersAssigned->removeElement($gamemastersAssigned);
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, Game>
+     */
+    public function getGameAssigned(): Collection
+    {
+        return $this->gameAssigned;
+    }
+
+    public function addGameAssigned(Game $gameAssigned): static
+    {
+        if (!$this->gameAssigned->contains($gameAssigned)) {
+            $this->gameAssigned->add($gameAssigned);
+        }
+
+        return $this;
+    }
+
+    public function removeGameAssigned(Game $gameAssigned): static
+    {
+        $this->gameAssigned->removeElement($gameAssigned);
+
+        return $this;
+    }
 }

+ 34 - 0
src/Entity/Game.php

@@ -71,11 +71,18 @@ class Game
     #[ORM\ManyToMany(targetEntity: Gamemaster::class, mappedBy: 'gamesCanMaster')]
     private Collection $gamemasters;
 
+    /**
+     * @var Collection<int, Event>
+     */
+    #[ORM\ManyToMany(targetEntity: Event::class, mappedBy: 'gameAssigned')]
+    private Collection $eventsAssignedTo;
+
     public function __construct()
     {
         $this->genre = new ArrayCollection();
         $this->id = Uuid::v7();
         $this->gamemasters = new ArrayCollection();
+        $this->eventsAssignedTo = new ArrayCollection();
     }
 
     public function getId(): ?Uuid
@@ -297,4 +304,31 @@ class Game
         return $this;
     }
 
+    /**
+     * @return Collection<int, Event>
+     */
+    public function getEventsAssignedTo(): Collection
+    {
+        return $this->eventsAssignedTo;
+    }
+
+    public function addEventsAssignedTo(Event $eventsAssignedTo): static
+    {
+        if (!$this->eventsAssignedTo->contains($eventsAssignedTo)) {
+            $this->eventsAssignedTo->add($eventsAssignedTo);
+            $eventsAssignedTo->addGameAssigned($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEventsAssignedTo(Event $eventsAssignedTo): static
+    {
+        if ($this->eventsAssignedTo->removeElement($eventsAssignedTo)) {
+            $eventsAssignedTo->removeGameAssigned($this);
+        }
+
+        return $this;
+    }
+
 }

+ 34 - 0
src/Entity/Gamemaster.php

@@ -60,9 +60,16 @@ class Gamemaster
     #[ORM\Column(length: 255, nullable: true)]
     private ?string $slug = null;
 
+    /**
+     * @var Collection<int, Event>
+     */
+    #[ORM\ManyToMany(targetEntity: Event::class, mappedBy: 'gamemastersAssigned')]
+    private Collection $eventsAssignedTo;
+
     public function __construct()
     {
         $this->gamesCanMaster = new ArrayCollection();
+        $this->eventsAssignedTo = new ArrayCollection();
     }
 
     public function getId(): ?Uuid
@@ -224,5 +231,32 @@ class Gamemaster
         return $this;
     }
 
+    /**
+     * @return Collection<int, Event>
+     */
+    public function getEventsAssignedTo(): Collection
+    {
+        return $this->eventsAssignedTo;
+    }
+
+    public function addEventsAssignedTo(Event $eventsAssignedTo): static
+    {
+        if (!$this->eventsAssignedTo->contains($eventsAssignedTo)) {
+            $this->eventsAssignedTo->add($eventsAssignedTo);
+            $eventsAssignedTo->addGamemastersAssigned($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEventsAssignedTo(Event $eventsAssignedTo): static
+    {
+        if ($this->eventsAssignedTo->removeElement($eventsAssignedTo)) {
+            $eventsAssignedTo->removeGamemastersAssigned($this);
+        }
+
+        return $this;
+    }
+
 
 }

+ 16 - 0
src/Repository/GamemasterRepository.php

@@ -5,6 +5,7 @@ namespace App\Repository;
 use App\Entity\Gamemaster;
 use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 use Doctrine\Persistence\ManagerRegistry;
+use Symfony\Component\Uid\UuidV7;
 
 /**
  * @extends ServiceEntityRepository<Gamemaster>
@@ -34,6 +35,21 @@ class GamemasterRepository extends ServiceEntityRepository
             ->getResult();
     }
 
+    public function findByStrID(string $id): ?Gamemaster
+    {
+        if (!UuidV7::isValid($id)) {
+            return null;
+        }
+
+        try {
+            $uuid = UuidV7::fromString($id);
+        } catch (\Throwable $e) {
+            return null;
+        }
+
+        return $this->find($uuid);
+    }
+
     //    /**
     //     * @return Gamemaster[] Returns an array of Gamemaster objects
     //     */

+ 129 - 0
templates/admin/event/config/game.html.twig

@@ -0,0 +1,129 @@
+{% extends 'bulma.html.twig' %}
+
+{% block title %}Administration > Événement > Configurer{% endblock %}
+
+{% block content %}
+
+    <nav class="breadcrumb has-arrow-separator" aria-label="breadcrumbs">
+      <ul>
+        <li><a href="{{ path('app_main') }}">Accueil</a></li>
+        <li><a href="{{ path('app_admin') }}">Administration</a></li>
+        <li><a href="{{ path('app_admin_event') }}">Gestion des événements</a></li>
+        <li class="is-active"><a>Configuration</a></li>
+      </ul>
+    </nav>
+
+    <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) }}
+    </div>
+
+    <div class="tabs is-boxed">
+        <ul>
+            <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><a href="{{ path('app_admin_event_config_slot', {'id': event.id}) }}">Slots</a></li>
+            <li><a href="{{ path('app_admin_event_config_gamemaster', {'id': event.id}) }}">Meneur(euse)s de jeu</a></li>
+            <li class="is-active"><a>Jeux</a></li>
+            <li><a href="{{ path('app_admin_event_config_party', {'id': event.id}) }}">Parties</a></li>
+        </ul>
+    </div>
+
+    <div id="tabs-content">
+
+    {% if event.getGamemastersAssigned|length < 1 %}
+            <article class="message is-danger">
+              <div class="message-header">
+                <p>Aucun(e) MJ assigné(e)</p>
+              </div>
+              <div class="message-body">
+                  La liste des jeux de rôle d'un événement dépend des MJ qui ont été assigné(e)s. Vous devez assigner des MJ avant de pouvoir choisir les jeux.
+              </div>
+          </article>
+    {% else %}
+      <div class="content">
+        <h3 class="title is-3">Assignez les jeux</h3>
+        <p>Cliquez sur les jeux à supprimer de cet événement.  <em class="has-text-danger">Fonctionnalité en cours de développement.</em></p>
+      </div>
+
+      <div class="grid is-col-min-12">
+        {% for game in gamesPlayed %}
+
+        <div class="cell">
+          
+          <div class="card">
+            <div class="card-image">
+            <figure class="image is-3by1">
+
+                {% if game.picture %}
+                <img src="/images/games/{{ game.picture }}"  />
+                {% else %}
+                
+                {% endif %}
+
+            </figure>
+            </div>
+            <div class="card-content">
+              <div class="content text-limit-height">
+                <p>
+                  <strong>{{ game.name }}</strong>
+                  <br/>
+                  {% for genre in game.genre %}<span class="tag is-info is-light">{{ genre.genre }}</span> {% endfor %}
+                  
+                  
+                </p>
+              </div>
+            </div>
+          </div>
+          
+        </div>
+        {% endfor %}
+      </div>
+
+            <div class="content">
+        <h3 class="title is-3">Autres jeux disponibles</h3>
+        <p>Cliquez sur les jeux à ajouter à cet événement.  <em class="has-text-danger">Fonctionnalité en cours de développement.</em></p>
+      </div>
+
+      <div class="grid is-col-min-12">
+        {% for game in gamesNotPlayed %}
+
+        <div class="cell">
+          
+          <div class="card">
+            <div class="card-image">
+            <figure class="image is-3by1">
+
+                {% if game.picture %}
+                <img src="/images/games/{{ game.picture }}"  />
+                {% else %}
+                
+                {% endif %}
+
+            </figure>
+            </div>
+            <div class="card-content">
+              <div class="content text-limit-height">
+                <p>
+                  <strong>{{ game.name }}</strong>
+                  <br/>
+                  {% for genre in game.genre %}<span class="tag is-info is-light">{{ genre.genre }}</span> {% endfor %}
+                  
+                  
+                </p>
+              </div>
+            </div>
+          </div>
+          
+        </div>
+        {% endfor %}
+      </div>
+
+
+
+
+    {% endif %}
+ 
+    </div>
+
+{% endblock %}

+ 71 - 0
templates/admin/event/config/gamemaster.html.twig

@@ -0,0 +1,71 @@
+{% extends 'bulma.html.twig' %}
+
+{% block title %}Administration > Événement > Configurer{% endblock %}
+
+{% block content %}
+
+    <nav class="breadcrumb has-arrow-separator" aria-label="breadcrumbs">
+      <ul>
+        <li><a href="{{ path('app_main') }}">Accueil</a></li>
+        <li><a href="{{ path('app_admin') }}">Administration</a></li>
+        <li><a href="{{ path('app_admin_event') }}">Gestion des événements</a></li>
+        <li class="is-active"><a>Configuration</a></li>
+      </ul>
+    </nav>
+
+    <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) }}
+    </div>
+
+    <div class="tabs is-boxed">
+        <ul>
+            <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><a href="{{ path('app_admin_event_config_slot', {'id': event.id}) }}">Slots</a></li>
+            <li class="is-active"><a>Meneur(euse)s de jeu</a></li>
+            <li><a href="{{ path('app_admin_event_config_game', {'id': event.id}) }}">Jeux</a></li>
+            <li><a href="{{ path('app_admin_event_config_party', {'id': event.id}) }}">Parties</a></li>
+        </ul>
+    </div>
+
+    <div id="tabs-content">
+
+      <div class="content">
+        <h3 class="title is-3">Assignez les MJ</h3>
+        <p>Cliquez sur les MJ à assigner à cet événement (un second clic pour les désassigner). La liste des jeux sera adaptées aux jeux que les MJ peuvent animer.</p>
+      </div>
+
+      <div class="grid is-col-min-12">
+        {% for gamemaster in gamemasters %}
+
+        <div class="cell">
+          <div class="box" data-gamemaster-toggle-gamemaster-id-value="{{ gamemaster.id }}" data-gamemaster-toggle-event-id-value="{{ event.id }}" {{ stimulus_controller('gamemaster-toggle') }}>
+          <article class="media"{% if gamemaster not in event.getGamemastersAssigned %} style="filter:grayscale(1) opacity(0.3);"{% endif %}>
+            <figure class="media-left">
+              <p class="image is-64x64">
+                {% if gamemaster.picture %}
+                <img src="/images/gamemasters/{{ gamemaster.picture }}"  />
+                {% else %}
+                <twig:ux:icon name="bi:person-fill"/>
+                {% endif %}
+              </p>
+            </figure>
+            <div class="media-content">
+              <div class="content">
+                <p>
+                  <strong>{{ gamemaster.preferedName }}</strong>
+                  <br/>
+                  {{ gamemaster.description }}
+                </p>
+              </div>
+            </div>
+          </article>
+          </div>
+        </div>
+        {% endfor %}
+      </div>
+
+    </div>
+
+{% endblock %}

+ 24 - 3
templates/admin/event/config/index.html.twig

@@ -23,8 +23,9 @@
             <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>
-            <li><a>Meneur(euse)s de jeu</a></li>
-            <li><a>Jeux</a></li>
+            <li><a href="{{ path('app_admin_event_config_gamemaster', {'id': event.id}) }}">Meneur(euse)s de jeu</a></li>
+            <li><a href="{{ path('app_admin_event_config_game', {'id': event.id}) }}">Jeux</a></li>
+            <li><a href="{{ path('app_admin_event_config_party', {'id': event.id}) }}">Parties</a></li>
         </ul>
     </div>
 
@@ -62,6 +63,26 @@
                 <p class="heading has-text-danger">Aucun slot</p>
               {% endif %}
             </div>
+            <div class="level-item has-text-centered">
+              {% if event.getGamemastersAssigned()|length > 0 %}
+              <div>
+                <p class="title">{{ event.getGamemastersAssigned()|length }}</p>
+                <p class="heading">MJ</p>
+                </div>
+              {% else %}
+                <p class="heading has-text-danger">Aucun(e) MJ</p>
+              {% endif %}
+            </div>
+            <div class="level-item has-text-centered">
+              {% if event.getGameAssigned()|length > 0 %}
+              <div>
+                <p class="title">{{ event.getGameAssigned()|length }}</p>
+                <p class="heading">jeux</p>
+                </div>
+              {% else %}
+                <p class="heading has-text-danger">Aucun jeu</p>
+              {% endif %}
+            </div>
           </div>
         </div>
       </div>
@@ -75,7 +96,7 @@
         <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 %}
+        {% if event.getSlots()|length > 0 and event.getGamemastersAssigned()|length > 0 and event.getGameAssigned()|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>

+ 51 - 0
templates/admin/event/config/party.html.twig

@@ -0,0 +1,51 @@
+{% extends 'bulma.html.twig' %}
+
+{% block title %}Administration > Événement > Configurer{% endblock %}
+
+{% block content %}
+
+    <nav class="breadcrumb has-arrow-separator" aria-label="breadcrumbs">
+      <ul>
+        <li><a href="{{ path('app_main') }}">Accueil</a></li>
+        <li><a href="{{ path('app_admin') }}">Administration</a></li>
+        <li><a href="{{ path('app_admin_event') }}">Gestion des événements</a></li>
+        <li class="is-active"><a>Configuration</a></li>
+      </ul>
+    </nav>
+
+    <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) }}
+    </div>
+
+    <div class="tabs is-boxed">
+        <ul>
+            <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><a href="{{ path('app_admin_event_config_slot', {'id': event.id}) }}">Slots</a></li>
+            <li><a href="{{ path('app_admin_event_config_gamemaster', {'id': event.id}) }}">Meneur(euse)s de jeu</a></li>
+            <li><a href="{{ path('app_admin_event_config_game', {'id': event.id}) }}">Jeux</a></li>
+            <li class="is-active"><a>Parties</a></li>
+        </ul>
+    </div>
+
+    <div id="tabs-content">
+
+    {% if event.getGameAssigned|length < 1 %}
+            <article class="message is-danger">
+              <div class="message-header">
+                <p>Aucun jeu de rôle assigné</p>
+              </div>
+              <div class="message-body">
+                  La liste des jeux de rôle doit être déterminée pour enregistrer des parties sur le planning de cet événement.
+              </div>
+          </article>
+    {% else %}
+        <div id="planning">
+          {{ component('Planning', {event: event, pathEmptySlot: 'app_main', pathFullSlot: 'app_main'}) }}
+        </div>
+    {% endif %}
+
+    </div>
+
+{% endblock %}

+ 3 - 2
templates/admin/event/config/period.html.twig

@@ -23,8 +23,9 @@
             <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>
-            <li><a>Meneur(euse)s de jeu</a></li>
-            <li><a>Jeux</a></li>
+            <li><a href="{{ path('app_admin_event_config_gamemaster', {'id': event.id}) }}">Meneur(euse)s de jeu</a></li>
+             <li><a href="{{ path('app_admin_event_config_game', {'id': event.id}) }}">Jeux</a></li>
+            <li><a href="{{ path('app_admin_event_config_party', {'id': event.id}) }}">Parties</a></li>
         </ul>
     </div>
 

+ 6 - 4
templates/admin/event/config/slot.html.twig

@@ -23,8 +23,9 @@
             <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>
-            <li><a>Meneur(euse)s de jeu</a></li>
-            <li><a>Jeux</a></li>
+            <li><a href="{{ path('app_admin_event_config_gamemaster', {'id': event.id}) }}">Meneur(euse)s de jeu</a></li>
+            <li><a href="{{ path('app_admin_event_config_game', {'id': event.id}) }}">Jeux</a></li>
+            <li><a href="{{ path('app_admin_event_config_party', {'id': event.id}) }}">Parties</a></li>
         </ul>
     </div>
 
@@ -80,8 +81,10 @@
           </div>
         </div>
         <div class="block">
+          <div class="content">
           <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>
+          <p>Cliquez sur les <strong>slots</strong> pour les basculer <span class="icon-text"><span class="icon"><twig:ux:icon name="bi:lock-fill"/></span><span><em>indisponible</em></span></span> (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>
         </div>
         {% if event.getSlots()|length > 0 %}
         <div id="planning">
@@ -91,7 +94,6 @@
           <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.

+ 3 - 2
templates/admin/event/config/space.html.twig

@@ -23,8 +23,9 @@
             <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>
-            <li><a>Meneur(euse)s de jeu</a></li>
-            <li><a>Jeux</a></li>
+            <li><a href="{{ path('app_admin_event_config_gamemaster', {'id': event.id}) }}">Meneur(euse)s de jeu</a></li>
+            <li><a href="{{ path('app_admin_event_config_game', {'id': event.id}) }}">Jeux</a></li>
+            <li><a href="{{ path('app_admin_event_config_party', {'id': event.id}) }}">Parties</a></li>
         </ul>
     </div>
 

+ 1 - 1
templates/admin/event/index.html.twig

@@ -39,7 +39,7 @@
                     <td>du {{ event.startOn|date('d/m/Y H:i', app_timezone) }}<br/>au {{ event.endOn|date('d/m/Y H:i', app_timezone) }}</td>
                     <td>
                         <a class="button" href="{{ path('app_admin_event_edit', {id: event.id}) }}">Éditer</a>
-                        <a class="button is-primary" href="{{ path('app_admin_event_config_space', {id: event.id}) }}">Configurer</a>
+                        <a class="button is-primary" href="{{ path('app_admin_event_config', {id: event.id}) }}">Configurer</a>
                         <a class="button is-danger" data-id="{{ path('app_admin_event_delete', {'id': event.id})}}" href="#" {{ stimulus_controller('admin_confirm') }}>Supprimer</a>
                     </td>
                 </tr>

+ 1 - 1
templates/admin/game/index.html.twig

@@ -40,7 +40,7 @@
                     <td><a href="{{ path('app_admin_game_edit', {id: game.id}) }}">{{ game.name }}</a></td>
                     <td>{{ game.description }}</td>
                     <td>{% for genre in game.genre %}<span class="tag is-info is-light">{{ genre.genre }}</span> {% endfor %}</td>
-                    <td>{% if game.isValidByAdmin %}<span class="icon"><twig:ux:icon name="bi:check"/></span>{% else %}<a href="{{ path('app_admin_game_valid', {id: game.id}) }}" class="button">Valider</a>{%endif%}</td>
+                    <td>{% if game.isValidByAdmin %}<span class="icon"><twig:ux:icon name="bi:check"/></span>{% else %}<a href="{{ path('app_admin_game_valid', {id: game.id}) }}" class="button"  data-turbo="false">Valider</a>{%endif%}</td>
                     <td>
                         <a class="button" href="{{ path('app_admin_game_edit', {id: game.id}) }}">Éditer</a>
                         <a class="button is-danger" data-id="{{ path('app_admin_game_delete', {'id': game.id})}}" href="#" {{ stimulus_controller('admin_confirm') }}>Supprimer</a>