Files
encoach_backend_new_v3/frontend/src/pages/student/StudentAttendance.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

76 lines
3.4 KiB
TypeScript

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 { useAttendance } from "@/hooks/queries";
import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from "recharts";
import AiTipBanner from "@/components/ai/AiTipBanner";
const badgeVariant = (s: string) => s === "present" ? "default" as const : s === "absent" ? "destructive" as const : "secondary" as const;
export default function StudentAttendance() {
const { data: attendanceRecords = [], isLoading } = useAttendance();
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 counts = { present: 0, absent: 0, late: 0, excused: 0 };
attendanceRecords.forEach(a => counts[a.status]++);
const total = attendanceRecords.length;
const rate = total > 0 ? ((counts.present + counts.late) / total * 100).toFixed(1) : "0";
const pieData = [
{ name: "Present", value: counts.present, color: "hsl(142, 71%, 45%)" },
{ name: "Late", value: counts.late, color: "hsl(38, 92%, 50%)" },
{ name: "Absent", value: counts.absent, color: "hsl(0, 72%, 51%)" },
{ name: "Excused", value: counts.excused, color: "hsl(199, 89%, 48%)" },
].filter(d => d.value > 0);
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold">Attendance</h1>
<p className="text-muted-foreground">Your attendance record across all courses.</p>
</div>
<AiTipBanner context="student-attendance" variant="insight" />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<Card className="lg:col-span-1">
<CardHeader><CardTitle className="text-lg">Summary</CardTitle></CardHeader>
<CardContent>
<div className="text-center mb-4">
<p className="text-4xl font-bold text-primary">{rate}%</p>
<p className="text-sm text-muted-foreground">Attendance Rate</p>
</div>
<ResponsiveContainer width="100%" height={200}>
<PieChart>
<Pie data={pieData} dataKey="value" cx="50%" cy="50%" innerRadius={50} outerRadius={80}>
{pieData.map((d, i) => <Cell key={i} fill={d.color} />)}
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
</CardContent>
</Card>
<Card className="lg:col-span-2">
<CardHeader><CardTitle className="text-lg">Attendance Records</CardTitle></CardHeader>
<CardContent>
<Table>
<TableHeader><TableRow><TableHead>Date</TableHead><TableHead>Course</TableHead><TableHead>Status</TableHead></TableRow></TableHeader>
<TableBody>
{attendanceRecords.map(a => (
<TableRow key={a.id}>
<TableCell>{a.date}</TableCell>
<TableCell>{a.courseName}</TableCell>
<TableCell><Badge variant={badgeVariant(a.status)} className="capitalize">{a.status}</Badge></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
</div>
);
}