From fe99f24dd325430a17a3a134581cec37a23b237c Mon Sep 17 00:00:00 2001 From: benjaminjohnson2204 Date: Wed, 16 Apr 2025 18:12:42 -0700 Subject: [PATCH] Several bugfixes --- backend/src/controllers/student.ts | 17 ++++- backend/src/util/student.ts | 6 +- frontend/src/components/AttendanceTable.tsx | 14 +++-- .../StudentForm/EnrollmentsEdit.tsx | 8 ++- frontend/src/components/StudentProfile.tsx | 63 +++++++++++++------ .../StudentsTable/useColumnSchema.tsx | 12 ++-- frontend/src/pages/attendance.tsx | 29 +++++---- 7 files changed, 99 insertions(+), 50 deletions(-) diff --git a/backend/src/controllers/student.ts b/backend/src/controllers/student.ts index 3d18a11..dcbb6d0 100644 --- a/backend/src/controllers/student.ts +++ b/backend/src/controllers/student.ts @@ -7,11 +7,13 @@ import { RequestHandler } from "express"; import { validationResult } from "express-validator"; import mongoose, { HydratedDocument } from "mongoose"; +import { ValidationError } from "../errors/validation"; import EnrollmentModel from "../models/enrollment"; import { Image } from "../models/image"; import ProgramModel from "../models/program"; import ProgressNoteModel from "../models/progressNote"; import StudentModel from "../models/student"; +import UserModel from "../models/user"; import { Enrollment } from "../types/enrollment"; import { createEnrollment, editEnrollment } from "../util/enrollment"; import validationErrorParser from "../util/validationErrorParser"; @@ -25,14 +27,12 @@ export const createStudent: RequestHandler = async (req, res, next) => { validationErrorParser(errors); - const newStudentId = new mongoose.Types.ObjectId(); - const { enrollments, ...studentData } = req.body as StudentRequest; // create enrollments for the student const createdEnrollments = await Promise.all( enrollments.map(async (program: Enrollment) => { - return await EnrollmentModel.create({ ...program, studentId: newStudentId }); + return await EnrollmentModel.create({ ...program, studentId: studentData._id }); }), ); @@ -51,6 +51,17 @@ export const createStudent: RequestHandler = async (req, res, next) => { export const editStudent: RequestHandler = async (req, res, next) => { try { + const { uid } = req.body; + + const user = await UserModel.findById(uid); + if (!user) { + throw ValidationError.USER_NOT_FOUND; + } + + if (user.accountType !== "admin") { + throw ValidationError.UNAUTHORIZED_USER; + } + const errors = validationResult(req); validationErrorParser(errors); diff --git a/backend/src/util/student.ts b/backend/src/util/student.ts index 7b4e74b..04d38a3 100644 --- a/backend/src/util/student.ts +++ b/backend/src/util/student.ts @@ -17,7 +17,11 @@ export const programValidatorUtil = async (enrollments: Enrollment[]) => { enrollments.forEach((enrollment: Enrollment) => { requiredFields.forEach((field) => { - if (!enrollment[field as keyof Enrollment]) + // Can't just test for truthiness because 0 is a valid value for hoursLeft + if ( + enrollment[field as keyof Enrollment] === undefined || + enrollment[field as keyof Enrollment] === null + ) throw new Error(`Field ${field} is required on enrollment`); }); }); diff --git a/frontend/src/components/AttendanceTable.tsx b/frontend/src/components/AttendanceTable.tsx index fced3c1..0ecc1a1 100644 --- a/frontend/src/components/AttendanceTable.tsx +++ b/frontend/src/components/AttendanceTable.tsx @@ -1,6 +1,6 @@ import { Dot } from "lucide-react"; import Image from "next/image"; -import React, { Dispatch, SetStateAction, useRef, useState } from "react"; +import React, { Dispatch, SetStateAction, useMemo, useRef, useState } from "react"; import { FieldValues, SubmitHandler, useForm } from "react-hook-form"; import { cn } from "../lib/utils"; @@ -33,6 +33,12 @@ export function AttendanceTable({ setRemainingAbsenceSessions, firebaseToken, }: TableProps) { + // Exclude students that are not in the session students map for some reason + const existingStudents = useMemo( + () => session.students.filter(({ studentId }) => students[studentId]), + [session.students, students], + ); + const { register, setValue, @@ -71,7 +77,7 @@ export function AttendanceTable({ const onSubmit: SubmitHandler = (data) => { if (clickedRef.current) return; clickedRef.current = true; - const studentInfo = session.students.map((student) => { + const studentInfo = existingStudents.map((student) => { return { studentId: student.studentId, attended: data["attended_" + student.studentId] === "true" ? true : false, @@ -143,12 +149,12 @@ export function AttendanceTable({
- {session.students.map((student, index) => { + {existingStudents.map((student, index) => { return (
{/*no need to set all students*/} - + + + +
{currentView === "Edit" ? "View" : "Edit"} Mode
+ + ) : ( + + )}
{currentView === "View" ? ( diff --git a/frontend/src/components/StudentsTable/useColumnSchema.tsx b/frontend/src/components/StudentsTable/useColumnSchema.tsx index 745175c..0ebaf8d 100644 --- a/frontend/src/components/StudentsTable/useColumnSchema.tsx +++ b/frontend/src/components/StudentsTable/useColumnSchema.tsx @@ -4,7 +4,7 @@ import Link from "next/link"; import { useMemo } from "react"; import { Popover, PopoverContent, PopoverTrigger } from "../../components/ui/popover"; -import { Contact, Enrollment, ProgramLink } from "../StudentForm/types"; +import { Contact, Enrollment } from "../StudentForm/types"; import { Columns, ProgramMap, StudentMap } from "./types"; @@ -38,13 +38,13 @@ const PopoverInfoRow = ({ label, value }: { label: string; value: string }) => ( ); -const ProgramPopover = ({ link, program }: { link: ProgramLink; program: Program }) => { +const ProgramPopover = ({ link, program }: { link: Enrollment; program: Program }) => { if (!program) return null; const rowInfo = [ ["Type", program.type], ["Schedule", program.daysOfWeek.join(", ")], - ["Start Date", new Date(/*program.startDate*/).toLocaleDateString("en-US")], - ["Renewal Date", new Date(/*program.endDate*/).toLocaleDateString("en-US")], + ["Start Date", new Date(link.startDate).toLocaleDateString("en-US")], + ["Renewal Date", new Date(link.renewalDate).toLocaleDateString("en-US")], ["Hours Left", link.hoursLeft.toString()], ]; @@ -127,7 +127,7 @@ export function useColumnSchema({ id: isTablet ? "Curr. P1" : "Curr. Program 1", header: isTablet ? "Curr. P1" : "Curr. Program 1", cell: (info) => { - const enrollments = info.getValue() as unknown as ProgramLink[]; + const enrollments = info.getValue() as unknown as Enrollment[]; const link = enrollments.filter((enr) => enr.status === "Joined")[0]; if (!link) return null; const program = allPrograms[link.programId]; @@ -140,7 +140,7 @@ export function useColumnSchema({ id: isTablet ? "Curr. P2" : "Curr. Program 2", header: isTablet ? "Curr. P2" : "Curr. Program 2", cell: (info) => { - const enrollments = info.getValue() as unknown as ProgramLink[]; + const enrollments = info.getValue() as unknown as Enrollment[]; const link = enrollments.filter((enr) => enr.status === "Joined")[1]; if (!link) return null; const program = allPrograms[link.programId]; diff --git a/frontend/src/pages/attendance.tsx b/frontend/src/pages/attendance.tsx index 6befbd4..03bfce1 100644 --- a/frontend/src/pages/attendance.tsx +++ b/frontend/src/pages/attendance.tsx @@ -156,19 +156,22 @@ export default function AttendanceDashboard() { className="flex overflow-x-hidden overflow-y-hidden hover:overflow-x-auto" ref={scrollRef} > - {allAbsenceSessions?.map((absenceSession, i) => { - const program = allPrograms[absenceSession.programId]; - const student = allStudents[absenceSession.studentId]; - return ( - - ); - })} + {// Exclude students that are not in the session students map for some reason + allAbsenceSessions + ?.filter((absenceSession) => allStudents[absenceSession.studentId]) + .map((absenceSession, i) => { + const program = allPrograms[absenceSession.programId]; + const student = allStudents[absenceSession.studentId]; + return ( + + ); + })} {remainingAbsenceSessions === 0 && (