File size: 3,947 Bytes
78d0e31 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
"use client"
import type React from "react"
import { useEffect, useState } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { UserCheck, HeartHandshake, DollarSign, Users } from "lucide-react"
import { Skeleton } from "@/components/ui/skeleton"
// Type for stats
type PulseStatsData = {
verifiedCHWs: number
activeGuardians: number
totalSupport: number
livesImpacted: number
totalUsers: number
lastUpdated: string
}
export function PulseStats() {
const [stats, setStats] = useState<Partial<PulseStatsData>>({})
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const fetchStats = async () => {
try {
const response = await fetch("/api/stats")
if (!response.ok) {
throw new Error("Failed to fetch stats")
}
const data = await response.json()
setStats(data)
} catch (err) {
setError("Failed to load statistics")
console.error(err)
} finally {
setLoading(false)
}
}
fetchStats()
}, [])
// Stat card component
const StatCard = ({
title,
value,
icon: Icon,
isLoading,
isError,
colorClass,
}: {
title: string
value: number | string
icon: React.ElementType
isLoading: boolean
isError: boolean
colorClass?: string
}) => (
<Card
className={`bg-gradient-to-br ${colorClass ? `${colorClass}/20 to-${colorClass}/5 border border-${colorClass}/20` : "from-background to-muted border"} shadow-lg rounded-xl overflow-hidden relative group`}
>
{colorClass && (
<div
className={`absolute inset-0 bg-${colorClass}/5 opacity-0 group-hover:opacity-100 transition-opacity duration-500`}
></div>
)}
<CardHeader className="flex flex-row items-center justify-between pb-2 relative z-10">
<CardTitle className="text-sm font-medium text-muted-foreground">{title}</CardTitle>
<Icon className={`h-5 w-5 ${colorClass ? `text-${colorClass}` : "text-primary"}`} />
</CardHeader>
<CardContent className="relative z-10">
{isError ? (
<div className="text-red-500 text-sm">Data unavailable</div>
) : isLoading ? (
<Skeleton className="h-8 w-3/4 bg-muted-foreground/20" />
) : (
<div className={`text-2xl font-bold ${colorClass ? `text-${colorClass}` : "text-foreground"}`}>{value}</div>
)}
<p className="text-xs text-muted-foreground/80 mt-1">
{isLoading
? "Loading..."
: error
? "Update failed"
: stats.lastUpdated
? `Updated: ${new Date(stats.lastUpdated).toLocaleString()}`
: "Live data"}
</p>
</CardContent>
</Card>
)
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<StatCard
title="Verified CHWs"
value={stats.verifiedCHWs?.toLocaleString() || "0"}
icon={UserCheck}
isLoading={loading}
isError={!!error}
colorClass="flame"
/>
<StatCard
title="Active Guardians"
value={stats.activeGuardians?.toLocaleString() || "0"}
icon={HeartHandshake}
isLoading={loading}
isError={!!error}
colorClass="guardian"
/>
<StatCard
title="Total Support"
value={`$${stats.totalSupport?.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) || "0.00"}`}
icon={DollarSign}
isLoading={loading}
isError={!!error}
colorClass="pulse"
/>
<StatCard
title="Lives Impacted"
value={stats.livesImpacted?.toLocaleString() || "0"}
icon={Users}
isLoading={loading}
isError={!!error}
colorClass="neon"
/>
</div>
)
}
|