Files
encoach_frontend_new_v3/src/pages/admin/AdminReports.tsx
Yamen Ahmad 88a385ed07 feat: initial frontend codebase — EnCoach v3
React 18 + TypeScript + Vite 5 + Tailwind CSS + shadcn/ui frontend:
- Multi-role auth: student, teacher, admin, corporate, agent, developer
- Student portal: dashboard, courses, adaptive learning, exams, placement
- Teacher portal: courses, assignments, grading, attendance, AI workbench
- Admin portal: 60+ management pages (LMS, exams, users, entities, AI)
- AI-powered features: study coach, generation page, gap analysis, TTS
- Exam system: session, auto-save, results, grading queue
- Full IELTS workflow: reading/listening/writing/speaking modules

Made-with: Cursor
2026-04-11 15:47:32 +04:00

98 lines
6.3 KiB
TypeScript

import { useCallback } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useCourses, useStudents } from "@/hooks/queries";
import { Download } from "lucide-react";
import { BarChart, Bar, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from "recharts";
import AiReportNarrative from "@/components/ai/AiReportNarrative";
export default function AdminReports() {
const { data: coursesData, isLoading: lc } = useCourses();
const { data: studentsData, isLoading: ls } = useStudents({ size: 500 });
const courses = coursesData?.items ?? [];
const students = studentsData?.items ?? [];
const enrollmentByCourse = courses.map(c => ({
name: c.title.length > 12 ? c.title.slice(0, 12) + "…" : c.title,
enrolled: c.enrolled ?? 0,
capacity: c.max_capacity ?? 0,
}));
const capacityByCourse = courses.filter(c => c.status === "active").map(c => {
const pct = c.max_capacity ? Math.round((c.enrolled / c.max_capacity) * 100) : 0;
return { name: c.title.length > 12 ? c.title.slice(0, 12) + "…" : c.title, fillRate: pct };
});
const statusPie = [
{ name: "Active", value: students.filter((s) => s.status === "active").length, color: "hsl(142, 71%, 45%)" },
{ name: "Inactive", value: students.filter((s) => s.status !== "active").length, color: "hsl(38, 92%, 50%)" },
].filter(d => d.value > 0);
const enrollmentReportData = { series: enrollmentByCourse, studentCount: students.length, courseCount: courses.length };
const performanceReportData = { byCourse: capacityByCourse };
const riskReportData = { distribution: statusPie, studentCount: students.length };
const attendanceReportData = { note: "No attendance data recorded yet." };
const exportCsv = useCallback(() => {
const rows = [
["Type", "Label", "Enrolled", "Capacity"],
...enrollmentByCourse.map(r => ["Enrollment", r.name, String(r.enrolled), String(r.capacity)]),
...capacityByCourse.map(r => ["Fill Rate", r.name, String(r.fillRate) + "%", ""]),
...statusPie.map(r => ["Status", r.name, String(r.value), ""]),
];
const csv = rows.map(r => r.join(",")).join("\n");
const blob = new Blob([csv], { type: "text/csv" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "encoach-report.csv";
a.click();
URL.revokeObjectURL(url);
}, [enrollmentByCourse, capacityByCourse, statusPie]);
if (lc || ls) 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>;
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div><h1 className="text-2xl font-bold">Reports</h1><p className="text-muted-foreground">Analytics and reporting dashboard.</p></div>
<Button variant="outline" onClick={exportCsv}><Download className="mr-2 h-4 w-4" />Export CSV</Button>
</div>
<Tabs defaultValue="enrollment">
<TabsList><TabsTrigger value="enrollment">Enrollment</TabsTrigger><TabsTrigger value="performance">Performance</TabsTrigger><TabsTrigger value="risk">Student Risk</TabsTrigger><TabsTrigger value="attendance">Attendance</TabsTrigger></TabsList>
<TabsContent value="enrollment" className="mt-4 space-y-4">
<AiReportNarrative report_type="enrollment" data={enrollmentReportData} />
<Card><CardHeader><CardTitle className="text-lg">Enrollment by Course</CardTitle></CardHeader><CardContent>
{enrollmentByCourse.length > 0 ? (
<ResponsiveContainer width="100%" height={300}><BarChart data={enrollmentByCourse}><CartesianGrid strokeDasharray="3 3" className="stroke-border" /><XAxis dataKey="name" tick={{fontSize:11}} /><YAxis /><Tooltip /><Bar dataKey="enrolled" name="Enrolled" fill="hsl(192, 82%, 32%)" radius={[4,4,0,0]} /><Bar dataKey="capacity" name="Capacity" fill="hsl(192, 82%, 32%, 0.3)" radius={[4,4,0,0]} /></BarChart></ResponsiveContainer>
) : <p className="text-center text-muted-foreground py-8">No courses available.</p>}
</CardContent></Card>
</TabsContent>
<TabsContent value="performance" className="mt-4 space-y-4">
<AiReportNarrative report_type="performance" data={performanceReportData} />
<Card><CardHeader><CardTitle className="text-lg">Course Fill Rate (%)</CardTitle></CardHeader><CardContent>
{capacityByCourse.length > 0 ? (
<ResponsiveContainer width="100%" height={300}><BarChart data={capacityByCourse}><CartesianGrid strokeDasharray="3 3" className="stroke-border" /><XAxis dataKey="name" tick={{fontSize:11}} /><YAxis domain={[0,100]} /><Tooltip /><Bar dataKey="fillRate" name="Fill %" fill="hsl(187, 72%, 55%)" radius={[4,4,0,0]} /></BarChart></ResponsiveContainer>
) : <p className="text-center text-muted-foreground py-8">No active courses.</p>}
</CardContent></Card>
</TabsContent>
<TabsContent value="risk" className="mt-4 space-y-4">
<AiReportNarrative report_type="risk" data={riskReportData} />
<Card><CardHeader><CardTitle className="text-lg">Student Status Distribution</CardTitle></CardHeader><CardContent className="flex justify-center">
{statusPie.length > 0 ? (
<ResponsiveContainer width="100%" height={300}><PieChart><Pie data={statusPie} dataKey="value" cx="50%" cy="50%" outerRadius={100} label>{statusPie.map((d,i) => <Cell key={i} fill={d.color} />)}</Pie><Tooltip /><Legend /></PieChart></ResponsiveContainer>
) : <p className="text-center text-muted-foreground py-8">No students available.</p>}
</CardContent></Card>
</TabsContent>
<TabsContent value="attendance" className="mt-4 space-y-4">
<AiReportNarrative report_type="attendance" data={attendanceReportData} />
<Card><CardHeader><CardTitle className="text-lg">Attendance</CardTitle></CardHeader><CardContent>
<p className="text-center text-muted-foreground py-8">No attendance data recorded yet. Attendance tracking will appear here once sessions are recorded.</p>
</CardContent></Card>
</TabsContent>
</Tabs>
</div>
);
}