Files
encoach_backend_new_v2/frontend/src/pages/admin/AdminLmsDashboard.tsx
Yamen Ahmad f1c4953a63 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
2026-04-10 17:26:42 +04:00

203 lines
9.3 KiB
TypeScript

import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Users, BookOpen, GraduationCap, Layers, Ticket, DollarSign, Plus, BarChart3, FolderOpen } from "lucide-react";
import { Link } from "react-router-dom";
import { useCourses, useBatches, useStudents, useTeachers } from "@/hooks/queries";
import { useQuery } from "@tanstack/react-query";
import { api } from "@/lib/api-client";
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts";
import AiInsightsPanel from "@/components/ai/AiInsightsPanel";
import AiTipBanner from "@/components/ai/AiTipBanner";
interface DashboardStats {
total_students?: number;
total_teachers?: number;
total_courses?: number;
active_courses?: number;
total_batches?: number;
active_batches?: number;
total_entities?: number;
total_departments?: number;
total_classrooms?: number;
total_exams?: number;
total_assignments?: number;
total_tickets?: number;
open_tickets?: number;
total_subjects?: number;
total_resources?: number;
total_revenue?: number;
total_payments?: number;
total_exam_sessions?: number;
}
export default function AdminLmsDashboard() {
const { data: coursesData, isLoading: lc } = useCourses();
const { data: studentsData, isLoading: ls } = useStudents({ size: 500 });
const { data: teachersData, isLoading: lt } = useTeachers({ size: 500 });
const { data: batchesData, isLoading: lb } = useBatches();
const { data: dbStats } = useQuery<DashboardStats>({
queryKey: ["dashboard", "stats"],
queryFn: async () => {
const res = await api.get<{ data: DashboardStats }>("/stats");
return (res as { data: DashboardStats }).data ?? res;
},
staleTime: 30_000,
});
const courses = coursesData?.items ?? [];
const students = studentsData?.items ?? [];
const teachers = teachersData?.items ?? [];
const batches = batchesData?.items ?? [];
if (lc || ls || lt || lb) 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 revenue = dbStats?.total_revenue ?? 0;
const openTickets = dbStats?.open_tickets ?? 0;
const statCards = [
{ label: "Total Students", value: String(students.length), icon: Users, color: "text-primary" },
{ label: "Active Courses", value: `${courses.filter(c => c.status === "active").length} / ${courses.length}`, icon: BookOpen, color: "text-info" },
{ label: "Teachers", value: String(teachers.length), icon: GraduationCap, color: "text-success" },
{ label: "Active Batches", value: `${batches.filter(b => b.status === "active").length} / ${batches.length}`, icon: Layers, color: "text-warning" },
{ label: "Open Tickets", value: String(openTickets), icon: Ticket, color: "text-destructive" },
{ label: "Revenue", value: `$${revenue.toLocaleString()}`, icon: DollarSign, color: "text-success" },
];
const courseChartData = courses.map(c => {
const label = c.title || "";
const short = label.length > 14 ? label.substring(0, 14) + "…" : label;
return { course: short, capacity: c.max_capacity ?? 0, enrolled: c.enrolled ?? 0 };
});
const batchChartData = batches.map(b => {
const label = b.name || "";
const short = label.length > 12 ? label.substring(0, 12) + "…" : label;
return { batch: short, capacity: b.capacity ?? 0 };
});
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">Admin Dashboard</h1>
<p className="text-muted-foreground">Platform overview and key metrics.</p>
</div>
<div className="flex gap-2">
<Button variant="outline" asChild><Link to="/admin/students"><Plus className="mr-1 h-3 w-3" />Add Student</Link></Button>
<Button asChild><Link to="/admin/courses"><Plus className="mr-1 h-3 w-3" />New Course</Link></Button>
</div>
</div>
<AiTipBanner context="admin-lms-dashboard" variant="insight" />
<AiInsightsPanel />
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-4">
{statCards.map(s => (
<Card key={s.label}>
<CardContent className="pt-4 pb-4">
<s.icon className={`h-5 w-5 ${s.color} mb-1`} />
<p className="text-lg font-bold">{s.value}</p>
<p className="text-xs text-muted-foreground">{s.label}</p>
</CardContent>
</Card>
))}
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
{[
{ label: "Departments", value: dbStats?.total_departments ?? 0, icon: FolderOpen },
{ label: "Classrooms", value: dbStats?.total_classrooms ?? 0, icon: BarChart3 },
{ label: "Subjects", value: dbStats?.total_subjects ?? 0, icon: BookOpen },
{ label: "Resources", value: dbStats?.total_resources ?? 0, icon: Layers },
].map(s => (
<Card key={s.label}>
<CardContent className="pt-3 pb-3 flex items-center gap-3">
<s.icon className="h-4 w-4 text-muted-foreground" />
<div>
<p className="text-sm font-semibold">{s.value}</p>
<p className="text-xs text-muted-foreground">{s.label}</p>
</div>
</CardContent>
</Card>
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader><CardTitle className="text-lg">Courses Overview</CardTitle></CardHeader>
<CardContent>
{courseChartData.length > 0 ? (
<ResponsiveContainer width="100%" height={220}>
<BarChart data={courseChartData}>
<CartesianGrid strokeDasharray="3 3" className="stroke-border" />
<XAxis dataKey="course" className="fill-muted-foreground" tick={{ fontSize: 10 }} />
<YAxis className="fill-muted-foreground" tick={{ fontSize: 12 }} />
<Tooltip />
<Bar dataKey="capacity" name="Capacity" fill="hsl(var(--primary))" radius={[4, 4, 0, 0]} />
<Bar dataKey="enrolled" name="Enrolled" fill="hsl(var(--info))" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
) : (
<p className="text-sm text-muted-foreground py-8 text-center">No course data available.</p>
)}
</CardContent>
</Card>
<Card>
<CardHeader><CardTitle className="text-lg">Batch Capacity</CardTitle></CardHeader>
<CardContent>
{batchChartData.length > 0 ? (
<ResponsiveContainer width="100%" height={220}>
<BarChart data={batchChartData}>
<CartesianGrid strokeDasharray="3 3" className="stroke-border" />
<XAxis dataKey="batch" className="fill-muted-foreground" tick={{ fontSize: 10 }} />
<YAxis className="fill-muted-foreground" tick={{ fontSize: 12 }} />
<Tooltip />
<Bar dataKey="capacity" name="Capacity" fill="hsl(var(--info))" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
) : (
<p className="text-sm text-muted-foreground py-8 text-center">No batch data available.</p>
)}
</CardContent>
</Card>
</div>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-lg">Quick Summary</CardTitle>
</CardHeader>
<CardContent>
<Table>
<TableHeader><TableRow><TableHead>Module</TableHead><TableHead className="text-right">Count</TableHead><TableHead>Status</TableHead></TableRow></TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">Exams</TableCell>
<TableCell className="text-right">{dbStats?.total_exams ?? 0}</TableCell>
<TableCell className="text-muted-foreground">{dbStats?.total_exam_sessions ?? 0} sessions</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Assignments</TableCell>
<TableCell className="text-right">{dbStats?.total_assignments ?? 0}</TableCell>
<TableCell className="text-muted-foreground">across courses</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Support Tickets</TableCell>
<TableCell className="text-right">{dbStats?.total_tickets ?? 0}</TableCell>
<TableCell className="text-muted-foreground">{openTickets} open</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Payments</TableCell>
<TableCell className="text-right">{dbStats?.total_payments ?? 0}</TableCell>
<TableCell className="text-muted-foreground">${revenue.toLocaleString()} total</TableCell>
</TableRow>
</TableBody>
</Table>
</CardContent>
</Card>
</div>
);
}