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:
@@ -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
|
||||
|
||||
@@ -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'])]
|
||||
|
||||
@@ -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 €</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, ',', ' ') }} €</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 €</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, ',', ' ') }} €</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, ',', ' ') }} €</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, ',', ' ') }} €</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, ',', ' ') }} €</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, ',', ' ') }} €</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, ',', ' ') }} €</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, ',', ' ') }} €</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 %}
|
||||
|
||||
Reference in New Issue
Block a user