feat: complete pragativeer v2 frontend and backend, docker setup, and cbt engine
This commit is contained in:
192
src/app/dashboard/progress/page.tsx
Normal file
192
src/app/dashboard/progress/page.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||||
import { ChevronDown, ChevronRight, CheckCircle2, Circle, Clock, Activity, Target } from "lucide-react"
|
||||
|
||||
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} />
|
||||
}
|
||||
}
|
||||
|
||||
export default function ProgressTrackerPage() {
|
||||
const [subjects, setSubjects] = useState<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",
|
||||
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 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-5xl mx-auto">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight text-foreground flex items-center gap-3">
|
||||
<Target className="text-[#00F0FF]" size={32} />
|
||||
Syllabus Tracker
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2 font-medium">Track your syllabus coverage and mastery per chapter to ensure no gaps.</p>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user