99 lines
4.2 KiB
TypeScript
99 lines
4.2 KiB
TypeScript
import { createClient } from '@/lib/supabase/server'
|
||
import { createAdminClient } from '@/lib/supabase/admin'
|
||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||
import { Package, ShoppingCart, Users, TrendingUp } from 'lucide-react'
|
||
import { AdminRecentOrders } from '@/components/admin/recent-orders'
|
||
|
||
// ─── KPI-Daten aus DB ────────────────────────────────────────────────────────
|
||
async function getKpis() {
|
||
const supabase = await createClient()
|
||
const adminDb = createAdminClient()
|
||
|
||
const [
|
||
{ data: orders },
|
||
{ count: userCount },
|
||
{ count: productCount },
|
||
] = await Promise.all([
|
||
adminDb.from('orders').select('total_price, status, created_at'),
|
||
adminDb.from('profiles').select('*', { count: 'exact', head: true }),
|
||
supabase.from('products').select('*', { count: 'exact', head: true }),
|
||
])
|
||
|
||
const now = new Date()
|
||
const currentYear = now.getFullYear()
|
||
const currentMonth = now.getMonth()
|
||
|
||
const completedOrdersThisMonth = (orders ?? []).filter(o => {
|
||
if (o.status !== 'completed') return false
|
||
const orderDate = new Date(o.created_at)
|
||
return orderDate.getFullYear() === currentYear && orderDate.getMonth() === currentMonth
|
||
})
|
||
|
||
const totalRevenue = completedOrdersThisMonth.reduce((sum, o) => sum + Number(o.total_price), 0)
|
||
const orderCount = (orders ?? []).length
|
||
|
||
return { totalRevenue, orderCount, userCount: userCount ?? 0, productCount: productCount ?? 0 }
|
||
}
|
||
|
||
export default async function AdminDashboard() {
|
||
const { totalRevenue, orderCount, userCount, productCount } = await getKpis()
|
||
|
||
return (
|
||
<div className="p-8 space-y-8">
|
||
<h2 className="text-3xl font-bold tracking-tight text-gradient">Dashboard</h2>
|
||
|
||
{/* KPI Cards – echte DB-Daten */}
|
||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||
<Card className="glass-dark border-white/5">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
<CardTitle className="text-sm font-medium text-slate-300">Umsatz aktueller Monat</CardTitle>
|
||
<TrendingUp className="h-4 w-4 text-primary" />
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold text-white">
|
||
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(totalRevenue)}
|
||
</div>
|
||
<p className="text-xs text-slate-400">Nur fertige Bestellungen</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="glass-dark border-white/5">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
<CardTitle className="text-sm font-medium text-slate-300">Bestellungen</CardTitle>
|
||
<ShoppingCart className="h-4 w-4 text-primary" />
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold text-white">{orderCount}</div>
|
||
<p className="text-xs text-slate-400">Gesamt</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="glass-dark border-white/5">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
<CardTitle className="text-sm font-medium text-slate-300">Produkte</CardTitle>
|
||
<Package className="h-4 w-4 text-primary" />
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold text-white">{productCount}</div>
|
||
<p className="text-xs text-slate-400">Im Katalog</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="glass-dark border-white/5">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
<CardTitle className="text-sm font-medium text-slate-300">Kunden</CardTitle>
|
||
<Users className="h-4 w-4 text-primary" />
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold text-white">{userCount}</div>
|
||
<p className="text-xs text-slate-400">Registrierte User</p>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* Letzte Bestellungen – Client Component mit 30s-Polling */}
|
||
<AdminRecentOrders />
|
||
</div>
|
||
)
|
||
}
|