11import { ActivityIcon , UsersIcon } from 'lucide-react'
22import { useTranslation } from 'react-i18next'
33import { Card , CardContent , CardDescription , CardHeader , CardTitle } from '../ui/card'
4+ import { Badge } from '../ui/badge'
45import { SystemStats } from '@/service/api'
56
67const UserStatisticsCard = ( { data } : { data : SystemStats | undefined } ) => {
78 const { t } = useTranslation ( )
9+ const totalUsers = data ?. total_user ?? 0
10+ const percentOfTotal = ( value : number | undefined ) => {
11+ if ( ! totalUsers || value === undefined || value <= 0 ) return null
12+ return Math . round ( ( value / totalUsers ) * 100 )
13+ }
814
915 return (
1016 < Card >
@@ -13,40 +19,82 @@ const UserStatisticsCard = ({ data }: { data: SystemStats | undefined }) => {
1319 < CardDescription > { t ( 'monitorUsers' ) } </ CardDescription >
1420 </ CardHeader >
1521 < CardContent className = "flex flex-col gap-2" >
16- < div className = "flex flex-row items-center gap-3 rounded-lg border p-4 shadow" >
17- < UsersIcon className = "text-muted-foreground" />
18- { t ( 'statistics.users' ) }
19- < span className = "ms-auto font-bold" > { data ?. total_user || 0 } </ span >
22+ < div className = "flex min-w-0 flex-row items-center gap-2 rounded-lg border p-3 shadow-sm md:gap-3 md:p-4 " >
23+ < UsersIcon className = "size-5 text-muted-foreground md:size-6 " />
24+ < span className = "truncate text-sm md:text-base" > { t ( 'statistics.users' ) } </ span >
25+ < span className = "ms-auto font-bold text-sm md:text-base " > { totalUsers } </ span >
2026 </ div >
21- < div className = "flex flex-row items-center gap-3 rounded-lg border p-4 shadow" >
22- < ActivityIcon className = "text-muted-foreground" />
23- { t ( 'statistics.activeUsers' ) }
24- < span className = "ms-auto font-bold" > { data ?. active_users || 0 } </ span >
27+ < div className = "flex min-w-0 flex-row items-center gap-2 rounded-lg border p-3 shadow-sm md:gap-3 md:p-4" >
28+ < ActivityIcon className = "size-5 text-muted-foreground md:size-6" />
29+ < span className = "truncate text-sm md:text-base" > { t ( 'statistics.activeUsers' ) } </ span >
30+ < div className = "ms-auto flex items-center gap-2" >
31+ { percentOfTotal ( data ?. active_users ) !== null && (
32+ < Badge variant = "secondary" className = "text-[10px] md:text-xs" >
33+ { percentOfTotal ( data ?. active_users ) } %
34+ </ Badge >
35+ ) }
36+ < span className = "font-bold text-sm md:text-base" > { data ?. active_users || 0 } </ span >
37+ </ div >
2538 </ div >
26- < div className = "flex flex-row items-center gap-3 rounded-lg border p-4 shadow" >
27- < div className = "size-4 rounded-full bg-green-600" />
28- { t ( 'statistics.onlineUsers' ) }
29- < span className = "ms-auto font-bold" > { data ?. online_users || 0 } </ span >
39+ < div className = "flex min-w-0 flex-row items-center gap-2 rounded-lg border p-3 shadow-sm md:gap-3 md:p-4" >
40+ < div className = "size-2 rounded-full bg-green-600 md:size-3" />
41+ < span className = "truncate text-sm md:text-base" > { t ( 'statistics.onlineUsers' ) } </ span >
42+ < div className = "ms-auto flex items-center gap-2" >
43+ { percentOfTotal ( data ?. online_users ) !== null && (
44+ < Badge variant = "secondary" className = "text-[10px] md:text-xs" >
45+ { percentOfTotal ( data ?. online_users ) } %
46+ </ Badge >
47+ ) }
48+ < span className = "font-bold text-sm md:text-base" > { data ?. online_users || 0 } </ span >
49+ </ div >
3050 </ div >
31- < div className = "flex flex-row items-center gap-3 rounded-lg border p-4 shadow" >
32- < div className = "size-4 rounded-full bg-red-600" />
33- { t ( 'statistics.expiredUsers' ) }
34- < span className = "ms-auto font-bold" > { data ?. expired_users || 0 } </ span >
51+ < div className = "flex min-w-0 flex-row items-center gap-2 rounded-lg border p-3 shadow-sm md:gap-3 md:p-4" >
52+ < div className = "size-2 rounded-full bg-red-600 md:size-3" />
53+ < span className = "truncate text-sm md:text-base" > { t ( 'statistics.expiredUsers' ) } </ span >
54+ < div className = "ms-auto flex items-center gap-2" >
55+ { percentOfTotal ( data ?. expired_users ) !== null && (
56+ < Badge variant = "secondary" className = "text-[10px] md:text-xs" >
57+ { percentOfTotal ( data ?. expired_users ) } %
58+ </ Badge >
59+ ) }
60+ < span className = "font-bold text-sm md:text-base" > { data ?. expired_users || 0 } </ span >
61+ </ div >
3562 </ div >
36- < div className = "flex flex-row items-center gap-3 rounded-lg border p-4 shadow" >
37- < div className = "size-4 rounded-full bg-orange-600" />
38- { t ( 'statistics.limitedUsers' ) }
39- < span className = "ms-auto font-bold" > { data ?. limited_users || 0 } </ span >
63+ < div className = "flex min-w-0 flex-row items-center gap-2 rounded-lg border p-3 shadow-sm md:gap-3 md:p-4" >
64+ < div className = "size-2 rounded-full bg-orange-600 md:size-3" />
65+ < span className = "truncate text-sm md:text-base" > { t ( 'statistics.limitedUsers' ) } </ span >
66+ < div className = "ms-auto flex items-center gap-2" >
67+ { percentOfTotal ( data ?. limited_users ) !== null && (
68+ < Badge variant = "secondary" className = "text-[10px] md:text-xs" >
69+ { percentOfTotal ( data ?. limited_users ) } %
70+ </ Badge >
71+ ) }
72+ < span className = "font-bold text-sm md:text-base" > { data ?. limited_users || 0 } </ span >
73+ </ div >
4074 </ div >
41- < div className = "flex flex-row items-center gap-3 rounded-lg border p-4 shadow" >
42- < div className = "size-4 rounded-full bg-purple-600" />
43- { t ( 'statistics.onHoldUsers' ) }
44- < span className = "ms-auto font-bold" > { data ?. on_hold_users || 0 } </ span >
75+ < div className = "flex min-w-0 flex-row items-center gap-2 rounded-lg border p-3 shadow-sm md:gap-3 md:p-4" >
76+ < div className = "size-2 rounded-full bg-purple-600 md:size-3" />
77+ < span className = "truncate text-sm md:text-base" > { t ( 'statistics.onHoldUsers' ) } </ span >
78+ < div className = "ms-auto flex items-center gap-2" >
79+ { percentOfTotal ( data ?. on_hold_users ) !== null && (
80+ < Badge variant = "secondary" className = "text-[10px] md:text-xs" >
81+ { percentOfTotal ( data ?. on_hold_users ) } %
82+ </ Badge >
83+ ) }
84+ < span className = "font-bold text-sm md:text-base" > { data ?. on_hold_users || 0 } </ span >
85+ </ div >
4586 </ div >
46- < div className = "flex flex-row items-center gap-3 rounded-lg border p-4 shadow" >
47- < div className = "size-4 rounded-full bg-slate-600" />
48- { t ( 'statistics.disabledUsers' ) }
49- < span className = "ms-auto font-bold" > { data ?. disabled_users || 0 } </ span >
87+ < div className = "flex min-w-0 flex-row items-center gap-2 rounded-lg border p-3 shadow-sm md:gap-3 md:p-4" >
88+ < div className = "size-2 rounded-full bg-slate-600 md:size-3" />
89+ < span className = "truncate text-sm md:text-base" > { t ( 'statistics.disabledUsers' ) } </ span >
90+ < div className = "ms-auto flex items-center gap-2" >
91+ { percentOfTotal ( data ?. disabled_users ) !== null && (
92+ < Badge variant = "secondary" className = "text-[10px] md:text-xs" >
93+ { percentOfTotal ( data ?. disabled_users ) } %
94+ </ Badge >
95+ ) }
96+ < span className = "font-bold text-sm md:text-base" > { data ?. disabled_users || 0 } </ span >
97+ </ div >
5098 </ div >
5199 </ CardContent >
52100 </ Card >
0 commit comments