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

163 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { TrendingUp, TrendingDown, Brain, Zap, Target, Clock } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import { useState } from "react"
import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser"
const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
const DEMO_QUESTIONS = [42, 58, 35, 71, 65, 48, 60]
const DEMO_ACCURACY = [82, 78, 91, 88, 85, 79, 93]
const DEMO_HOURS = [2.5, 3.5, 2, 4.5, 4, 3, 3.8]
const ZERO_DATA = [0, 0, 0, 0, 0, 0, 0]
export function WeeklyProgressReport() {
const { userId } = useActiveUser()
const isDemo = isDemoUser(userId)
const [metric, setMetric] = useState<"questions" | "accuracy" | "hours">("accuracy")
const [hovered, setHovered] = useState<number | null>(null)
const QUESTIONS = isDemo ? DEMO_QUESTIONS : ZERO_DATA
const ACCURACY = isDemo ? DEMO_ACCURACY : ZERO_DATA
const HOURS = isDemo ? DEMO_HOURS : ZERO_DATA
const DATA = metric === "questions" ? QUESTIONS : metric === "accuracy" ? ACCURACY : HOURS
const maxVal = Math.max(1, ...DATA)
const avgVal = Math.round((DATA.reduce((a, b) => a + b, 0) / DATA.length) * 10) / 10
const trend = isDemo ? DATA[DATA.length - 1] - DATA[0] : 0
const trendUp = trend >= 0
const UNIT = metric === "accuracy" ? "%" : metric === "hours" ? "h" : ""
const avgAccuracy = isDemo ? Math.round(ACCURACY.reduce((a, b) => a + b, 0) / ACCURACY.length) : 0
return (
<div className="flex flex-col h-full">
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 flex flex-col group hover:border-[var(--cyan-ai-30)] transition-colors">
<CardContent className="p-5 flex flex-col gap-4 h-full">
{/* Summary chips */}
<div className="flex items-center gap-2 flex-wrap">
<div className="flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full bg-green-500/10 text-green-500 border border-green-500/20">
<TrendingUp size={12} />
{avgAccuracy}% avg accuracy
</div>
<div className={`flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full border ${trendUp ? "bg-[var(--cyan-ai-10)] text-[var(--cyan-ai)] border-[var(--cyan-ai-20)]" : "bg-red-500/10 text-red-500 border-red-500/20"}`}>
{trendUp ? <TrendingUp size={12} /> : <TrendingDown size={12} />}
{trendUp ? "+" : ""}{trend}{UNIT} vs Mon
</div>
<div className="flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full bg-yellow-500/10 text-yellow-500 border border-yellow-500/20">
<Zap size={12} />
{isDemo ? "7/7 days active" : "Day 1 Active"}
</div>
</div>
{/* Metric Toggle */}
<div className="flex gap-1.5">
{(["accuracy", "questions", "hours"] as const).map((m) => (
<button
key={m}
onClick={() => setMetric(m)}
className={`flex-1 py-1.5 text-xs font-bold rounded-lg border transition-all capitalize ${metric === m ? "bg-[var(--cyan-ai-10)] border-[var(--cyan-ai-20)] text-[var(--cyan-ai)]" : "bg-white/5 border-transparent text-muted-foreground hover:bg-white/10"}`}
>
{m === "accuracy" ? "Accuracy" : m === "questions" ? "Questions" : "Study Hrs"}
</button>
))}
</div>
{/* Bar Chart with Y-axis */}
<div className="flex gap-2 flex-1 min-h-[120px]">
{/* Y-axis */}
<div className="flex flex-col justify-between pb-5 text-right pr-1">
{[100, 75, 50, 25, 0].map(v => (
<span key={v} className="text-[10px] text-muted-foreground font-semibold leading-none">
{metric === "hours" ? (v / 100 * 5).toFixed(1) : `${v}${metric === "accuracy" ? "%" : ""}`}
</span>
))}
</div>
<div className="flex-1 flex flex-col gap-0.5">
{/* Grid + Bars */}
<div className="relative flex-1 min-h-[120px]">
{[25, 50, 75].map(pct => (
<div key={pct} className="absolute w-full border-t border-border/15" style={{ bottom: `${pct}%` }} />
))}
<div className="absolute inset-0 flex items-end gap-1.5">
{DAYS.map((day, i) => (
<div
key={day}
className="flex-1 flex flex-col justify-end items-center h-full relative cursor-pointer"
onMouseEnter={() => setHovered(i)}
onMouseLeave={() => setHovered(null)}
>
{/* Tooltip */}
{hovered === i && (
<div className="absolute bottom-full mb-1.5 left-1/2 -translate-x-1/2 bg-background border border-border rounded-lg px-2.5 py-1.5 text-xs font-bold whitespace-nowrap shadow-lg z-10 pointer-events-none">
<div>{day}</div>
<div className="text-[var(--cyan-ai)]">{DATA[i]}{UNIT}</div>
</div>
)}
<div
className={`w-full rounded-t-md ${
!isDemo
? "bg-white/5 border border-white/10"
: i === 6
? "bg-gradient-to-t from-[var(--cyan-ai-50)] to-[var(--cyan-ai)]"
: hovered === i
? "bg-gradient-to-t from-[var(--cyan-ai-40)] to-[var(--cyan-ai-80)]"
: "bg-gradient-to-t from-white/10 to-white/25"
}`}
style={{ height: isDemo ? `${(DATA[i] / maxVal) * 100}%` : "4px" }}
/>
</div>
))}
</div>
</div>
{/* X labels */}
<div className="flex gap-1.5 pt-1">
{DAYS.map((day) => (
<div key={day} className="flex-1 text-center">
<span className="text-[10px] text-muted-foreground font-bold">{day}</span>
</div>
))}
</div>
</div>
</div>
{/* Quick stats row */}
<div className="grid grid-cols-3 gap-2">
{[
{ label: "Avg Accuracy", value: `${avgAccuracy}%`, icon: <Target size={12} className="text-green-400" /> },
{ label: "Questions / Day", value: isDemo ? `${Math.round(QUESTIONS.reduce((a, b) => a + b, 0) / 7)}` : "0", icon: <Zap size={12} className="text-[var(--cyan-ai)]" /> },
{ label: "Study Hrs / Day", value: isDemo ? `${(HOURS.reduce((a, b) => a + b, 0) / 7).toFixed(1)}h` : "0.0h", icon: <Clock size={12} className="text-amber-400" /> },
].map(stat => (
<div key={stat.label} className="p-2.5 rounded-xl bg-white/5 border border-border/30 text-center">
<div className="flex items-center justify-center gap-1 mb-0.5">{stat.icon}</div>
<div className="text-base font-black">{stat.value}</div>
<div className="text-[10px] text-muted-foreground font-semibold">{stat.label}</div>
</div>
))}
</div>
{/* AI insight */}
<div className="px-3 py-2.5 bg-[var(--cyan-ai-10)] border border-[var(--cyan-ai-20)] rounded-xl text-xs text-[var(--cyan-ai)] font-medium flex items-start gap-2.5">
<Brain size={15} className="shrink-0 mt-0.5" />
<div>
{isDemo ? (
<>
<span className="font-black text-white">Sign errors in Thermodynamics</span> — detected in your last 3 attempts (W = −PΔV misapplied). We&apos;ve queued 3 targeted practice questions for tomorrow.{" "}
<span className="text-[var(--cyan-ai-80)]"><strong className="text-white">Tip:</strong> You peak at 7–9 PM. Schedule the hardest topics then.</span>
</>
) : (
<>
<span className="font-black text-white">AI Progress Engine Calibrating</span> — Complete your first CBT mock test or DPP to begin mapping subject retention, speed trends, and error velocity.
</>
)}
</div>
</div>
</CardContent>
</Card>
</div>
)
}