Add admin dashboard stats: CA, commissions E-Ticket/Stripe, orders, billets, orgas

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-03-22 22:09:16 +01:00
parent 94ebe09181
commit 47916f5f30
3 changed files with 105 additions and 13 deletions

View File

@@ -24,7 +24,7 @@
- [ ] Générer un récapitulatif mensuel des ventes (export CSV/PDF)
### Admin
- [ ] Dashboard admin : stats globales (CA global, commission E-Ticket totale, commission Stripe totale, nb commandes, nb billets, nb orgas)
- [x] Dashboard admin : stats globales (CA global, commission E-Ticket, commission Stripe, nb commandes, nb billets, nb orgas, revenus net)
- [x] Admin : liste de toutes les commandes avec filtres (recherche, statut, KPIs)
- [x] Admin : pouvoir suspendre/réactiver un organisateur (badge, bouton toggle, redirect si suspendu, audit log)
- [x] Admin : pouvoir modifier l'offre/commission d'un orga existant

View File

@@ -26,9 +26,49 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
class AdminController extends AbstractController
{
#[Route('', name: 'app_admin_dashboard')]
public function dashboard(): Response
public function dashboard(EntityManagerInterface $em): Response
{
return $this->render('admin/dashboard.html.twig');
$allUsers = $em->getRepository(User::class)->findAll();
$organizers = array_filter($allUsers, fn (User $u) => \in_array('ROLE_ORGANIZER', $u->getRoles(), true) && $u->isApproved());
$totalCA = (int) ($em->createQueryBuilder()
->select('SUM(o.totalHT)')
->from(BilletBuyer::class, 'o')
->where('o.status = :paid')
->setParameter('paid', BilletBuyer::STATUS_PAID)
->getQuery()
->getSingleScalarResult() ?? 0);
$nbOrders = $em->getRepository(BilletBuyer::class)->count(['status' => BilletBuyer::STATUS_PAID]);
$nbBillets = $em->getRepository(\App\Entity\BilletOrder::class)->count([]);
$commissionEticket = 0;
$commissionStripe = 0;
$paidOrders = $em->createQueryBuilder()
->select('o', 'e')
->from(BilletBuyer::class, 'o')
->join('o.event', 'e')
->join('e.account', 'a')
->where('o.status = :paid')
->setParameter('paid', BilletBuyer::STATUS_PAID)
->getQuery()
->getResult();
foreach ($paidOrders as $order) {
$rate = $order->getEvent()->getAccount()->getCommissionRate() ?? 3;
$ht = $order->getTotalHT() / 100;
$commissionEticket += $ht * ($rate / 100);
$commissionStripe += $ht * 0.015 + 0.25;
}
return $this->render('admin/dashboard.html.twig', [
'nbOrgas' => \count($organizers),
'nbOrders' => $nbOrders,
'nbBillets' => $nbBillets,
'totalCA' => $totalCA / 100,
'commissionEticket' => $commissionEticket,
'commissionStripe' => $commissionStripe,
]);
}
#[Route('/sync-meilisearch', name: 'app_admin_sync_meilisearch', methods: ['POST'])]

View File

@@ -8,18 +8,70 @@
<p class="font-bold text-gray-500 italic">Bonjour {{ app.user.firstName }}, bienvenue sur l'administration.</p>
</div>
<div class="flex flex-wrap gap-6 mb-10">
<div class="flex-1 min-w-[280px] admin-card">
<p class="admin-stat-label font-black uppercase text-gray-400">CA HT Global</p>
<p class="text-4xl font-black mt-2">0,00 &euro;</p>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-10">
<div class="admin-card">
<p class="admin-stat-label font-black uppercase text-gray-400">CA Global HT</p>
<p class="text-3xl font-black text-indigo-600 mt-2">{{ totalCA|number_format(2, ',', ' ') }} &euro;</p>
</div>
<div class="flex-1 min-w-[280px] admin-card-yellow">
<p class="admin-stat-label font-black uppercase">CA HT Commission</p>
<p class="text-4xl font-black mt-2">0,00 &euro;</p>
<div class="admin-card-yellow">
<p class="admin-stat-label font-black uppercase">Commission E-Ticket</p>
<p class="text-3xl font-black mt-2">{{ commissionEticket|number_format(2, ',', ' ') }} &euro;</p>
</div>
<div class="admin-card">
<p class="admin-stat-label font-black uppercase text-gray-400">Commission Stripe</p>
<p class="text-3xl font-black mt-2">{{ commissionStripe|number_format(2, ',', ' ') }} &euro;</p>
</div>
<div class="admin-card">
<p class="admin-stat-label font-black uppercase text-gray-400">Commandes payees</p>
<p class="text-3xl font-black mt-2">{{ nbOrders }}</p>
</div>
<div class="admin-card">
<p class="admin-stat-label font-black uppercase text-gray-400">Billets generes</p>
<p class="text-3xl font-black mt-2">{{ nbBillets }}</p>
</div>
<div class="admin-card">
<p class="admin-stat-label font-black uppercase text-gray-400">Organisateurs actifs</p>
<p class="text-3xl font-black mt-2">{{ nbOrgas }}</p>
</div>
</div>
<form method="post" action="{{ path('app_admin_sync_meilisearch') }}" class="inline">
<button type="submit" class="admin-btn font-black uppercase text-xs tracking-widest hover:bg-indigo-600 hover:text-black transition-all">Sync Meilisearch</button>
</form>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-10">
<div class="admin-card">
<p class="admin-stat-label font-black uppercase text-gray-400 mb-2">Revenus E-Ticket</p>
<div class="flex items-baseline gap-2">
<p class="text-2xl font-black text-green-600">{{ (totalCA - commissionStripe)|number_format(2, ',', ' ') }} &euro;</p>
<p class="text-xs font-bold text-gray-400">CA - Stripe</p>
</div>
<div class="mt-4 pt-4 border-t-2 border-gray-200">
<div class="flex justify-between text-sm font-bold mb-1">
<span class="text-gray-500">CA Global HT</span>
<span>{{ totalCA|number_format(2, ',', ' ') }} &euro;</span>
</div>
<div class="flex justify-between text-sm font-bold mb-1">
<span class="text-gray-500">- Stripe</span>
<span class="text-red-600">{{ commissionStripe|number_format(2, ',', ' ') }} &euro;</span>
</div>
<div class="flex justify-between text-sm font-bold mb-1">
<span class="text-gray-500">- Reverse orga</span>
<span class="text-red-600">{{ (totalCA - commissionEticket - commissionStripe)|number_format(2, ',', ' ') }} &euro;</span>
</div>
<div class="flex justify-between text-sm font-black pt-2 border-t border-gray-200">
<span>Net E-Ticket</span>
<span class="text-indigo-600">{{ commissionEticket|number_format(2, ',', ' ') }} &euro;</span>
</div>
</div>
</div>
<div class="admin-card">
<p class="admin-stat-label font-black uppercase text-gray-400 mb-4">Actions rapides</p>
<div class="space-y-3">
<a href="{{ path('app_admin_orders') }}" class="block admin-btn-sm-white text-xs font-black uppercase tracking-widest hover:bg-indigo-600 hover:text-black transition-all text-center py-2">Voir toutes les commandes</a>
<a href="{{ path('app_admin_organizers') }}" class="block admin-btn-sm-white text-xs font-black uppercase tracking-widest hover:bg-indigo-600 hover:text-black transition-all text-center py-2">Gerer les organisateurs</a>
<a href="{{ path('app_admin_invite_organizer') }}" class="block admin-btn-sm-white text-xs font-black uppercase tracking-widest hover:bg-indigo-600 hover:text-black transition-all text-center py-2">Inviter un organisateur</a>
<a href="{{ path('app_admin_logs') }}" class="block admin-btn-sm-white text-xs font-black uppercase tracking-widest hover:bg-indigo-600 hover:text-black transition-all text-center py-2">Audit trail</a>
<form method="post" action="{{ path('app_admin_sync_meilisearch') }}" class="block">
<button type="submit" class="w-full admin-btn-sm-yellow text-xs font-black uppercase tracking-widest hover:bg-indigo-600 hover:text-black transition-all py-2">Sync Meilisearch</button>
</form>
</div>
</div>
</div>
{% endblock %}