feat: setup admin JSON generation and pdf viewer for free plan DPPs

This commit is contained in:
2026-07-27 14:56:25 +05:30
parent c214ca41b6
commit a5bb2d867c
74 changed files with 8546 additions and 915 deletions

View File

@@ -1,8 +1,9 @@
"use client"
import { useEffect, useState } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Activity, RefreshCw } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import { TrendingUp, Flame, RefreshCw } from "lucide-react"
import Link from "next/link"
import { useActiveUser } from "@/hooks/useActiveUser"
@@ -59,65 +60,108 @@ export function ReadinessGauge() {
}
return (
<Card className="border-border shadow-sm h-full flex flex-col">
<CardHeader className="pb-2">
<CardTitle className="text-xl flex items-center gap-2">
<Activity className="text-primary" size={24} />
Exam Readiness
</CardTitle>
<CardDescription>Real-time AI composite score.</CardDescription>
</CardHeader>
<div className="flex flex-col h-full">
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
🎯 Exam Readiness
</h2>
<Link href="/dashboard/ai-coach/readiness" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">Expand View ↗</Link>
</div>
<CardContent className="flex flex-col items-center flex-1 justify-center p-6 space-y-6">
<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-4 group hover:border-[#00F0FF]/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" />
{/* SVG Circular Gauge */}
<div className="relative w-32 h-32 flex items-center justify-center">
<svg className="w-full h-full transform -rotate-90" viewBox="0 0 100 100">
{/* Background Ring */}
<circle cx="50" cy="50" r="45" stroke="currentColor" strokeWidth="8" fill="transparent" className="text-secondary" />
{/* Progress Ring */}
<circle
cx="50" cy="50" r="45"
stroke="currentColor"
strokeWidth="8"
fill="transparent"
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
strokeLinecap="round"
className={`transition-all duration-1000 ease-out ${getColor(score)}`}
/>
</svg>
<div className="absolute flex flex-col items-center justify-center">
<span className="text-3xl font-black">{score}</span>
<span className="text-[10px] text-muted-foreground uppercase tracking-wider font-bold">/ 100</span>
<div className="flex justify-between items-start mb-4 relative z-10">
<span className="text-[11px] font-bold text-muted-foreground uppercase tracking-widest">Updated daily • Today</span>
<span className="flex items-center gap-1 text-xs font-bold text-green-500 bg-green-500/10 px-2.5 py-0.5 rounded-full">
<TrendingUp size={14} className="stroke-[2.5px]" />
+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="#00F0FF" />
<stop offset="100%" stopColor="#3B82F6" />
</linearGradient>
</defs>
<circle cx="50" cy="50" r="45" stroke="currentColor" strokeWidth="6" fill="transparent" className="text-[#00F0FF]/10" />
<circle
cx="50" cy="50" r="45"
stroke="url(#scoreGradient)"
strokeWidth="6"
fill="transparent"
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
strokeLinecap="round"
className="transition-all duration-1500 ease-out"
/>
</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 w-full">
<div className="bg-white/5 rounded-md p-2">
<div className="text-sm font-bold flex items-baseline gap-1">88% <span className="text-[10px] text-green-500">↑</span></div>
<div className="text-[10px] font-medium text-muted-foreground">Accuracy</div>
</div>
<div className="bg-white/5 rounded-md p-2">
<div className="text-sm font-bold flex items-baseline gap-1">45s <span className="text-[10px] text-red-500">↓</span></div>
<div className="text-[10px] font-medium text-muted-foreground">Speed/Q</div>
</div>
<div className="bg-white/5 rounded-md p-2">
<div className="text-sm font-bold flex items-baseline gap-1">High <span className="text-[10px] text-green-500">↑</span></div>
<div className="text-[10px] font-medium text-muted-foreground">Consistency</div>
</div>
<div className="bg-white/5 rounded-md p-2">
<div className="text-sm font-bold flex items-baseline gap-1">64% <span className="text-[10px] text-green-500">↑</span></div>
<div className="text-[10px] font-medium text-muted-foreground">Syllabus</div>
</div>
</div>
</div>
{/* Breakdown Bars */}
<div className="w-full space-y-3">
{/* Subject Bars */}
<div className="space-y-1.5 mb-3 relative z-10">
{[
{ label: "Accuracy", value: breakdown.accuracy, max: 40 },
{ label: "Speed", value: breakdown.speed, max: 20 },
{ label: "Coverage", value: breakdown.coverage, max: 20 },
{ label: "Consistency", value: breakdown.consistency, max: 10 },
{ label: "Recovery", value: breakdown.recovery, max: 10 },
].map((item, idx) => (
<div key={idx} className="space-y-1">
<div className="flex justify-between text-xs font-semibold">
<span className="text-muted-foreground">{item.label}</span>
<span>{item.value} <span className="opacity-50">/ {item.max}</span></span>
</div>
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
<div
className="h-full bg-primary transition-all duration-1000 ease-out"
style={{ width: `${(item.value / item.max) * 100}%` }}
{ name: "Physics", val: 68, color: "#00F0FF" },
{ name: "Chemistry", val: 74, color: "#F59E0B" },
{ name: "Biology", val: 82, color: "#10B981" },
].map(sub => (
<div key={sub.name} className="flex items-center gap-2">
<span className="text-[9px] font-semibold text-muted-foreground w-14 shrink-0">{sub.name}</span>
<div className="flex-1 h-1.5 rounded-full bg-white/10 overflow-hidden">
<div
className="h-full rounded-full transition-all duration-1000"
style={{ width: `${sub.val}%`, background: sub.color }}
/>
</div>
<span className="text-[9px] font-bold w-7 text-right" style={{ color: sub.color }}>{sub.val}%</span>
</div>
))}
</div>
</CardContent>
</Card>
{/* National Avg */}
<div className="flex items-center justify-between px-2 py-1.5 bg-white/5 rounded-md mb-3 relative z-10">
<span className="text-[10px] text-muted-foreground font-semibold">National Avg</span>
<span className="text-[10px] font-bold text-muted-foreground">58 / 100</span>
<span className="text-[10px] font-bold text-green-500">You: +{score - 58} above avg</span>
</div>
<div className="mt-auto px-3 py-2.5 bg-[#00F0FF]/10 border border-[#00F0FF]/20 rounded-md text-xs text-[#00F0FF] font-semibold flex items-center gap-2 relative z-10">
<Flame size={16} className="fill-[#00F0FF]/20 shrink-0" />
Your speed has dropped slightly. Focus on time-bound practice today.
</div>
</Card>
</div>
)
}