- Migration: student intake fields (health, academic, dismissal, agreement), student_guardians + student_curriculum_logins tables, extend authorized_pickups (alt_phone, notes, kind) with RLS mirroring existing policies - New /students/new full-page multi-section form (replaces the add dialog) - Students list links to the full page; regenerate Supabase types Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
import { createFileRoute, Link } from "@tanstack/react-router";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { supabase } from "@/integrations/supabase/client";
|
|
import { useAuth } from "@/hooks/use-auth";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Plus, ChevronRight } from "lucide-react";
|
|
|
|
export const Route = createFileRoute("/_authenticated/students")({
|
|
head: () => ({ meta: [{ title: "Students — School Portal" }] }),
|
|
component: StudentsPage,
|
|
});
|
|
|
|
function StudentsPage() {
|
|
const { roles } = useAuth();
|
|
const isAdmin = roles.includes("admin");
|
|
|
|
const { data: students } = useQuery({
|
|
queryKey: ["students"],
|
|
queryFn: async () => {
|
|
const { data, error } = await supabase
|
|
.from("students")
|
|
.select("id, first_name, last_name, dob, allergies, photo_release, class_id, classes(name)")
|
|
.order("last_name");
|
|
if (error) throw error;
|
|
return data ?? [];
|
|
},
|
|
});
|
|
|
|
return (
|
|
<div className="p-8 max-w-6xl">
|
|
<div className="flex justify-between items-center mb-6">
|
|
<div>
|
|
<h1 className="text-2xl font-semibold">Students</h1>
|
|
<p className="text-muted-foreground text-sm">{students?.length ?? 0} students visible to you</p>
|
|
</div>
|
|
{isAdmin && (
|
|
<Link to="/students/new">
|
|
<Button><Plus className="h-4 w-4 mr-1" /> Add student</Button>
|
|
</Link>
|
|
)}
|
|
</div>
|
|
|
|
<div className="bg-card border rounded-lg divide-y">
|
|
{(students ?? []).map((s) => (
|
|
<Link to="/students/$id" params={{ id: s.id }} key={s.id} className="flex items-center justify-between px-4 py-3 hover:bg-muted/50">
|
|
<div>
|
|
<div className="font-medium">{s.last_name}, {s.first_name}</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
{(s.classes as { name: string } | null)?.name ?? "No class"} · {s.allergies ? `Allergies: ${s.allergies}` : "No allergies"}
|
|
</div>
|
|
</div>
|
|
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
|
</Link>
|
|
))}
|
|
{students?.length === 0 && <div className="p-6 text-sm text-muted-foreground text-center">No students yet.</div>}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|