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
commit 11a7265460
392 changed files with 62287 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
import { useParams, Link } from "react-router-dom";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { useBatches, useStudents, useTimetable } from "@/hooks/queries";
import { ArrowLeft, Users, Calendar, BookOpen } from "lucide-react";
export default function AdminBatchDetail() {
const { id } = useParams();
const batchId = id ? Number(id) : NaN;
const { data: batchesData, isLoading: lb } = useBatches();
const { data: studentsData, isLoading: ls } = useStudents(
Number.isFinite(batchId) ? { batch_id: batchId, size: 500 } : { size: 500 },
);
const { data: timetableData, isLoading: ltt } = useTimetable();
const batches = batchesData?.items ?? [];
const students = studentsData?.items ?? [];
const timetableSessions = Array.isArray(timetableData) ? timetableData : [];
if (lb || ls || ltt) 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 batch = batches.find(b => String(b.id) === id);
if (!batch) return <div className="p-8 text-center text-muted-foreground">Batch not found.</div>;
const studentIds = Array.isArray(batch.student_ids) ? batch.student_ids : [];
const batchStudents = Number.isFinite(batchId)
? students.filter((s) => s.batch_id === batchId)
: students.filter((s) => studentIds.includes(s.id));
const coursePrefix = (batch.course_name || "").split(" ")[0];
const batchSessions = timetableSessions.filter(s =>
s.batch_name === batch.name || (coursePrefix && (s.course_name || "").includes(coursePrefix)),
);
return (
<div className="space-y-6">
<div className="flex items-center gap-3">
<Button variant="ghost" size="icon" asChild><Link to="/admin/batches"><ArrowLeft className="h-4 w-4" /></Link></Button>
<div>
<h1 className="text-2xl font-bold">{batch.name}</h1>
<p className="text-muted-foreground">{batch.course_name} · {batch.teacher_name}</p>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-4 gap-4">
<Card><CardContent className="pt-4"><Users className="h-4 w-4 text-primary mb-1" /><p className="text-lg font-bold">{batch.student_count}/{batch.capacity}</p><p className="text-xs text-muted-foreground">Students</p></CardContent></Card>
<Card><CardContent className="pt-4"><Calendar className="h-4 w-4 text-info mb-1" /><p className="text-sm font-bold">{batch.schedule}</p><p className="text-xs text-muted-foreground">Schedule</p></CardContent></Card>
<Card><CardContent className="pt-4"><BookOpen className="h-4 w-4 text-success mb-1" /><p className="text-sm font-bold">{batch.start_date} {batch.end_date}</p><p className="text-xs text-muted-foreground">Duration</p></CardContent></Card>
<Card><CardContent className="pt-4"><Badge variant={batch.status === "active" ? "default" : "secondary"} className="capitalize">{batch.status}</Badge><p className="text-xs text-muted-foreground mt-1">Status</p></CardContent></Card>
</div>
<Card>
<CardHeader><CardTitle className="text-lg">Enrolled Students</CardTitle></CardHeader>
<CardContent>
<Table>
<TableHeader><TableRow><TableHead>Student</TableHead><TableHead>Attendance</TableHead><TableHead>Avg Grade</TableHead><TableHead>Risk</TableHead></TableRow></TableHeader>
<TableBody>
{batchStudents.map(s => (
<TableRow key={s.id}>
<TableCell className="font-medium">{s.name}</TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell><Badge variant="secondary" className="capitalize"></Badge></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
<Card>
<CardHeader><CardTitle className="text-lg">Timetable</CardTitle></CardHeader>
<CardContent className="space-y-2">
{batchSessions.length === 0 ? (
<p className="text-muted-foreground text-sm">No sessions scheduled.</p>
) : batchSessions.map(s => (
<div key={s.id} className="flex items-center justify-between p-3 rounded border">
<div>
<p className="text-sm font-medium">{s.day} {s.start_time}{s.end_time}</p>
<p className="text-xs text-muted-foreground">{s.room}</p>
</div>
<span className="text-xs px-2 py-1 rounded text-white bg-primary">{s.course_name}</span>
</div>
))}
</CardContent>
</Card>
</div>
);
}