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:
89
src/pages/admin/AdminBatchDetail.tsx
Normal file
89
src/pages/admin/AdminBatchDetail.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user