feat: add revenue and top services stats to dashboard
This commit is contained in:
@@ -448,13 +448,15 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
|
||||
<Card className="p-4 sm:p-6">
|
||||
<div className="flex items-center justify-between mb-2 sm:mb-3">
|
||||
<div className="p-2 sm:p-3 bg-blue-500 rounded-lg text-white">
|
||||
<Globe size={20} className="sm:hidden" />
|
||||
<Globe size={24} className="hidden sm:block" />
|
||||
<TrendingUp size={20} className="sm:hidden" />
|
||||
<TrendingUp size={24} className="hidden sm:block" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs sm:text-sm text-slate-600 mb-0.5 sm:mb-1">Reservas online</p>
|
||||
<p className="text-2xl sm:text-3xl font-bold text-slate-900">{allShopAppointments.length}</p>
|
||||
<p className="text-[10px] sm:text-xs text-slate-500 mt-1.5 sm:mt-2">Marcações feitas pela plataforma</p>
|
||||
<p className="text-xs sm:text-sm text-slate-600 mb-0.5 sm:mb-1">Faturação (Serviços + Produtos)</p>
|
||||
<p className="text-2xl sm:text-3xl font-bold text-slate-900">
|
||||
{currency(totalRevenue + allShopAppointments.filter(a => a.status === 'concluido' || a.status === 'confirmado').reduce((s, a) => s + (a.total || 0), 0))}
|
||||
</p>
|
||||
<p className="text-[10px] sm:text-xs text-slate-500 mt-1.5 sm:mt-2">Receita no período selecionado</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-4 sm:p-6">
|
||||
@@ -525,6 +527,35 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Serviços Mais Procurados */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-bold text-slate-900 mb-4">Top Serviços</h3>
|
||||
{(() => {
|
||||
const map = new Map<string, { name: string; qty: number }>();
|
||||
allShopAppointments.forEach(a => {
|
||||
if (a.status === 'cancelado') return;
|
||||
const svc = shop.services.find(s => s.id === a.serviceId);
|
||||
if (!svc) return;
|
||||
const prev = map.get(a.serviceId)?.qty ?? 0;
|
||||
map.set(a.serviceId, { name: svc.name, qty: prev + 1 });
|
||||
});
|
||||
const top = Array.from(map.values()).sort((a, b) => b.qty - a.qty).slice(0, 4);
|
||||
|
||||
if (top.length === 0) return <p className="text-sm text-slate-500 text-center py-4">Sem dados suficientes</p>;
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{top.map((t, i) => (
|
||||
<div key={i} className="flex items-center justify-between p-3 border border-slate-100 rounded-lg bg-slate-50">
|
||||
<span className="font-semibold text-slate-700 text-sm truncate pr-2">{t.name}</span>
|
||||
<Badge color="indigo" variant="soft">{t.qty} reservas</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</Card>
|
||||
|
||||
{/* Próximos Agendamentos */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-bold text-slate-900 mb-4">Seguinte</h3>
|
||||
|
||||
Reference in New Issue
Block a user