From 9ce105f7edc21985b6631d1ad32dc1966900b0d2 Mon Sep 17 00:00:00 2001 From: adescoteaux1 Date: Fri, 5 Dec 2025 06:12:57 -0500 Subject: [PATCH 1/3] init --- frontend/src/app/sessions/[id]/page.tsx | 74 ++++++++++++++++++- .../src/components/games/StudentSelector.tsx | 16 ++-- .../src/components/rate/StudentSelector.tsx | 32 ++++++-- frontend/src/contexts/sessionContext.tsx | 22 ++++++ 4 files changed, 128 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/sessions/[id]/page.tsx b/frontend/src/app/sessions/[id]/page.tsx index 3a2594c1..603c4264 100644 --- a/frontend/src/app/sessions/[id]/page.tsx +++ b/frontend/src/app/sessions/[id]/page.tsx @@ -3,6 +3,8 @@ import { Avatar } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { ConfirmDialog } from "@/components/ui/confirm-dialog"; +import type { StudentTuple} from '@/contexts/sessionContext'; +import { StudentAttendance, useSessionContext } from '@/contexts/sessionContext'; import { formatRecurrence, useSession, useSessions } from "@/hooks/useSessions"; import { useSessionStudents, @@ -32,7 +34,7 @@ import { } from "lucide-react"; import Link from "next/link"; import { useSearchParams } from "next/navigation"; -import { use, useState } from "react"; +import { use, useEffect, useState } from "react"; interface PageProps { params: Promise<{ @@ -80,6 +82,43 @@ export default function SessionPage({ params }: PageProps) { const [deleteType, setDeleteType] = useState<"single" | "recurring" | null>( null ); + const { attendance, setAttendance, setStudents, students: contextStudents, attendance: contextAttendance } = useSessionContext(); + + useEffect(() => { + if (!studentsLoading && sessionStudents.length > 0) { + // 1. Calculate the NEW student tuples based on current session students + const studentTuples: StudentTuple[] = sessionStudents + .filter(s => s.session_student_id) + .map(s => ({ + studentId: s.id, + sessionStudentId: s.session_student_id!, + })); + + // 2. Calculate the NEW attendance map + const initialAttendance: StudentAttendance = sessionStudents.reduce((acc, student) => { + if (student.session_student_id) { + acc[student.session_student_id] = student.present ?? true; + } + return acc; + }, {} as StudentAttendance); + + // --- CRITICAL CHECK: Prevent infinite loop by comparing objects/arrays --- + + // Check if the student list length has changed (simplest check) + const studentsChanged = studentTuples.length !== contextStudents.length; + + // Check if the attendance map contents have changed (more robust) + const attendanceJson = JSON.stringify(initialAttendance); + const contextAttendanceJson = JSON.stringify(contextAttendance); + const attendanceChanged = attendanceJson !== contextAttendanceJson; + + if (studentsChanged || attendanceChanged) { + // Only update if there's a difference + setStudents(studentTuples); + setAttendance(initialAttendance); + } + } +}, [sessionStudents, studentsLoading, setAttendance, setStudents, contextStudents, contextAttendance]); // Add context state dependencies if (sessionLoading || studentsLoading) { return ( @@ -140,12 +179,25 @@ export default function SessionPage({ params }: PageProps) { }); }; - const handleToggleAttendance = (studentId: string, present: boolean) => { + const handleToggleAttendance = ( + studentId: string, + sessionStudentId: number | undefined, + present: boolean + ) => { + // 1. Update the backend/API updateSessionStudent({ session_id: id, student_id: studentId, present, }); + + // 2. Update the Session Context for immediate UI filtering in other views + if (sessionStudentId) { + setAttendance({ + ...(attendance ?? {}), + [sessionStudentId]: present, + }); + } }; const handleEditClick = () => { @@ -522,14 +574,28 @@ export default function SessionPage({ params }: PageProps) { {mode === "attendance" && (