feat: integrate real data into dashboard
- Created dashboard service with DB queries for users, economy, events - Added client stats provider with 30s caching for Discord metrics - Implemented /api/stats endpoint aggregating all dashboard data - Created useDashboardStats React hook with auto-refresh - Updated Dashboard.tsx to display real data with loading/error states - Added comprehensive test coverage (11 tests passing) - Replaced all mock values with live Discord and database metrics
This commit is contained in:
@@ -6,8 +6,49 @@ import {
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Activity, Server, Users, Zap } from "lucide-react";
|
||||
import { useDashboardStats } from "@/hooks/use-dashboard-stats";
|
||||
|
||||
export function Dashboard() {
|
||||
const { stats, loading, error } = useDashboardStats();
|
||||
|
||||
if (loading && !stats) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold tracking-tight">Dashboard</h2>
|
||||
<p className="text-muted-foreground">Loading dashboard data...</p>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<Card key={i}>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Loading...</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-8 w-20 bg-muted animate-pulse rounded" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold tracking-tight">Dashboard</h2>
|
||||
<p className="text-destructive">Error loading dashboard: {error}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!stats) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
@@ -23,8 +64,8 @@ export function Dashboard() {
|
||||
<Server className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">12</div>
|
||||
<p className="text-xs text-muted-foreground">+2 from last month</p>
|
||||
<div className="text-2xl font-bold">{stats.guilds.count}</div>
|
||||
<p className="text-xs text-muted-foreground">Active guilds</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -34,19 +75,21 @@ export function Dashboard() {
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">1,234</div>
|
||||
<p className="text-xs text-muted-foreground">+10% from last month</p>
|
||||
<div className="text-2xl font-bold">{stats.users.active.toLocaleString()}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{stats.users.total.toLocaleString()} total registered
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Commands Run</CardTitle>
|
||||
<CardTitle className="text-sm font-medium">Commands</CardTitle>
|
||||
<Zap className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">12,345</div>
|
||||
<p className="text-xs text-muted-foreground">+5% from last month</p>
|
||||
<div className="text-2xl font-bold">{stats.commands.total}</div>
|
||||
<p className="text-xs text-muted-foreground">Registered commands</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -56,8 +99,8 @@ export function Dashboard() {
|
||||
<Activity className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">24ms</div>
|
||||
<p className="text-xs text-muted-foreground">+2ms from last hour</p>
|
||||
<div className="text-2xl font-bold">{stats.ping.avg}ms</div>
|
||||
<p className="text-xs text-muted-foreground">WebSocket latency</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -65,11 +108,25 @@ export function Dashboard() {
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
|
||||
<Card className="col-span-4">
|
||||
<CardHeader>
|
||||
<CardTitle>Activity Overview</CardTitle>
|
||||
<CardTitle>Economy Overview</CardTitle>
|
||||
<CardDescription>Server economy statistics</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-[200px] w-full bg-muted/20 flex items-center justify-center border-2 border-dashed border-muted rounded-md text-muted-foreground">
|
||||
Chart Placeholder
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium">Total Wealth</p>
|
||||
<p className="text-2xl font-bold">{BigInt(stats.economy.totalWealth).toLocaleString()} AU</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium">Average Level</p>
|
||||
<p className="text-xl font-bold">{stats.economy.avgLevel}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium">Top Streak</p>
|
||||
<p className="text-xl font-bold">{stats.economy.topStreak} days</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -81,27 +138,30 @@ export function Dashboard() {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center">
|
||||
<div className="w-2 h-2 rounded-full bg-emerald-500 mr-2" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium leading-none">New guild joined</p>
|
||||
<p className="text-sm text-muted-foreground">2 minutes ago</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="w-2 h-2 rounded-full bg-destructive mr-2" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Error in verify command</p>
|
||||
<p className="text-sm text-muted-foreground">15 minutes ago</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500 mr-2" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Bot restarted</p>
|
||||
<p className="text-sm text-muted-foreground">1 hour ago</p>
|
||||
</div>
|
||||
</div>
|
||||
{stats.recentEvents.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No recent events</p>
|
||||
) : (
|
||||
stats.recentEvents.slice(0, 5).map((event, i) => (
|
||||
<div key={i} className="flex items-center">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full mr-2 ${event.type === 'success'
|
||||
? 'bg-emerald-500'
|
||||
: event.type === 'error'
|
||||
? 'bg-destructive'
|
||||
: 'bg-blue-500'
|
||||
}`}
|
||||
/>
|
||||
<div className="space-y-1 flex-1">
|
||||
<p className="text-sm font-medium leading-none">
|
||||
{event.icon} {event.message}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{new Date(event.timestamp).toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user