Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-06-30 20:41:27 +00:00
co-authored by renee-png
parent d7e7de54d3
commit a2a4e6c811
5 changed files with 359 additions and 17 deletions
+80
View File
@@ -0,0 +1,80 @@
import { createFileRoute } from "@tanstack/react-router";
import { useAuth, highestRole } from "@/hooks/use-auth";
import { useQuery } from "@tanstack/react-query";
import { supabase } from "@/integrations/supabase/client";
import { Users, ClipboardCheck, Receipt, CalendarDays } from "lucide-react";
export const Route = createFileRoute("/_authenticated/dashboard")({
head: () => ({ meta: [{ title: "Dashboard — School Portal" }] }),
component: Dashboard,
});
function Dashboard() {
const { user, roles } = useAuth();
const role = highestRole(roles);
const { data: studentCount } = useQuery({
queryKey: ["students-count"],
queryFn: async () => {
const { count } = await supabase.from("students").select("*", { count: "exact", head: true });
return count ?? 0;
},
});
const { data: todayAttendance } = useQuery({
queryKey: ["attendance-today"],
queryFn: async () => {
const today = new Date().toISOString().slice(0, 10);
const { count } = await supabase.from("attendance").select("*", { count: "exact", head: true }).eq("date", today);
return count ?? 0;
},
});
const { data: upcoming } = useQuery({
queryKey: ["upcoming-events"],
queryFn: async () => {
const today = new Date().toISOString().slice(0, 10);
const { data } = await supabase.from("calendar_events").select("*").gte("date", today).order("date").limit(5);
return data ?? [];
},
});
return (
<div className="p-8 max-w-6xl">
<h1 className="text-2xl font-semibold">Welcome back</h1>
<p className="text-muted-foreground">Signed in as <span className="capitalize">{role}</span></p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6">
<Stat icon={Users} label="Students" value={studentCount ?? "—"} />
<Stat icon={ClipboardCheck} label="Attendance recorded today" value={todayAttendance ?? "—"} />
<Stat icon={CalendarDays} label="Upcoming events" value={upcoming?.length ?? "—"} />
</div>
<div className="mt-8 bg-card border rounded-lg p-6">
<h2 className="font-semibold mb-3 flex items-center gap-2"><CalendarDays className="h-4 w-4" /> Upcoming on the calendar</h2>
{upcoming && upcoming.length > 0 ? (
<ul className="space-y-2 text-sm">
{upcoming.map((e) => (
<li key={e.id} className="flex justify-between border-b last:border-0 py-2">
<span>{e.title}</span>
<span className="text-muted-foreground">{new Date(e.date).toLocaleDateString()}</span>
</li>
))}
</ul>
) : (
<p className="text-sm text-muted-foreground">No upcoming events.</p>
)}
</div>
</div>
);
}
function Stat({ icon: Icon, label, value }: { icon: typeof Users; label: string; value: number | string }) {
return (
<div className="bg-card border rounded-lg p-6">
<Icon className="h-5 w-5 text-primary mb-2" />
<div className="text-3xl font-semibold">{value}</div>
<div className="text-sm text-muted-foreground">{label}</div>
</div>
);
}