chore: remove parent dashboard UI and update PRD tags
This commit is contained in:
42
PRD.md
42
PRD.md
@@ -78,40 +78,34 @@ The stack is orchestrated using `docker-compose.yml` and consists of 8 distinct
|
||||
The backend exposes the following RESTful APIs powered by FastAPI:
|
||||
|
||||
**Authentication**
|
||||
- `POST /api/auth/register` - Create new student account
|
||||
- `POST /api/auth/login` - Authenticate & receive JWT session
|
||||
- `POST /api/auth/forgot-password` - Request password reset link
|
||||
- `POST /api/auth/otp-request` / `otp-verify` - Phone number authentication
|
||||
- `[Built] POST /api/auth/register` - Create new student account
|
||||
- `[Built] POST /api/auth/login` - Authenticate & receive JWT session
|
||||
- `[Built] POST /api/auth/forgot-password` - Request password reset link
|
||||
- `[Planned] POST /api/auth/otp-request` / `otp-verify` - Phone number authentication
|
||||
|
||||
**Users & Profiles**
|
||||
- `GET/PUT /api/users/{id}` - Fetch/update user profiles and settings
|
||||
- `POST /api/users/{id}/avatar` - Upload profile picture to MinIO
|
||||
- `GET /api/users/{id}/attempts` - Retrieve past test history
|
||||
- `[Built] GET/PUT /api/users/{id}` - Fetch/update user profiles and settings
|
||||
- `[Planned] POST /api/users/{id}/avatar` - Upload profile picture to MinIO
|
||||
- `[Built] GET /api/users/{id}/attempts` - Retrieve past test history
|
||||
|
||||
**CBT Engine**
|
||||
- `GET /api/tests` - List active Mock Tests, PYQs, and Weekly Tests
|
||||
- `GET /api/tests/{id}/questions` - Fetch test sections and questions
|
||||
- `POST /api/attempts/submit` - Auto-grade and record a CBT session
|
||||
- `[Built] GET /api/tests` - List active Mock Tests, PYQs, and Weekly Tests
|
||||
- `[Built] GET /api/tests/{id}/questions` - Fetch test sections and questions
|
||||
- `[Built] POST /api/attempts/submit` - Auto-grade and record a CBT session
|
||||
|
||||
**AI Coach**
|
||||
- `GET /api/ai-coach/readiness/{id}` - Predict JEE/NEET rank and college
|
||||
- `GET /api/ai-coach/study-plan/today` - Generate daily tasks
|
||||
- `GET /api/mistakes/today` - Fetch spaced repetition flashcards (SM-2 algorithm)
|
||||
- `POST /api/mistakes/review` - Update mistake mastery based on recall
|
||||
- `POST /api/ai-coach/chat/stream` - AI Tutor socket connection
|
||||
- `[Planned] GET /api/ai-coach/readiness/{id}` - Predict JEE/NEET rank and college
|
||||
- `[Planned] GET /api/ai-coach/study-plan/today` - Generate daily tasks
|
||||
- `[Planned] GET /api/mistakes/today` - Fetch spaced repetition flashcards (SM-2 algorithm)
|
||||
- `[Planned] POST /api/mistakes/review` - Update mistake mastery based on recall
|
||||
- `[Built] POST /api/ai-coach/chat/stream` - AI Tutor socket connection
|
||||
|
||||
**Subscription & Billing (Gateway Agnostic)**
|
||||
- `POST /api/billing/create-order` - Generate payment gateway order IDs
|
||||
- `POST /api/billing/verify-payment` - Cryptographically verify webhooks and upgrade tier
|
||||
- `[Built] POST /api/billing/create-order` - Generate payment gateway order IDs
|
||||
- `[Built] POST /api/billing/verify-payment` - Cryptographically verify webhooks and upgrade tier
|
||||
|
||||
**Admin System**
|
||||
- `GET/POST/PUT/DELETE /api/admin/*` - Full CRUD for Tests, Users, Coupons, and System Settings
|
||||
|
||||
**Parent System (Planned / UI Mocked)**
|
||||
- *Note: The UI exists at `/parent/dashboard` but currently uses fallback mock data. Backend APIs are not yet built.*
|
||||
- `[Planned] POST /api/parent/register` - Parent account creation
|
||||
- `[Planned] POST /api/parent/link-student` - Link student to parent dashboard
|
||||
- `[Planned] GET /api/parent/{parent_id}/dashboard/{student_id}` - Fetch real-time student analytics
|
||||
- `[Built] GET/POST/PUT/DELETE /api/admin/*` - Full CRUD for Tests, Users, Coupons, and System Settings
|
||||
|
||||
## 5. Technology Stack Summary
|
||||
- **Frontend:** Next.js 14/15, TailwindCSS, Framer Motion, Lucide Icons, Shadcn UI.
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts"
|
||||
import { Activity, Clock, Flame, AlertTriangle, RefreshCw } from "lucide-react"
|
||||
|
||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||
|
||||
// Hardcoded mock UUIDs for this sprint
|
||||
const PARENT_ID = "parent-123"
|
||||
|
||||
const DICTIONARY = {
|
||||
en: {
|
||||
title: "Parent Monitoring Portal",
|
||||
subtitle: "Real-time analytics for your child's learning journey.",
|
||||
lang_toggle: "हिंदी",
|
||||
time_spent: "Weekly Time Spent",
|
||||
minutes: "Minutes",
|
||||
streak: "Active Streak",
|
||||
days: "Days",
|
||||
tests_attempted: "Tests Attempted",
|
||||
tests: "This week",
|
||||
accuracy_trend: "7-Day Accuracy Trend",
|
||||
areas_of_concern: "Areas of Concern",
|
||||
attention_needed: "Attention Needed",
|
||||
pending_errors: "pending errors",
|
||||
loading: "Loading child data...",
|
||||
no_concerns: "No current areas of concern. Great job!"
|
||||
},
|
||||
hi: {
|
||||
title: "अभिभावक निगरानी पोर्टल",
|
||||
subtitle: "आपके बच्चे की सीखने की यात्रा के लिए वास्तविक समय विश्लेषिकी।",
|
||||
lang_toggle: "English",
|
||||
time_spent: "साप्ताहिक समय व्यतीत",
|
||||
minutes: "मिनट",
|
||||
streak: "सक्रिय लकीर",
|
||||
days: "दिन",
|
||||
tests_attempted: "परीक्षण का प्रयास",
|
||||
tests: "इस सप्ताह",
|
||||
accuracy_trend: "7-दिवसीय सटीकता प्रवृत्ति",
|
||||
areas_of_concern: "चिंता के विषय",
|
||||
attention_needed: "ध्यान देने की आवश्यकता है",
|
||||
pending_errors: "लंबित त्रुटियां",
|
||||
loading: "बच्चे का डेटा लोड हो रहा है...",
|
||||
no_concerns: "चिंता का कोई विषय नहीं है। बहुत बढ़िया!"
|
||||
}
|
||||
}
|
||||
|
||||
export default function ParentDashboard() {
|
||||
const { userId, loading: userLoading } = useActiveUser()
|
||||
const [lang, setLang] = useState<'en' | 'hi'>('en')
|
||||
const [data, setData] = useState<any>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const t = DICTIONARY[lang]
|
||||
|
||||
useEffect(() => {
|
||||
if (userLoading || !userId) return;
|
||||
async function fetchDashboard() {
|
||||
try {
|
||||
const res = await fetch(`/api/parent/${PARENT_ID}/dashboard/${userId}`)
|
||||
if (!res.ok) throw new Error("Failed to fetch")
|
||||
const payload = await res.json()
|
||||
setData(payload)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
// Fallback mock data if server fails
|
||||
setData({
|
||||
student_name: "Rahul",
|
||||
streak_count: 5,
|
||||
weekly_minutes_spent: 124.5,
|
||||
total_tests_attempted_this_week: 3,
|
||||
accuracy_trend: [
|
||||
{ date: "Jul 01", accuracy: 40 },
|
||||
{ date: "Jul 02", accuracy: 55 },
|
||||
{ date: "Jul 03", accuracy: 50 },
|
||||
{ date: "Jul 04", accuracy: 70 },
|
||||
{ date: "Jul 05", accuracy: 65 },
|
||||
{ date: "Jul 06", accuracy: 80 },
|
||||
{ date: "Jul 07", accuracy: 85 }
|
||||
],
|
||||
areas_of_concern: [
|
||||
{ topic: "Thermodynamics", pending_errors: 4 },
|
||||
{ topic: "Integration", pending_errors: 2 }
|
||||
]
|
||||
})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
fetchDashboard()
|
||||
}, [userId, userLoading])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<RefreshCw className="animate-spin text-muted-foreground mr-3" /> {t.loading}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-8 max-w-6xl mx-auto space-y-10 animate-in fade-in duration-500">
|
||||
|
||||
{/* Header & Language Toggle */}
|
||||
<header className="flex flex-col md:flex-row md:items-center justify-between gap-4 border-b border-border pb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight text-primary">
|
||||
{t.title}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-1 font-medium">{t.subtitle} — {data?.student_name}</p>
|
||||
</div>
|
||||
{/*
|
||||
<div className="flex items-center space-x-3">
|
||||
<Label htmlFor="lang-switch" className="font-bold text-muted-foreground">{lang === 'hi' ? 'English' : 'हिंदी'}</Label>
|
||||
<Switch
|
||||
id="lang-switch"
|
||||
checked={lang === 'hi'}
|
||||
onCheckedChange={(c) => setLang(c ? 'hi' : 'en')}
|
||||
/>
|
||||
</div>
|
||||
*/}
|
||||
</header>
|
||||
|
||||
{/* Metric Blocks */}
|
||||
<section className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<Card className="border-border shadow-sm bg-secondary/10">
|
||||
<CardContent className="p-6 flex items-center gap-5">
|
||||
<div className="p-4 bg-primary/10 rounded-2xl text-primary"><Clock size={28}/></div>
|
||||
<div>
|
||||
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">{t.time_spent}</p>
|
||||
<h3 className="text-3xl font-black mt-1">{data?.weekly_minutes_spent} <span className="text-sm font-semibold text-muted-foreground lowercase">{t.minutes}</span></h3>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-border shadow-sm bg-secondary/10">
|
||||
<CardContent className="p-6 flex items-center gap-5">
|
||||
<div className="p-4 bg-amber-500/10 rounded-2xl text-amber-500"><Flame size={28}/></div>
|
||||
<div>
|
||||
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">{t.streak}</p>
|
||||
<h3 className="text-3xl font-black mt-1">{data?.streak_count} <span className="text-sm font-semibold text-muted-foreground lowercase">{t.days}</span></h3>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-border shadow-sm bg-secondary/10">
|
||||
<CardContent className="p-6 flex items-center gap-5">
|
||||
<div className="p-4 bg-emerald-500/10 rounded-2xl text-emerald-500"><Activity size={28}/></div>
|
||||
<div>
|
||||
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">{t.tests_attempted}</p>
|
||||
<h3 className="text-3xl font-black mt-1">{data?.total_tests_attempted_this_week} <span className="text-sm font-semibold text-muted-foreground lowercase">{t.tests}</span></h3>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
{/* Main Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
|
||||
{/* Chart */}
|
||||
<Card className="lg:col-span-2 border-border shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">{t.accuracy_trend}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-[300px] w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={data?.accuracy_trend}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#333" opacity={0.2} />
|
||||
<XAxis dataKey="date" stroke="#888" fontSize={12} tickLine={false} axisLine={false} />
|
||||
<YAxis stroke="#888" fontSize={12} tickLine={false} axisLine={false} domain={[0, 100]} tickFormatter={(val) => `${val}%`} />
|
||||
<Tooltip
|
||||
contentStyle={{ backgroundColor: 'hsl(var(--background))', borderColor: 'hsl(var(--border))', borderRadius: '8px' }}
|
||||
itemStyle={{ color: 'hsl(var(--primary))' }}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="accuracy"
|
||||
stroke="hsl(var(--primary))"
|
||||
strokeWidth={4}
|
||||
dot={{ r: 4, fill: 'hsl(var(--primary))' }}
|
||||
activeDot={{ r: 8 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Areas of Concern */}
|
||||
<Card className="border-border shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<AlertTriangle className="text-red-500" size={20} />
|
||||
{t.areas_of_concern}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{!data?.areas_of_concern || data.areas_of_concern.length === 0 ? (
|
||||
<div className="text-center p-6 bg-secondary/20 rounded-xl">
|
||||
<p className="text-muted-foreground font-medium">{t.no_concerns}</p>
|
||||
</div>
|
||||
) : (
|
||||
data.areas_of_concern.map((concern: any, idx: number) => (
|
||||
<div key={idx} className="p-4 rounded-xl border border-red-500/20 bg-red-500/5 relative overflow-hidden group">
|
||||
<div className="absolute left-0 top-0 bottom-0 w-1 bg-red-500 rounded-l-xl"></div>
|
||||
<h4 className="font-bold text-foreground mb-1">{concern.topic}</h4>
|
||||
<p className="text-xs font-semibold text-red-500 uppercase tracking-wider">
|
||||
{t.attention_needed} • {concern.pending_errors} {t.pending_errors}
|
||||
</p>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user