65 lines
1.7 KiB
TypeScript
65 lines
1.7 KiB
TypeScript
import { Card, CardContent } from '@/components/ui/card'
|
|
import { Skeleton } from '@/components/ui/skeleton'
|
|
import type { Stats } from '../../types'
|
|
|
|
interface KPICardsProps {
|
|
stats: Stats | undefined
|
|
isLoading: boolean
|
|
}
|
|
|
|
interface KPICardProps {
|
|
title: string
|
|
value: number | string
|
|
isLoading: boolean
|
|
valueClass?: string
|
|
}
|
|
|
|
function KPICard({ title, value, isLoading, valueClass }: KPICardProps) {
|
|
return (
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
{isLoading ? (
|
|
<>
|
|
<Skeleton className="h-8 w-16 mb-2" />
|
|
<Skeleton className="h-4 w-24" />
|
|
</>
|
|
) : (
|
|
<>
|
|
<p className={`text-3xl font-bold ${valueClass ?? ''}`}>{value}</p>
|
|
<p className="text-sm text-muted-foreground mt-1">{title}</p>
|
|
</>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
export function KPICards({ stats, isLoading }: KPICardsProps) {
|
|
return (
|
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<KPICard
|
|
title="Total Findings"
|
|
value={stats?.totalAnomalies ?? 0}
|
|
isLoading={isLoading}
|
|
/>
|
|
<KPICard
|
|
title="Critical / High"
|
|
value={stats?.criticalHighCount ?? 0}
|
|
isLoading={isLoading}
|
|
valueClass={stats && stats.criticalHighCount > 0 ? 'text-destructive' : undefined}
|
|
/>
|
|
<KPICard
|
|
title="Active Sessions"
|
|
value={stats?.runningSessions ?? 0}
|
|
isLoading={isLoading}
|
|
valueClass={stats && stats.runningSessions > 0 ? 'text-green-500' : undefined}
|
|
/>
|
|
<KPICard
|
|
title="Total Sessions"
|
|
value={stats?.totalSessions ?? 0}
|
|
isLoading={isLoading}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|