Template genérico de checkout com ASAAS, parametrizado via env vars. Inclui fluxo completo: checkout → pedido → polling → webhook → admin. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
145 lines
5.7 KiB
TypeScript
145 lines
5.7 KiB
TypeScript
import { getProdutos, getPedidos, getClientes, getCupons, getAgendamentos } from "@/lib/actions"
|
|
import { ProductTable } from "@/components/product-table"
|
|
import { TransactionTable } from "@/components/transaction-table"
|
|
import { CustomerTable } from "@/components/customer-table"
|
|
import { DashboardHeader } from "@/components/dashboard-header"
|
|
import { DashboardShell } from "@/components/dashboard-shell"
|
|
import { DashboardStats } from "@/components/dashboard-stats"
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
import { ProductFormDialog } from "@/components/product-form-dialog"
|
|
import { CustomerFormDialog } from "@/components/customer-form-dialog"
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Overview } from "@/components/overview"
|
|
import { RecentSales } from "@/components/recent-sales"
|
|
import { AuthGuard } from "@/components/auth-guard"
|
|
import { CupomTable } from "@/components/cupom-table"
|
|
import { AgendamentosTable } from "@/components/agendamentos-table"
|
|
|
|
export const dynamic = "force-dynamic"
|
|
|
|
export const metadata = {
|
|
title: "Dashboard — Admin",
|
|
}
|
|
|
|
export default async function AdminDashboard() {
|
|
const [produtos, pedidos, clientes, cupons, agendamentos] = await Promise.all([
|
|
getProdutos(),
|
|
getPedidos({ limit: 200 }),
|
|
getClientes({ limit: 200 }),
|
|
getCupons(),
|
|
getAgendamentos({ limit: 100 }),
|
|
])
|
|
|
|
const pedidosConfirmados = pedidos.filter(
|
|
(p) => p.status === "RECEIVED" || p.status === "CONFIRMED"
|
|
)
|
|
const pedidosPendentes = pedidos.filter((p) => p.status === "PENDING").length
|
|
const receitaTotal = pedidosConfirmados.reduce((acc, p) => acc + p.valor_centavos / 100, 0)
|
|
const ticketMedio = pedidosConfirmados.length > 0 ? receitaTotal / pedidosConfirmados.length : 0
|
|
|
|
// Receita mensal (12 meses) para o gráfico
|
|
const receitaMensal = Array(12).fill(0)
|
|
pedidosConfirmados.forEach((p) => {
|
|
const mes = new Date(p.created_at).getMonth()
|
|
receitaMensal[mes] += p.valor_centavos / 100
|
|
})
|
|
|
|
// Últimas 5 vendas para o card de resumo
|
|
const ultimasVendas = pedidosConfirmados.slice(0, 5) as Parameters<typeof RecentSales>[0]["vendas"]
|
|
|
|
// Contagem de agendamentos pendentes para badge
|
|
const agendamentosPendentes = agendamentos.filter(
|
|
(a) => a.status === "pendente" || a.status === "confirmado"
|
|
).length
|
|
|
|
return (
|
|
<AuthGuard>
|
|
<DashboardShell>
|
|
<DashboardHeader
|
|
heading="Dashboard de Administração"
|
|
text="Gerencie seus produtos, clientes e visualize estatísticas de vendas."
|
|
>
|
|
<ProductFormDialog />
|
|
</DashboardHeader>
|
|
|
|
{/* Cards de métricas */}
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
<DashboardStats
|
|
receitaTotal={receitaTotal}
|
|
totalPedidosConfirmados={pedidosConfirmados.length}
|
|
ticketMedio={ticketMedio}
|
|
pedidosPendentes={pedidosPendentes}
|
|
/>
|
|
</div>
|
|
|
|
{/* Gráfico + últimas vendas */}
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
|
|
<Card className="col-span-4">
|
|
<CardHeader>
|
|
<CardTitle className="font-heading">Receita Mensal</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="pl-2">
|
|
<Overview data={receitaMensal} />
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="col-span-3">
|
|
<CardHeader>
|
|
<CardTitle className="font-heading">Últimas Vendas</CardTitle>
|
|
<CardDescription className="font-sans">
|
|
{pedidosConfirmados.length} venda{pedidosConfirmados.length !== 1 ? "s" : ""} confirmada{pedidosConfirmados.length !== 1 ? "s" : ""} no total.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<RecentSales vendas={ultimasVendas} />
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Abas */}
|
|
<Tabs defaultValue="pedidos" className="space-y-4">
|
|
<TabsList>
|
|
<TabsTrigger value="pedidos">Pedidos</TabsTrigger>
|
|
<TabsTrigger value="agendamentos">
|
|
Agendamentos
|
|
{agendamentosPendentes > 0 && (
|
|
<span className="ml-1.5 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground text-[10px] font-bold w-4 h-4">
|
|
{agendamentosPendentes}
|
|
</span>
|
|
)}
|
|
</TabsTrigger>
|
|
<TabsTrigger value="products">Produtos</TabsTrigger>
|
|
<TabsTrigger value="customers">Clientes</TabsTrigger>
|
|
<TabsTrigger value="cupons">Cupons</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="pedidos" className="space-y-4">
|
|
<TransactionTable transactions={pedidos} />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="agendamentos" className="space-y-4">
|
|
<AgendamentosTable agendamentos={agendamentos as Parameters<typeof AgendamentosTable>[0]["agendamentos"]} />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="products" className="space-y-4">
|
|
<div className="flex justify-end mb-4">
|
|
<ProductFormDialog />
|
|
</div>
|
|
<ProductTable products={produtos} />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="customers" className="space-y-4">
|
|
<div className="flex justify-end mb-4">
|
|
<CustomerFormDialog />
|
|
</div>
|
|
<CustomerTable customers={clientes} />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="cupons" className="space-y-4">
|
|
<CupomTable cupons={cupons} />
|
|
</TabsContent>
|
|
</Tabs>
|
|
</DashboardShell>
|
|
</AuthGuard>
|
|
)
|
|
}
|