feat(security): Ajoute la route de déconnexion et configure la redirection.
 feat(Dto/Ag): Crée les DTOs AgType, AgMembersType et AgOrderType.
 feat(Controller/Admin): Implémente la gestion des AG (CRUD complet).
 feat(templates/admin): Ajoute les templates pour la gestion des AG.
```
This commit is contained in:
Serreau Jovann
2025-11-23 17:06:10 +01:00
parent c4c8ad92be
commit bfc2370d2e
29 changed files with 1653 additions and 22 deletions

79
templates/admin/ag.twig Normal file
View File

@@ -0,0 +1,79 @@
{% extends 'admin/base.twig' %}
{% block title %}AG (Assemblée Générale){% endblock %}
{% block page_title %}AG (Assemblée Générale){% endblock %}
{% block body %}
<div class="p-4 sm:p-6 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 min-h-screen">
<div class="flex justify-between items-center mb-6 border-b pb-2 border-gray-200 dark:border-gray-700">
<h1 class="text-2xl font-bold">Liste des AG</h1>
{# NOUVEAU BOUTON: CRÉER UNE AG #}
<a href="{{ path('admin_ag_new') }}"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 dark:bg-green-500 dark:hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
+ Créer une AG
</a>
</div>
{% for ag in ags %}
<div class="mb-6 p-4 border rounded-lg shadow-md bg-gray-50 dark:bg-gray-700 dark:border-gray-600 hover:shadow-lg transition duration-300">
<div class="flex justify-between items-start mb-2">
<p class="text-lg font-semibold text-indigo-600 dark:text-indigo-400">
{{ ag.agDateAt|date('d/m/Y H:i') }}
<span class="ml-2 px-3 py-1 text-xs font-medium rounded-full
{% if ag.type == 'Extraordinaire' %}
bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300
{% else %}
bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300
{% endif %}">
{{ ag.type }}
</span>
</p>
</div>
<p class="text-sm">
<strong>Président:</strong> {{ ag.president.pseudo }} /
<strong>Secrétaire:</strong> {{ ag.secretaire.pseudo }}
</p>
<div class="mt-2 text-sm text-gray-600 dark:text-gray-300">
<strong>Lieu:</strong> {{ ag.locate }}
{{ ag.locateZipcode }} {{ ag.locateCity }}
</div>
<div class="mt-3 pt-3 border-t border-gray-200 dark:border-gray-600 text-sm">
<p>
<strong>Membres principaux:</strong> <span class="font-medium">{{ ag.mainMembers.count }}</span>
</p>
<p>
<strong>Ordres du jour:</strong> <span class="font-medium">{{ ag.orders.count }}</span>
</p>
</div>
{% if ag.closed == false or ag.closed == null %}
{# Bloc Actions: SUPPRIMER / MODIFIER #}
<div class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-600 flex space-x-3">
{# Bouton Modifier #}
<a href="{{ path('admin_ag_edit', {'id': ag.id}) }}"
class="inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 dark:bg-indigo-500 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Modifier
</a>
{# Bouton Supprimer #}
<form method="POST" action="{{ path('admin_ag_delete', {'id': ag.id}) }}" onsubmit="return confirm('Êtes-vous sûr de vouloir supprimer cette Assemblée Générale ?');">
<input type="hidden" name="_method" value="DELETE">
<button type="submit"
class="inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
Supprimer
</button>
</form>
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endblock %}

View File

@@ -0,0 +1,176 @@
{% extends 'admin/base.twig' %}
{% block title %}AG (Assemblée Générale){% endblock %}
{% block page_title %}AG (Assemblée Générale){% endblock %}
{% block body %}
<div class="p-4 sm:p-6 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 min-h-screen">
{# Titre Principal (Créer / Modifier) #}
<h1 class="text-2xl font-bold mb-6 border-b pb-2 border-gray-200 dark:border-gray-700">
{% if ag.vars.value.id is defined and ag.vars.value.id is not null %}
Modifier l'Assemblée Générale
{% else %}
Créer une nouvelle Assemblée Générale
{% endif %}
</h1>
{% form_theme ag 'form_admin.twig' %}
{# ============================================== #}
{# FORMULAIRE 1: DÉTAILS DE L'AG (Non répété ici) #}
{# ============================================== #}
{{ form_start(ag, {'attr': {'class': 'space-y-6 mb-10'}}) }}
{# Section 1: Détails de l'AG #}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 p-4 border rounded-lg bg-gray-50 dark:bg-gray-700">
<h2 class="col-span-full text-lg font-semibold text-gray-700 dark:text-gray-200 mb-2">Détails de l'AG</h2>
{{ form_row(ag.agDateAt) }}
{{ form_row(ag.closedAt) }}
{{ form_row(ag.type) }}
<div class="md:col-span-1"></div>
</div>
{# Section 2: Localisation #}
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 p-4 border rounded-lg bg-gray-50 dark:bg-gray-700">
<h2 class="col-span-full text-lg font-semibold text-gray-700 dark:text-gray-200 mb-2">Lieu</h2>
{{ form_row(ag.locate) }}
{{ form_row(ag.locateZipcode) }}
{{ form_row(ag.locateCity) }}
</div>
{# Section 3: Rôles #}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 p-4 border rounded-lg bg-gray-50 dark:bg-gray-700">
<h2 class="col-span-full text-lg font-semibold text-gray-700 dark:text-gray-200 mb-2">Rôles</h2>
{{ form_row(ag.president) }}
{{ form_row(ag.secretaire) }}
</div>
{# Boutons d'action pour le formulaire AG #}
<div class="flex justify-end space-x-4 pt-6 border-t border-gray-200 dark:border-gray-700">
<a href="{{ path('admin_ag') }}"
class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium rounded-md shadow-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Annuler
</a>
<button type="submit"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 dark:bg-indigo-500 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Enregistrer l'AG
</button>
</div>
{{ form_end(ag) }}
{# ============================================== #}
{# BLOC GESTION DES MEMBRES (Non répété ici) #}
{# ============================================== #}
<div class="mt-10 pt-6 border-t border-gray-200 dark:border-gray-700">
<h2 class="text-xl font-bold mb-4 text-gray-900 dark:text-gray-100">Ajouter/Gérer les membres</h2>
{% form_theme agMembers 'form_admin.twig' %}
{{ form_start(agMembers, {'attr': {'class': 'space-y-4 p-4 border rounded-lg bg-gray-50 dark:bg-gray-700 mb-6'}}) }}
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-end">
<div class="md:col-span-2">{{ form_row(agMembers.member) }}</div>
<div class="md:col-span-1">
<button type="submit" class="w-full inline-flex justify-center items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Ajouter le membre
</button>
</div>
</div>
{{ form_end(agMembers) }}
{# LISTE DES MEMBRES ACTUELS #}
{% if ag.vars.value.mainMembers is defined and ag.vars.value.mainMembers|length > 0 %}
<h3 class="text-lg font-semibold mt-8 mb-4 border-b pb-2 text-gray-700 dark:text-gray-300">
Liste des Membres Principaux ({{ ag.vars.value.mainMembers|length }})
</h3>
<ul class="space-y-3">
{% for memberAg in ag.vars.value.mainMembers %}
<li class="flex justify-between items-center p-3 rounded-md bg-white dark:bg-gray-800 shadow border border-gray-200 dark:border-gray-700">
<span class="text-gray-900 dark:text-gray-100 font-medium">
{{ memberAg.member.pseudo }} - <span class="text-sm font-normal text-gray-600 dark:text-gray-400">{{ memberAg.member.role }}</span>
</span>
<form method="POST" action="{{ path('admin_ag_edit',{id:ag.vars.value.id,idMember:memberAg.id}) }}" onsubmit="return confirm('Êtes-vous sûr de vouloir retirer {{ memberAg.member.pseudo }} de cette AG ?');">
<input type="hidden" name="_method" value="DELETE">
<button type="submit"
class="inline-flex items-center px-3 py-1 text-xs font-medium rounded-md text-white bg-red-500 hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition duration-150">
Supprimer
</button>
</form>
</li>
{% endfor %}
</ul>
{% else %}
<p class="mt-4 p-4 text-center text-gray-500 dark:text-gray-400 border border-dashed rounded-lg">
Aucun membre principal n'est encore rattaché à cette Assemblée Générale.
</p>
{% endif %}
</div>
{# ============================================== #}
{# BLOC ORDRES DU JOUR (ODJ) #}
{# ============================================== #}
<div class="mt-10 pt-6 border-t border-gray-200 dark:border-gray-700">
<h2 class="text-xl font-bold mb-4 text-gray-900 dark:text-gray-100">Ajouter un Ordre du Jour</h2>
{% form_theme agOrder 'form_admin.twig' %}
{# FORMULAIRE D'AJOUT D'ODJ #}
{{ form_start(agOrder, {'attr': {'class': 'space-y-4 p-4 border rounded-lg bg-gray-50 dark:bg-gray-700 mb-6'}}) }}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
{{ form_row(agOrder.title) }}
<div class="md:col-span-1 hidden md:block"></div>
</div>
{{ form_row(agOrder.description) }}
<div class="flex justify-end pt-2">
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-teal-600 hover:bg-teal-700 dark:bg-teal-500 dark:hover:bg-teal-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500">
Ajouter l'Ordre du Jour
</button>
</div>
{{ form_end(agOrder) }}
{# LISTE DES ORDRES DU JOUR ACTUELS #}
{% if ag.vars.value.orders is defined and ag.vars.value.orders|length > 0 %}
<h3 class="text-lg font-semibold mt-8 mb-4 border-b pb-2 text-gray-700 dark:text-gray-300">
Ordres du Jour ({{ ag.vars.value.orders|length }})
</h3>
<ul class="space-y-3">
{% for order in ag.vars.value.orders %}
<li class="p-4 rounded-md bg-white dark:bg-gray-800 shadow border border-gray-200 dark:border-gray-700">
<div class="flex justify-between items-start">
<span class="text-gray-900 dark:text-gray-100 font-medium text-base">
{{ loop.index }}. {{ order.title }}
</span>
{# Formulaire de Suppression de l'ODJ #}
<form method="POST" action="{{ path('admin_ag_edit', {'id': agMain.id, 'orderId': order.id}) }}" onsubmit="return confirm('Êtes-vous sûr de vouloir supprimer cet Ordre du Jour : {{ order.title }} ?');">
<input type="hidden" name="_method" value="DELETE">
<button type="submit"
class="inline-flex items-center px-3 py-1 text-xs font-medium rounded-md text-white bg-red-500 hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition duration-150 ml-4">
Supprimer
</button>
</form>
</div>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400 whitespace-pre-line">
{{ order.description|default('Aucune description fournie.') }}
</p>
</li>
{% endfor %}
</ul>
{% else %}
<p class="mt-4 p-4 text-center text-gray-500 dark:text-gray-400 border border-dashed rounded-lg">
Aucun Ordre du Jour n'a encore été défini pour cette Assemblée Générale.
</p>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,70 @@
{% extends 'admin/base.twig' %}
{% block title %}AG (Assemblée Générale){% endblock %}
{% block page_title %}AG (Assemblée Générale){% endblock %}
{% block body %}
<div class="p-4 sm:p-6 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 min-h-screen">
<h1 class="text-2xl font-bold mb-6 border-b pb-2 border-gray-200 dark:border-gray-700">
{% if ag.vars.value.id is defined and ag.vars.value.id is not null %}
Modifier l'Assemblée Générale
{% else %}
Créer une nouvelle Assemblée Générale
{% endif %}
</h1>
{# Le formulaire doit utiliser le thème 'form_admin.twig' pour le style #}
{% form_theme ag 'form_admin.twig' %}
{{ form_start(ag, {'attr': {'class': 'space-y-6'}}) }}
{# Section 1: Informations principales et dates (Grid Layout) #}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 p-4 border rounded-lg bg-gray-50 dark:bg-gray-700">
<h2 class="col-span-full text-lg font-semibold text-gray-700 dark:text-gray-200 mb-2">Détails de l'AG</h2>
{{ form_row(ag.agDateAt) }}
{{ form_row(ag.closedAt) }}
{{ form_row(ag.type) }}
{# Ce champ est seul sur la deuxième colonne pour l'exemple #}
<div class="md:col-span-1"></div>
</div>
{# Section 2: Localisation (Grid Layout) #}
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 p-4 border rounded-lg bg-gray-50 dark:bg-gray-700">
<h2 class="col-span-full text-lg font-semibold text-gray-700 dark:text-gray-200 mb-2">Lieu</h2>
{{ form_row(ag.locate) }}
{{ form_row(ag.locateZipcode) }}
{{ form_row(ag.locateCity) }}
</div>
{# Section 3: Rôles (Simple Layout) #}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 p-4 border rounded-lg bg-gray-50 dark:bg-gray-700">
<h2 class="col-span-full text-lg font-semibold text-gray-700 dark:text-gray-200 mb-2">Rôles</h2>
{{ form_row(ag.president) }}
{{ form_row(ag.secretaire) }}
</div>
{# Boutons d'action #}
<div class="flex justify-end space-x-4 pt-6 border-t border-gray-200 dark:border-gray-700">
{# Bouton Annuler/Retour #}
<a href="{{ path('admin_ag') }}"
class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium rounded-md shadow-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Annuler
</a>
{# Bouton Soumettre/Enregistrer #}
<button type="submit"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 dark:bg-indigo-500 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Enregistrer l'AG
</button>
</div>
{{ form_end(ag) }}
</div>
{% endblock %}

View File

@@ -26,9 +26,9 @@ Elle défilera donc avec le reste du contenu de la page.
L'overflow-y-auto n'est plus nécessaire ici car c'est le <body> qui gère le scroll. -->
<aside id="sidebar" class="w-64 bg-gray-800 text-white flex-shrink-0 transition-transform duration-300 transform -translate-x-full md:translate-x-0 z-40">
<div class="p-6">
<a href="{{ path('app_home') }}" target="_blank" class="p-6 block">
<h1 class="text-3xl font-bold tracking-wider">E-Cosplay</h1>
</div>
</a>
<nav class="mt-8">
<!-- Dashboard -->
@@ -60,19 +60,9 @@ L'overflow-y-auto n'est plus nécessaire ici car c'est le <body> qui gère le sc
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.523 5.754 18 7.5 18s3.332.477 4.5 1.247m0-13C13.168 5.477 14.754 5 16.5 5s3.332.477 4.5 1.253v13C19.832 18.523 18.246 18 16.5 18s-3.332.477-4.5 1.247"></path></svg>
AG (Assemblée Générale)
</a>
{# Le lien Paramètres a été supprimé #}
{# Ajoutez d'autres liens ici #}
</nav>
</aside>
<!-- 2. Contenu Principal et Navigation Supérieure -->
<!-- Le conteneur principal reste flex-col et prend l'espace restant -->
<div id="main-content" class="flex flex-col flex-1">
<!-- 2.1. Navbar (Barre de Navigation Supérieure) - bg-gray-800 -->

View File

@@ -223,7 +223,11 @@
tabindex="-1">
{{ 'logged_admin'|trans }}
</a>
<a href="{{ path('app_logout') }}"
class="block px-4 py-2 text-sm text-red-600 hover:bg-gray-100" role="menuitem"
tabindex="-1">
{{ 'logout_link'|trans }}
</a>
{% else %}
{# Afficher la connexion si non connecté #}
<a href="{{ path('app_login') }}"

View File

@@ -11,7 +11,7 @@
{# ---------- ROW ---------- #}
{% block form_row %}
{# La ROW est le conteneur du label, du widget et des erreurs. #}
<div class="mb-5">
<div class="mb-0">
{{ form_label(form) }}
<div class="mt-1">
{{ form_widget(form) }}