Admin panel (/admin, ROLE_ROOT): - Dashboard with CA HT Global/Commission cards and Meilisearch sync button - Buyers page with search (Meilisearch), create form, pagination (KnpPaginator) - Buyer actions: resend verification, force verify, reset password, delete - Organizers page with tabs (pending/approved), approve/reject with emails - Neo-brutalist design matching main site theme - Vite admin entry point with dedicated SCSS - CSP-compatible confirm dialogs via data-confirm attributes Meilisearch integration: - Auto-index buyers on email verification - Remove from index on buyer deletion - Manual sync button on dashboard - Search bar on buyers page - Add Meilisearch service to CI/SonarQube workflows - Add MEILISEARCH env vars to .env.test - Fix MeilisearchMessageHandler infinite loop: use request() directly instead of service methods that re-dispatch messages Email templates: - Redesign base email template to neo-brutalist style (borders, shadows, yellow footer) - Add E-Cosplay logo, "E-Ticket solution proposee par e-cosplay.fr" - Add admin_reset_password, organizer_approved, organizer_rejected templates Other: - Install knplabs/knp-paginator-bundle - Add ^/admin access_control for ROLE_ROOT in security.yaml - Update site footer with E-Ticket branding - 18 admin tests, updated MeilisearchMessageHandler tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
89 lines
6.3 KiB
Twig
89 lines
6.3 KiB
Twig
{% extends 'admin/base.html.twig' %}
|
|
|
|
{% block title %}Organisateurs{% endblock %}
|
|
|
|
{% block body %}
|
|
<div style="margin-bottom:2rem;">
|
|
<h1 class="text-3xl font-black uppercase tracking-tighter italic" style="border-bottom:4px solid #111827;display:inline-block;margin-bottom:0.5rem;">Organisateurs</h1>
|
|
<p class="font-bold text-gray-500 italic">{{ organizers.getTotalItemCount }} organisateur{{ organizers.getTotalItemCount > 1 ? 's' : '' }}.</p>
|
|
</div>
|
|
|
|
<div style="display:flex;gap:0;margin-bottom:2rem;">
|
|
<a href="{{ path('app_admin_organizers', {tab: 'pending'}) }}" style="flex:1;text-align:center;padding:0.75rem;border:3px solid #111827;border-right:none;{{ tab == 'pending' ? 'background:#fabf04;' : 'background:white;' }}" class="font-black uppercase text-sm tracking-widest transition-all">En attente</a>
|
|
<a href="{{ path('app_admin_organizers', {tab: 'approved'}) }}" style="flex:1;text-align:center;padding:0.75rem;border:3px solid #111827;{{ tab == 'approved' ? 'background:#fabf04;' : 'background:white;' }}" class="font-black uppercase text-sm tracking-widest transition-all">Valides</a>
|
|
</div>
|
|
|
|
<div style="border:4px solid #111827;box-shadow:6px 6px 0 rgba(0,0,0,1);background:white;">
|
|
<table style="width:100%;border-collapse:collapse;">
|
|
<thead>
|
|
<tr style="background:#111827;">
|
|
<th style="padding:0.75rem 1.5rem;text-align:left;" class="text-[10px] font-black uppercase tracking-widest text-white">Organisateur</th>
|
|
<th style="padding:0.75rem 1.5rem;text-align:left;" class="text-[10px] font-black uppercase tracking-widest text-white">Raison sociale</th>
|
|
<th style="padding:0.75rem 1.5rem;text-align:left;" class="text-[10px] font-black uppercase tracking-widest text-white">SIRET</th>
|
|
<th style="padding:0.75rem 1.5rem;text-align:left;" class="text-[10px] font-black uppercase tracking-widest text-white">Ville</th>
|
|
<th style="padding:0.75rem 1.5rem;text-align:left;" class="text-[10px] font-black uppercase tracking-widest text-white">Offre</th>
|
|
<th style="padding:0.75rem 1.5rem;text-align:right;" class="text-[10px] font-black uppercase tracking-widest text-white">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for orga in organizers %}
|
|
<tr style="border-bottom:1px solid #e5e7eb;" class="hover:bg-gray-50 transition-all">
|
|
<td style="padding:0.75rem 1.5rem;">
|
|
<p class="font-bold text-sm">{{ orga.firstName }} {{ orga.lastName }}</p>
|
|
<p class="text-gray-400 text-xs">{{ orga.email }}</p>
|
|
</td>
|
|
<td style="padding:0.75rem 1.5rem;" class="text-sm text-gray-600">{{ orga.companyName }}</td>
|
|
<td style="padding:0.75rem 1.5rem;" class="text-sm text-gray-600 font-mono">{{ orga.siret }}</td>
|
|
<td style="padding:0.75rem 1.5rem;" class="text-sm text-gray-600">{{ orga.postalCode }} {{ orga.city }}</td>
|
|
<td style="padding:0.75rem 1.5rem;">
|
|
{% if orga.offer %}
|
|
<span style="background:#e0e7ff;border:2px solid #111827;padding:0.15rem 0.5rem;" class="text-xs font-black uppercase">{{ orga.offer }}</span>
|
|
{% else %}
|
|
<span class="text-gray-400 text-xs">—</span>
|
|
{% endif %}
|
|
</td>
|
|
<td style="padding:0.75rem 1.5rem;text-align:right;">
|
|
<div style="display:flex;gap:0.5rem;justify-content:flex-end;">
|
|
{% if not orga.approved %}
|
|
<form method="post" action="{{ path('app_admin_approve_organizer', {id: orga.id}) }}">
|
|
<button type="submit" style="border:2px solid #111827;padding:0.4rem 0.75rem;background:#fabf04;cursor:pointer;" class="text-xs font-black uppercase tracking-widest hover:bg-green-500 hover:text-black transition-all">Approuver</button>
|
|
</form>
|
|
<form method="post" action="{{ path('app_admin_reject_organizer', {id: orga.id}) }}" data-confirm="Etes-vous sur de vouloir refuser et supprimer le compte de {{ orga.firstName }} {{ orga.lastName }} ? Cette action est irreversible.">
|
|
<button type="submit" style="border:2px solid #991b1b;padding:0.4rem 0.75rem;background:#dc2626;color:white;cursor:pointer;" class="text-xs font-black uppercase tracking-widest hover:bg-red-800 transition-all">Refuser</button>
|
|
</form>
|
|
{% else %}
|
|
<form method="post" action="{{ path('app_admin_delete_buyer', {id: orga.id}) }}" data-confirm="Etes-vous sur de vouloir supprimer le compte de {{ orga.firstName }} {{ orga.lastName }} ({{ orga.email }}) ? Cette action est irreversible.">
|
|
<button type="submit" style="border:2px solid #991b1b;padding:0.4rem 0.75rem;background:#dc2626;color:white;cursor:pointer;" class="text-xs font-black uppercase tracking-widest hover:bg-red-800 transition-all">Supprimer</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="6" style="padding:3rem;text-align:center;" class="text-gray-400 font-bold text-sm">
|
|
{% if tab == 'pending' %}
|
|
Aucune demande en attente.
|
|
{% else %}
|
|
Aucun organisateur valide.
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% if organizers.getTotalItemCount > 10 %}
|
|
<div style="display:flex;justify-content:center;gap:0.5rem;margin-top:1.5rem;">
|
|
{% for page in 1..organizers.getPageCount %}
|
|
{% if page == organizers.getCurrentPageNumber %}
|
|
<span style="border:2px solid #111827;padding:0.4rem 0.75rem;background:#fabf04;" class="text-xs font-black">{{ page }}</span>
|
|
{% else %}
|
|
<a href="{{ path('app_admin_organizers', {tab: tab, page: page}) }}" style="border:2px solid #111827;padding:0.4rem 0.75rem;background:white;" class="text-xs font-black hover:bg-gray-100 transition-all">{{ page }}</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endblock %}
|