89 lines
2.6 KiB
TypeScript
89 lines
2.6 KiB
TypeScript
"use client"
|
|
|
|
import { useQuery } from "@tanstack/react-query"
|
|
import { Card, CardContent } from "@/components/ui/card"
|
|
import { Users, UserCheck, UserX } from "lucide-react"
|
|
import { fetchUsers } from "@/app/protected/(admin)/dashboard/user-management/action"
|
|
import { User } from "@/src/models/users/users.model"
|
|
|
|
|
|
function calculateUserStats(users: User[]) {
|
|
const totalUsers = users.length
|
|
const activeUsers = users.filter((user) => !user.banned_until && user.email_confirmed_at).length
|
|
const inactiveUsers = totalUsers - activeUsers
|
|
|
|
return {
|
|
totalUsers,
|
|
activeUsers,
|
|
inactiveUsers,
|
|
activePercentage: totalUsers > 0 ? ((activeUsers / totalUsers) * 100).toFixed(1) : "0.0",
|
|
inactivePercentage: totalUsers > 0 ? ((inactiveUsers / totalUsers) * 100).toFixed(1) : "0.0",
|
|
}
|
|
}
|
|
|
|
export function UserStats() {
|
|
const { data: users = [], isLoading } = useQuery({
|
|
queryKey: ["users"],
|
|
queryFn: fetchUsers,
|
|
})
|
|
|
|
const stats = calculateUserStats(users)
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<>
|
|
{[...Array(3)].map((_, i) => (
|
|
<Card key={i} className="bg-background border-border">
|
|
<CardContent className="p-6">
|
|
<div className="space-y-4 animate-pulse">
|
|
<div className="h-5 w-24 bg-muted rounded" />
|
|
<div className="h-8 w-16 bg-muted rounded" />
|
|
<div className="h-4 w-32 bg-muted rounded" />
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</>
|
|
)
|
|
}
|
|
|
|
const cards = [
|
|
{
|
|
title: "Total Users",
|
|
value: stats.totalUsers,
|
|
subtitle: "Updated just now",
|
|
icon: Users,
|
|
},
|
|
{
|
|
title: "Active Users",
|
|
value: stats.activeUsers,
|
|
subtitle: `${stats.activePercentage}% of total users`,
|
|
icon: UserCheck,
|
|
},
|
|
{
|
|
title: "Inactive Users",
|
|
value: stats.inactiveUsers,
|
|
subtitle: `${stats.inactivePercentage}% of total users`,
|
|
icon: UserX,
|
|
},
|
|
]
|
|
|
|
return (
|
|
<>
|
|
{cards.map((card, index) => (
|
|
<Card key={index} className="bg-background border-border">
|
|
<CardContent className="p-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<div className="font-medium text-sm text-muted-foreground">{card.title}</div>
|
|
<card.icon className="h-4 w-4 text-muted-foreground" />
|
|
</div>
|
|
<div className="text-3xl font-bold mb-2">{card.value}</div>
|
|
<div className="text-sm text-muted-foreground">{card.subtitle}</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</>
|
|
)
|
|
}
|
|
|