feat(v3): restructure project + add complete frontend

- Restructure: move backend from new_project/ to backend/
- Add full React/TypeScript frontend (37 pages, 17 services, 16 type defs, 11 query hooks)
- Add docs/ with SRS specs, user stories, and workflow documentation
- Update .gitignore for new directory layout

Workflows implemented:
  WF1 User Signup, WF2 Placement Test, WF3 Exam Configuration,
  WF4 General English Exam, WF5 Course Generation,
  WF6 Entity Student Onboarding, AI Course Generation,
  Adaptive Learning Engine UI, White-Label Branding, Score Release

Made-with: Cursor
This commit is contained in:
Yamen Ahmad
2026-04-10 17:26:42 +04:00
parent a3e12f62fa
commit f1c4953a63
731 changed files with 67205 additions and 139 deletions

View File

@@ -0,0 +1,86 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { useGrades } from "@/hooks/queries";
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts";
import AiGradeExplainer from "@/components/ai/AiGradeExplainer";
import AiReportNarrative from "@/components/ai/AiReportNarrative";
export default function StudentGrades() {
const { data: gradeRecords = [], isLoading } = useGrades();
if (isLoading) return <div className="flex items-center justify-center min-h-[400px]"><div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" /></div>;
const avgGrade = gradeRecords.length
? (gradeRecords.reduce((sum, g) => sum + (g.grade / g.maxGrade) * 100, 0) / gradeRecords.length).toFixed(1)
: "0";
const highest = gradeRecords.length
? Math.max(...gradeRecords.map(g => (g.grade / g.maxGrade) * 100)).toFixed(1)
: "0";
const chartData = gradeRecords.map(g => ({
name: g.assignmentTitle.length > 15 ? g.assignmentTitle.slice(0, 15) + "…" : g.assignmentTitle,
grade: Math.round((g.grade / g.maxGrade) * 100),
}));
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold">Grades</h1>
<p className="text-muted-foreground">Track your academic performance.</p>
</div>
<AiReportNarrative narrative={`Your average grade is ${avgGrade}%. Your strongest area is essay writing with consistent scores above 80%. Focus on improving speaking scores — your last mock test scored 72%, which is below your average. AI recommends practicing with the IELTS Speaking Masterclass materials.`} />
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<Card><CardContent className="pt-6 text-center"><p className="text-sm text-muted-foreground">Average</p><p className="text-3xl font-bold text-primary">{avgGrade}%</p></CardContent></Card>
<Card><CardContent className="pt-6 text-center"><p className="text-sm text-muted-foreground">Highest</p><p className="text-3xl font-bold text-success">{highest}%</p></CardContent></Card>
<Card><CardContent className="pt-6 text-center"><p className="text-sm text-muted-foreground">Graded Items</p><p className="text-3xl font-bold">{gradeRecords.length}</p></CardContent></Card>
</div>
<Card>
<CardHeader><CardTitle className="text-lg">Grade Distribution</CardTitle></CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={250}>
<BarChart data={chartData}>
<CartesianGrid strokeDasharray="3 3" className="stroke-border" />
<XAxis dataKey="name" tick={{ fontSize: 11 }} className="fill-muted-foreground" />
<YAxis domain={[0, 100]} tick={{ fontSize: 11 }} className="fill-muted-foreground" />
<Tooltip />
<Bar dataKey="grade" fill="hsl(192, 82%, 32%)" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
<Card>
<CardHeader><CardTitle className="text-lg">All Grades</CardTitle></CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Assignment</TableHead>
<TableHead>Course</TableHead>
<TableHead>Type</TableHead>
<TableHead>Date</TableHead>
<TableHead className="text-right">Grade</TableHead>
<TableHead className="w-10"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{gradeRecords.map(g => (
<TableRow key={g.id}>
<TableCell className="font-medium">{g.assignmentTitle}</TableCell>
<TableCell>{g.courseName}</TableCell>
<TableCell><Badge variant="outline" className="text-xs capitalize">{g.type}</Badge></TableCell>
<TableCell>{g.date}</TableCell>
<TableCell className="text-right font-bold">{g.grade}/{g.maxGrade}</TableCell>
<TableCell><AiGradeExplainer studentName="Sarah Johnson" /></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
);
}