feat(orders): idempotency guard, order numbers, button lock, success page, my-orders, admin polling
This commit is contained in:
@@ -43,7 +43,7 @@ async function OrdersData() {
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-white/10 hover:bg-transparent">
|
||||
<TableHead className="w-[100px] text-slate-200">ID</TableHead>
|
||||
<TableHead className="w-[140px] text-slate-200">Bestellnr.</TableHead>
|
||||
<TableHead className="text-slate-200">Datum</TableHead>
|
||||
<TableHead className="text-slate-200">Kunde</TableHead>
|
||||
<TableHead className="text-slate-200">Betrag</TableHead>
|
||||
@@ -61,8 +61,8 @@ async function OrdersData() {
|
||||
) : (
|
||||
orders?.map((order) => (
|
||||
<TableRow key={order.id} className="border-white/5 hover:bg-white/5">
|
||||
<TableCell className="font-mono text-xs text-slate-500">
|
||||
{order.id.slice(0, 8)}...
|
||||
<TableCell className="font-mono text-xs text-primary font-semibold">
|
||||
{order.order_number ?? order.id.slice(0, 8) + '...'}
|
||||
</TableCell>
|
||||
<TableCell className="text-slate-300">
|
||||
{new Date(order.created_at).toLocaleDateString('de-DE')}
|
||||
|
||||
@@ -1,11 +1,36 @@
|
||||
import { createClient } from '@/lib/supabase/server'
|
||||
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 [
|
||||
{ data: orders },
|
||||
{ count: userCount },
|
||||
{ count: productCount },
|
||||
] = await Promise.all([
|
||||
supabase.from('orders').select('total_price, status'),
|
||||
supabase.from('profiles').select('*', { count: 'exact', head: true }),
|
||||
supabase.from('products').select('*', { count: 'exact', head: true }),
|
||||
])
|
||||
|
||||
const totalRevenue = (orders ?? []).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()
|
||||
|
||||
export default function AdminDashboard() {
|
||||
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">
|
||||
@@ -13,75 +38,49 @@ export default function AdminDashboard() {
|
||||
<TrendingUp className="h-4 w-4 text-primary" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-white">€12,234.56</div>
|
||||
<p className="text-xs text-slate-400">+20.1% zum Vormonat</p>
|
||||
<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">Aus allen 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">+573</div>
|
||||
<p className="text-xs text-slate-400">+12% zur Vorwoche</p>
|
||||
<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">12</div>
|
||||
<p className="text-xs text-slate-400">+2 neue Module</p>
|
||||
<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">2,450</div>
|
||||
<p className="text-xs text-slate-400">+180 heute</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
|
||||
<Card className="col-span-4 glass-dark border-white/5">
|
||||
<CardHeader>
|
||||
<CardTitle>Übersicht</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pl-2">
|
||||
<div className="h-[200px] flex items-center justify-center text-slate-400 italic">
|
||||
[Umsatz-Chart wird geladen...]
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="col-span-3 glass-dark border-white/5">
|
||||
<CardHeader>
|
||||
<CardTitle>Letzte Bestellungen</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{[1, 2, 3].map(i => (
|
||||
<div key={i} className="flex items-center gap-4">
|
||||
<div className="w-9 h-9 rounded-full bg-primary/10 flex items-center justify-center text-primary text-xs font-bold">
|
||||
JD
|
||||
</div>
|
||||
<div className="flex-1 space-y-1">
|
||||
<p className="text-sm font-medium text-white">John Doe</p>
|
||||
<p className="text-xs text-slate-400">john.doe@example.com</p>
|
||||
</div>
|
||||
<div className="font-medium text-white">+€450.00</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user