From 4af7d71e1882049f07d8238fd0c8b9a812058e41 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 7 Aug 2026 04:15:36 +0000 Subject: [PATCH] Modified by www.SourceFiles.app --- src/routes/_authenticated/attendance.tsx | 98 ++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/routes/_authenticated/attendance.tsx diff --git a/src/routes/_authenticated/attendance.tsx b/src/routes/_authenticated/attendance.tsx new file mode 100644 index 0000000..1ca0a37 --- /dev/null +++ b/src/routes/_authenticated/attendance.tsx @@ -0,0 +1,98 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { supabase } from "@/integrations/supabase/client"; +import { useAuth } from "@/hooks/use-auth"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { useState } from "react"; +import { toast } from "sonner"; + +export const Route = createFileRoute("/_authenticated/attendance")({ + head: () => ({ meta: [{ title: "Attendance — School Portal" }] }), + component: AttendancePage, +}); + +function AttendancePage() { + const { user, roles, loading } = useAuth(); + const isAdmin = roles.includes("admin"); + const qc = useQueryClient(); + const [date, setDate] = useState(new Date().toISOString().slice(0, 10)); + const [classId, setClassId] = useState(""); + + const { data: classes } = useQuery({ + // isAdmin is in the key so the query refetches once roles resolve; gated on + // !loading so it never runs with a not-yet-known (false) admin status. + queryKey: ["my-classes", user?.id, isAdmin], + queryFn: async () => { + let q = supabase.from("classes").select("id, name, teacher_id"); + if (!isAdmin) q = q.eq("teacher_id", user!.id); + return (await q.order("name")).data ?? []; + }, + enabled: !!user && !loading, + }); + + const { data: students } = useQuery({ + queryKey: ["class-students", classId, date], + queryFn: async () => { + if (!classId) return []; + const { data } = await supabase.from("students").select("id, first_name, last_name, attendance(status, date, id)").eq("class_id", classId); + return data ?? []; + }, + enabled: !!classId, + }); + + const mark = useMutation({ + mutationFn: async ({ studentId, status }: { studentId: string; status: string }) => { + const { error } = await supabase.from("attendance").upsert( + { student_id: studentId, date, status: status as "present" | "absent" | "late" | "excused", recorded_by: user!.id }, + { onConflict: "student_id,date" } + ); + if (error) throw error; + }, + onSuccess: () => qc.invalidateQueries({ queryKey: ["class-students", classId, date] }), + onError: (e: Error) => toast.error(e.message), + }); + + const getStatus = (s: { attendance: { date: string; status: string }[] }) => + s.attendance.find((a) => a.date === date)?.status ?? ""; + + return ( +
+

Attendance

+

Mark today's attendance for your class.

+ +
+
+ +
+ setDate(e.target.value)} /> +
+ + {classId && ( +
+ {(students ?? []).map((s) => { + const status = getStatus(s as { attendance: { date: string; status: string }[] }); + return ( +
+
{s.last_name}, {s.first_name}
+
+ {["present", "absent", "late", "excused"].map((opt) => ( + + ))} +
+
+ ); + })} + {students?.length === 0 &&
No students in this class.
} +
+ )} + {!classId &&

Select a class to begin.

} +
+ ); +}