270 lines
10 KiB
TypeScript
270 lines
10 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
|
import { ChevronDown, ChevronRight, CheckCircle2, Circle, Clock, Activity, Target } from "lucide-react"
|
|
import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser"
|
|
|
|
type ChapterStatus = 'Mastered' | 'Practiced' | 'In Progress' | 'Not Started'
|
|
|
|
interface Topic {
|
|
name: string
|
|
status: ChapterStatus
|
|
accuracy: number
|
|
questionsAttempted: number
|
|
}
|
|
|
|
interface Chapter {
|
|
name: string
|
|
topics: Topic[]
|
|
isExpanded: boolean
|
|
}
|
|
|
|
interface Subject {
|
|
name: string
|
|
chapters: Chapter[]
|
|
completionPct: number
|
|
}
|
|
|
|
const getStatusIcon = (status: ChapterStatus) => {
|
|
switch (status) {
|
|
case 'Mastered': return <CheckCircle2 className="text-emerald-500" size={16} />
|
|
case 'Practiced': return <CheckCircle2 className="text-amber-500" size={16} />
|
|
case 'In Progress': return <Clock className="text-[#00F0FF]" size={16} />
|
|
case 'Not Started': return <Circle className="text-muted-foreground" size={16} />
|
|
}
|
|
}
|
|
|
|
const DEMO_SUBJECTS: Subject[] = [
|
|
{
|
|
name: "Physics",
|
|
completionPct: 42,
|
|
chapters: [
|
|
{
|
|
name: "Kinematics",
|
|
isExpanded: true,
|
|
topics: [
|
|
{ name: "Motion in One Dimension", status: "Mastered", accuracy: 92, questionsAttempted: 150 },
|
|
{ name: "Projectile Motion", status: "Practiced", accuracy: 75, questionsAttempted: 85 },
|
|
{ name: "Relative Velocity", status: "In Progress", accuracy: 45, questionsAttempted: 20 },
|
|
]
|
|
},
|
|
{
|
|
name: "Laws of Motion",
|
|
isExpanded: false,
|
|
topics: [
|
|
{ name: "Newton's First Law", status: "Practiced", accuracy: 88, questionsAttempted: 40 },
|
|
{ name: "Friction", status: "In Progress", accuracy: 50, questionsAttempted: 35 },
|
|
]
|
|
},
|
|
{
|
|
name: "Thermodynamics",
|
|
isExpanded: false,
|
|
topics: [
|
|
{ name: "First Law", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
{ name: "Carnot Engine", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: "Chemistry",
|
|
completionPct: 68,
|
|
chapters: [
|
|
{
|
|
name: "Atomic Structure",
|
|
isExpanded: false,
|
|
topics: [
|
|
{ name: "Bohr Model", status: "Mastered", accuracy: 95, questionsAttempted: 110 },
|
|
{ name: "Quantum Numbers", status: "Mastered", accuracy: 88, questionsAttempted: 95 },
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: "Mathematics / Biology",
|
|
completionPct: 22,
|
|
chapters: [
|
|
{
|
|
name: "Calculus",
|
|
isExpanded: false,
|
|
topics: [
|
|
{ name: "Limits", status: "In Progress", accuracy: 60, questionsAttempted: 45 },
|
|
{ name: "Derivatives", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
const ZERO_SUBJECTS: Subject[] = [
|
|
{
|
|
name: "Physics",
|
|
completionPct: 0,
|
|
chapters: [
|
|
{
|
|
name: "Kinematics",
|
|
isExpanded: true,
|
|
topics: [
|
|
{ name: "Motion in One Dimension", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
{ name: "Projectile Motion", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
{ name: "Relative Velocity", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
]
|
|
},
|
|
{
|
|
name: "Laws of Motion",
|
|
isExpanded: false,
|
|
topics: [
|
|
{ name: "Newton's First Law", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
{ name: "Friction", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
]
|
|
},
|
|
{
|
|
name: "Thermodynamics",
|
|
isExpanded: false,
|
|
topics: [
|
|
{ name: "First Law", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
{ name: "Carnot Engine", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: "Chemistry",
|
|
completionPct: 0,
|
|
chapters: [
|
|
{
|
|
name: "Atomic Structure",
|
|
isExpanded: false,
|
|
topics: [
|
|
{ name: "Bohr Model", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
{ name: "Quantum Numbers", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: "Mathematics / Biology",
|
|
completionPct: 0,
|
|
chapters: [
|
|
{
|
|
name: "Core Foundation",
|
|
isExpanded: false,
|
|
topics: [
|
|
{ name: "Limits / Cell Structure", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
{ name: "Derivatives / Genetics", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
export default function ProgressTrackerPage() {
|
|
const { userId } = useActiveUser()
|
|
const isDemo = isDemoUser(userId)
|
|
const [subjects, setSubjects] = useState<Subject[]>(isDemo ? DEMO_SUBJECTS : ZERO_SUBJECTS)
|
|
|
|
useEffect(() => {
|
|
setSubjects(isDemo ? DEMO_SUBJECTS : ZERO_SUBJECTS)
|
|
}, [isDemo])
|
|
|
|
const toggleChapter = (subjectIndex: number, chapterIndex: number) => {
|
|
const newSubjects = [...subjects]
|
|
newSubjects[subjectIndex].chapters[chapterIndex].isExpanded = !newSubjects[subjectIndex].chapters[chapterIndex].isExpanded
|
|
setSubjects(newSubjects)
|
|
}
|
|
|
|
return (
|
|
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
|
|
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4">
|
|
<div>
|
|
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
|
|
<span className="p-2.5 rounded-xl bg-yellow-50 dark:bg-[#00F0FF]/10 border border-yellow-300/60 dark:border-[#00F0FF]/20">
|
|
<Target className="text-yellow-600 dark:text-[#00F0FF]" size={28} />
|
|
</span>
|
|
Syllabus Tracker
|
|
</h1>
|
|
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
|
Track your syllabus coverage and mastery per chapter to ensure no gaps.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
|
{subjects.map((sub, i) => (
|
|
<Card key={i} className="border-border shadow-sm">
|
|
<CardContent className="p-6 text-center">
|
|
<h3 className="font-bold text-lg mb-2">{sub.name}</h3>
|
|
<div className="text-3xl font-black text-[#00F0FF] mb-3">{sub.completionPct}%</div>
|
|
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
|
|
<div className="h-full bg-[#00F0FF]" style={{ width: `${sub.completionPct}%` }}></div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
|
|
<div className="space-y-6">
|
|
{subjects.map((subject, subIndex) => (
|
|
<Card key={subIndex} className="border-border shadow-sm overflow-hidden">
|
|
<CardHeader className="bg-secondary/30 pb-4 border-b border-border">
|
|
<CardTitle className="text-xl">{subject.name} Syllabus</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="p-0">
|
|
<div className="divide-y divide-border">
|
|
{subject.chapters.map((chapter, chapIndex) => (
|
|
<div key={chapIndex} className="flex flex-col">
|
|
<button
|
|
onClick={() => toggleChapter(subIndex, chapIndex)}
|
|
className="flex items-center justify-between p-4 hover:bg-secondary/20 transition-colors text-left"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
{chapter.isExpanded ? <ChevronDown size={18} className="text-muted-foreground" /> : <ChevronRight size={18} className="text-muted-foreground" />}
|
|
<span className="font-bold">{chapter.name}</span>
|
|
</div>
|
|
<div className="text-xs font-medium bg-secondary px-3 py-1 rounded-full">
|
|
{chapter.topics.length} Topics
|
|
</div>
|
|
</button>
|
|
|
|
{chapter.isExpanded && (
|
|
<div className="bg-background px-6 pb-4 pt-1 space-y-2">
|
|
<div className="flex justify-between items-center text-xs font-bold text-muted-foreground px-2 py-2">
|
|
<span className="w-1/2">TOPIC</span>
|
|
<span className="w-1/4 text-center">ACCURACY</span>
|
|
<span className="w-1/4 text-right">QUESTIONS</span>
|
|
</div>
|
|
{chapter.topics.map((topic, topIndex) => (
|
|
<div key={topIndex} className="flex justify-between items-center bg-secondary/10 p-3 rounded-lg border border-border/50">
|
|
<div className="w-1/2 flex items-center gap-3">
|
|
{getStatusIcon(topic.status)}
|
|
<div>
|
|
<p className="font-medium text-sm">{topic.name}</p>
|
|
<p className="text-[10px] uppercase tracking-wider text-muted-foreground">{topic.status}</p>
|
|
</div>
|
|
</div>
|
|
<div className="w-1/4 text-center">
|
|
{topic.accuracy > 0 ? (
|
|
<span className={`font-mono text-sm font-bold ${topic.accuracy > 80 ? 'text-emerald-500' : topic.accuracy > 60 ? 'text-amber-500' : 'text-red-500'}`}>
|
|
{topic.accuracy}%
|
|
</span>
|
|
) : <span className="text-muted-foreground text-sm">-</span>}
|
|
</div>
|
|
<div className="w-1/4 text-right">
|
|
<span className="font-mono text-sm">{topic.questionsAttempted > 0 ? topic.questionsAttempted : '-'}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|