Files
webshop/shop/components/admin/recent-orders.tsx

141 lines
5.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useEffect, useState, useRef } from 'react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import Link from 'next/link'
type RecentOrder = {
id: string
order_number: string
created_at: string
total_price: number
status: string
customer_data?: {
company_name?: string
first_name?: string
last_name?: string
}
}
const statusClass: Record<string, string> = {
pending: 'bg-amber-500/20 text-amber-400 border-amber-500/30',
active: 'bg-green-500/20 text-green-400 border-green-500/30',
cancelled: 'bg-red-500/20 text-red-400 border-red-500/30',
}
const statusLabel: Record<string, string> = {
pending: 'Eingegangen',
active: 'In Bearbeitung',
cancelled: 'Storniert',
}
const POLL_INTERVAL_MS = 30_000
export function AdminRecentOrders() {
const [orders, setOrders] = useState<RecentOrder[]>([])
const [newOrderIds, setNewOrderIds] = useState<Set<string>>(new Set())
const [lastChecked, setLastChecked] = useState<Date>(new Date())
const knownIds = useRef<Set<string>>(new Set())
const fetchOrders = async () => {
try {
const res = await fetch('/api/admin/recent-orders', { cache: 'no-store' })
if (!res.ok) return
const { orders: fresh } = await res.json()
// Neue Bestellungen erkennen (Highlight)
const freshIds = new Set<string>(fresh.map((o: RecentOrder) => o.id))
const newOnes = new Set<string>()
freshIds.forEach(id => {
if (!knownIds.current.has(id)) newOnes.add(id)
})
knownIds.current = freshIds
setOrders(fresh)
setNewOrderIds(newOnes)
setLastChecked(new Date())
// Highlight nach 5 Sekunden entfernen
if (newOnes.size > 0) {
setTimeout(() => setNewOrderIds(new Set()), 5000)
}
} catch (e) {
console.error('Polling error:', e)
}
}
useEffect(() => {
fetchOrders()
const interval = setInterval(fetchOrders, POLL_INTERVAL_MS)
return () => clearInterval(interval)
}, [])
return (
<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 folgt in nächstem Update]
</div>
</CardContent>
</Card>
<Card className="col-span-3 glass-dark border-white/5">
<CardHeader className="flex flex-row items-center justify-between pb-3">
<CardTitle>Neue Bestellungen</CardTitle>
<span className="text-[10px] text-slate-500">
Aktualisiert: {lastChecked.toLocaleTimeString('de-DE')}
</span>
</CardHeader>
<CardContent>
{orders.length === 0 ? (
<p className="text-slate-500 text-sm italic text-center py-8">Noch keine Bestellungen</p>
) : (
<div className="space-y-3">
{orders.map(order => {
const isNew = newOrderIds.has(order.id)
const customerName =
order.customer_data?.company_name ||
`${order.customer_data?.first_name ?? ''} ${order.customer_data?.last_name ?? ''}`.trim() ||
'Unbekannt'
return (
<Link key={order.id} href="/admin/orders" className="block">
<div
className={`flex items-center gap-3 p-3 rounded-lg transition-all duration-500 ${
isNew
? 'bg-primary/10 border border-primary/30 shadow-[0_0_12px_rgba(59,130,246,0.2)]'
: 'bg-white/5 hover:bg-white/10 border border-white/5'
}`}
>
<div className="w-9 h-9 rounded-full bg-primary/10 flex items-center justify-center text-primary text-xs font-bold shrink-0">
{customerName.slice(0, 2).toUpperCase()}
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-white truncate">{customerName}</p>
<p className="text-xs text-slate-500 font-mono">#{order.order_number}</p>
</div>
<div className="text-right shrink-0 space-y-1">
<p className="font-medium text-white text-sm">
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(order.total_price)}
</p>
<Badge className={`text-[10px] ${statusClass[order.status] ?? ''}`}>
{statusLabel[order.status] ?? order.status}
</Badge>
</div>
</div>
</Link>
)
})}
</div>
)}
</CardContent>
</Card>
</div>
)
}