sto-k-odnomu/admin/src/pages/DashboardPage.tsx

250 lines
9.2 KiB
TypeScript
Raw Normal View History

2026-01-06 20:12:36 +00:00
import { useQuery } from '@tanstack/react-query'
import { Users, FileText, Package, DollarSign, TrendingUp, Clock } from 'lucide-react'
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar } from 'recharts'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { analyticsApi, type DashboardData, type ChartDataPoint } from '@/api/analytics'
import { useAuthStore } from '@/stores/authStore'
export default function DashboardPage() {
const { isAuthenticated, token } = useAuthStore()
// Only make requests if authenticated and token exists
const isReady = isAuthenticated && !!token && !!localStorage.getItem('admin_token')
const { data: dashboardData, isLoading: dashboardLoading } = useQuery<DashboardData>({
queryKey: ['dashboard'],
queryFn: analyticsApi.getDashboard,
enabled: isReady,
})
const { data: usersChartData } = useQuery<{ data: ChartDataPoint[] }>({
queryKey: ['users-chart'],
queryFn: analyticsApi.getUsersChart,
enabled: isReady,
})
const { data: revenueChartData } = useQuery<{ data: ChartDataPoint[] }>({
queryKey: ['revenue-chart'],
queryFn: analyticsApi.getRevenueChart,
enabled: isReady,
})
if (dashboardLoading) {
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold tracking-tight">Dashboard</h1>
<p className="text-muted-foreground">
2026-01-06 21:25:06 +00:00
Welcome to Sto k Odnomu Admin Panel
2026-01-06 20:12:36 +00:00
</p>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{[...Array(4)].map((_, i) => (
<Card key={i}>
<CardContent className="pt-6">
<div className="animate-pulse">
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
<div className="h-8 bg-gray-200 rounded w-1/2"></div>
</div>
</CardContent>
</Card>
))}
</div>
</div>
)
}
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold tracking-tight">Dashboard</h1>
<p className="text-muted-foreground">
2026-01-06 21:25:06 +00:00
Welcome to Sto k Odnomu Admin Panel
2026-01-06 20:12:36 +00:00
</p>
</div>
{/* Stats Cards */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Users</CardTitle>
<Users className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{dashboardData?.stats.users || 0}</div>
<p className="text-xs text-muted-foreground">
Registered users
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Cards</CardTitle>
<FileText className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{dashboardData?.stats.cards || 0}</div>
<p className="text-xs text-muted-foreground">
Game cards created
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Active Packs</CardTitle>
<Package className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{dashboardData?.stats.enabledPacks || 0}</div>
<p className="text-xs text-muted-foreground">
of {dashboardData?.stats.packs || 0} total packs
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Payments</CardTitle>
<DollarSign className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{dashboardData?.stats.payments || 0}</div>
<p className="text-xs text-muted-foreground">
Successful transactions
</p>
</CardContent>
</Card>
</div>
{/* Charts */}
<div className="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle>User Registrations</CardTitle>
<CardDescription>
Daily user registrations for the last 30 days
</CardDescription>
</CardHeader>
<CardContent>
{usersChartData?.data && (
<ResponsiveContainer width="100%" height={300}>
<LineChart data={usersChartData.data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="date"
tickFormatter={(value) => new Date(value).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
/>
<YAxis />
<Tooltip
labelFormatter={(value) => new Date(value).toLocaleDateString()}
formatter={(value: number) => [value, 'Registrations']}
/>
<Line
type="monotone"
dataKey="registrations"
stroke="#8884d8"
strokeWidth={2}
dot={{ fill: '#8884d8' }}
/>
</LineChart>
</ResponsiveContainer>
)}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Revenue Trend</CardTitle>
<CardDescription>
Daily revenue for the last 30 days
</CardDescription>
</CardHeader>
<CardContent>
{revenueChartData?.data && (
<ResponsiveContainer width="100%" height={300}>
<BarChart data={revenueChartData.data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="date"
tickFormatter={(value) => new Date(value).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
/>
<YAxis />
<Tooltip
labelFormatter={(value) => new Date(value).toLocaleDateString()}
formatter={(value: number) => [`$${value}`, 'Revenue']}
/>
<Bar dataKey="revenue" fill="#82ca9d" />
</BarChart>
</ResponsiveContainer>
)}
</CardContent>
</Card>
</div>
{/* Recent Users and Top Packs */}
<div className="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Clock className="h-5 w-5" />
Recent Users
</CardTitle>
<CardDescription>
Latest user registrations
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{dashboardData?.recentUsers.slice(0, 5).map((user) => (
<div key={user.id} className="flex items-center justify-between">
<div>
<p className="font-medium">{user.name || 'No name'}</p>
<p className="text-sm text-muted-foreground">{user.email || 'No email'}</p>
</div>
<div className="text-sm text-muted-foreground">
{user.createdAt ? new Date(user.createdAt).toLocaleDateString() : 'N/A'}
</div>
</div>
)) || (
<p className="text-muted-foreground">No recent users</p>
)}
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<TrendingUp className="h-5 w-5" />
Popular Packs
</CardTitle>
<CardDescription>
Most popular card packs
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{dashboardData?.topPacks.map((pack) => (
<div key={pack.id} className="flex items-center justify-between">
<div>
<p className="font-medium">{pack.title}</p>
<p className="text-sm text-muted-foreground">{pack.cards} cards</p>
</div>
<Badge variant={pack.enabled ? "default" : "secondary"}>
{pack.enabled ? 'Active' : 'Disabled'}
</Badge>
</div>
)) || (
<p className="text-muted-foreground">No packs available</p>
)}
</div>
</CardContent>
</Card>
</div>
</div>
)
}