Files
pragativeer-v2/src/components/AI/ReadinessGauge.tsx

188 lines
7.9 KiB
TypeScript

"use client"
import { fetchWithAuth } from "@/lib/apiClient";
import { useEffect, useState } from "react"
import { Card, CardContent } from "@/components/ui/card"
import { TrendingUp, Flame, RefreshCw } from "lucide-react"
import Link from "next/link"
import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser"
export function ReadinessGauge() {
const [data, setData] = useState<any>(null)
const [loading, setLoading] = useState(true)
const { userId, loading: userLoading } = useActiveUser();
const isDemo = isDemoUser(userId);
useEffect(() => {
if (userLoading || !userId) return;
async function fetchReadiness() {
try {
const res = await fetchWithAuth(`/api/ai-coach/readiness/${userId}`)
if (!res.ok) throw new Error("Failed to fetch")
const json = await res.json()
setData(json)
} catch (err) {
if (isDemo) {
setData({
score: 72,
breakdown: {
accuracy: 30,
speed: 15,
consistency: 8,
recovery: 5,
coverage: 14
}
})
} else {
setData({
score: 0,
breakdown: {
accuracy: 0,
speed: 0,
consistency: 0,
recovery: 0,
coverage: 0
}
})
}
} finally {
setLoading(false)
}
}
fetchReadiness()
}, [userId, userLoading, isDemo])
if (loading) {
return (
<Card className="border-border shadow-sm h-full flex items-center justify-center min-h-[300px]">
<RefreshCw className="animate-spin text-muted-foreground" />
</Card>
)
}
const score = data?.score || 0
const isZero = score === 0 && !isDemo
const circumference = 2 * Math.PI * 45 // r=45
const strokeDashoffset = circumference - (score / 100) * circumference
return (
<div className="flex flex-col h-full">
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] relative overflow-hidden flex-1 flex flex-col justify-between p-5 min-h-[440px] group hover:border-[var(--cyan-ai-30)] transition-colors">
{/* Background glow decoration */}
<div className="absolute -top-1/2 -right-1/4 w-48 h-48 bg-[radial-gradient(circle,rgba(94,196,184,0.06)_0%,transparent_70%)] rounded-full pointer-events-none" />
<div className="flex justify-between items-start mb-4 relative z-10">
<span className="text-[11px] font-bold text-muted-foreground uppercase tracking-widest">
{isZero ? "Algorithmic Baseline" : "Updated daily • Today"}
</span>
<span className={`flex items-center gap-1 text-xs font-bold px-2.5 py-0.5 rounded-full ${isZero ? "text-muted-foreground bg-white/5" : "text-green-500 bg-green-500/10"}`}>
<TrendingUp size={14} className="stroke-[2.5px]" />
{isZero ? "Day 1 Baseline" : "+5 this week"}
</span>
</div>
<div className="flex flex-col md:flex-row items-center gap-6 mb-4 relative z-10">
{/* Circular Score */}
<div className="relative w-[110px] h-[110px] shrink-0">
<svg className="w-full h-full transform -rotate-90" viewBox="0 0 100 100">
<defs>
<linearGradient id="scoreGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="var(--cyan-ai)" />
<stop offset="100%" stopColor="#3B82F6" />
</linearGradient>
</defs>
<circle cx="50" cy="50" r="45" stroke="currentColor" strokeWidth="6" fill="transparent" className="text-[var(--cyan-ai-10)]" />
<circle
cx="50" cy="50" r="45"
stroke="url(#scoreGradient)"
strokeWidth="6"
fill="transparent"
strokeDasharray={circumference}
strokeDashoffset={isZero ? circumference : strokeDashoffset}
strokeLinecap="round"
/>
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center text-center mt-1">
<span className="text-[32px] font-extrabold leading-none">{score}</span>
<span className="text-[9px] text-muted-foreground font-semibold mt-0.5">SCORE</span>
</div>
</div>
{/* Quick metric grid beside ring */}
<div className="flex-1 grid grid-cols-2 gap-2.5 w-full">
<div className="bg-white/5 rounded-lg p-2.5">
<div className="text-base font-black flex items-baseline gap-1">
{isZero ? "0%" : "88%"}
{!isZero && <span className="text-xs text-green-500">↑</span>}
</div>
<div className="text-xs font-bold text-muted-foreground">Accuracy</div>
</div>
<div className="bg-white/5 rounded-lg p-2.5">
<div className="text-base font-black flex items-baseline gap-1">
{isZero ? "-- s" : "45s"}
{!isZero && <span className="text-xs text-red-500">↓</span>}
</div>
<div className="text-xs font-bold text-muted-foreground">Speed/Q</div>
</div>
<div className="bg-white/5 rounded-lg p-2.5">
<div className="text-base font-black flex items-baseline gap-1">
{isZero ? "0%" : "High"}
{!isZero && <span className="text-xs text-green-500">↑</span>}
</div>
<div className="text-xs font-bold text-muted-foreground">Consistency</div>
</div>
<div className="bg-white/5 rounded-lg p-2.5">
<div className="text-base font-black flex items-baseline gap-1">
{isZero ? "0%" : "64%"}
{!isZero && <span className="text-xs text-green-500">↑</span>}
</div>
<div className="text-xs font-bold text-muted-foreground">Syllabus</div>
</div>
</div>
</div>
{/* Subject Bars */}
<div className="space-y-2 mb-4 relative z-10">
{[
{ name: "Physics", val: isZero ? 0 : 68, color: "var(--cyan-ai)" },
{ name: "Chemistry", val: isZero ? 0 : 74, color: "#F59E0B" },
{ name: "Biology / Maths", val: isZero ? 0 : 82, color: "#10B981" },
].map(sub => (
<div key={sub.name} className="flex items-center gap-3">
<span className="text-xs font-bold text-muted-foreground w-24 shrink-0 truncate">{sub.name}</span>
<div className="flex-1 h-2 rounded-full bg-white/10 overflow-hidden">
<div
className="h-full rounded-full"
style={{ width: `${sub.val}%`, background: sub.color }}
/>
</div>
<span className="text-xs font-black w-10 text-right" style={{ color: sub.color }}>{sub.val}%</span>
</div>
))}
</div>
{/* National Avg */}
<div className="flex items-center justify-between px-3 py-2 bg-white/5 rounded-lg mb-4 relative z-10 text-xs font-bold">
<span className="text-muted-foreground">National Avg</span>
<span className="text-muted-foreground">58 / 100</span>
<span className={isZero ? "text-muted-foreground" : "text-green-500"}>
{isZero ? "Pending test data" : `You: +${score - 58} above avg`}
</span>
</div>
<div className="mt-auto px-3.5 py-3 bg-[var(--cyan-ai-10)] border border-[var(--cyan-ai-20)] rounded-lg text-xs md:text-sm text-[var(--cyan-ai)] font-bold flex items-center gap-2 relative z-10">
<Flame size={18} className="fill-[var(--cyan-ai-20)] shrink-0" />
{isZero
? "Complete your first diagnostic test to establish your personalized readiness score."
: "Your speed has dropped slightly. Focus on time-bound practice today."}
</div>
</Card>
</div>
)
}