Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V2 Student Form #108

Merged
merged 19 commits into from
Dec 18, 2024
Merged
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: fix enrollment frontend and backend inconsistency
  • Loading branch information
aaronchan32 committed Nov 28, 2024
commit d917f2c2cda3634d7edd8840e6bb004c8174b916
61 changes: 37 additions & 24 deletions backend/src/controllers/student.ts
Original file line number Diff line number Diff line change
@@ -22,16 +22,25 @@ export const createStudent: RequestHandler = async (req, res, next) => {

validationErrorParser(errors);

const newStudentId = new mongoose.Types.ObjectId();

const { enrollments, ...studentData } = req.body as StudentRequest;
const newStudent = await StudentModel.create(studentData);

// create enrollments for the student
await Promise.all(
const createdEnrollments = await Promise.all(
enrollments.map(async (program: Enrollment) => {
await createEnrollment({ ...program, studentId: newStudent._id });
return await EnrollmentModel.create({ ...program, studentId: newStudentId });
}),
);

res.status(201).json(newStudent);
const newStudent = await StudentModel.create({
...studentData,
enrollments: createdEnrollments.map((enrollment) => enrollment._id),
});

const populatedStudent = await StudentModel.findById(newStudent._id).populate("enrollments");

res.status(201).json(populatedStudent);
} catch (error) {
next(error);
}
@@ -49,42 +58,46 @@ export const editStudent: RequestHandler = async (req, res, next) => {
if (studentId !== studentData._id.toString()) {
return res.status(400).json({ message: "Invalid student ID" });
}
const updatedStudent = await StudentModel.findByIdAndUpdate(studentId, studentData, {
new: true,
});
if (!updatedStudent) {
return res.status(404).json({ message: "Student not found" });
}

// update enrollments for the student
await Promise.all(
const updatedEnrollments = await Promise.all(
enrollments.map(async (enrollment: Enrollment) => {
const enrollmentExists = await EnrollmentModel.findById(enrollment._id);
const enrollmentBody = { ...enrollment, studentId: new mongoose.Types.ObjectId(studentId) };
if (!enrollmentExists) await createEnrollment(enrollmentBody);
else await editEnrollment(enrollmentBody);
if (!enrollmentExists) {
return await createEnrollment(enrollmentBody);
} else {
return await editEnrollment(enrollmentBody);
}
}),
);

res.status(200).json({ ...updatedStudent.toObject(), enrollments });
const updatedStudent = await StudentModel.findByIdAndUpdate(
studentId,
{ ...studentData, enrollments: updatedEnrollments.map((enrollment) => enrollment?._id) },
{
new: true,
},
);
if (!updatedStudent) {
return res.status(404).json({ message: "Student not found" });
}

const populatedStudent = await StudentModel.findById(updatedStudent._id).populate(
"enrollments",
);

res.status(200).json(populatedStudent);
} catch (error) {
next(error);
}
};

export const getAllStudents: RequestHandler = async (_, res, next) => {
try {
const students = await StudentModel.find();

// gather all enrollments for each student and put them in student.programs
const hydratedStudents = await Promise.all(
students.map(async (student) => {
const enrollments = await EnrollmentModel.find({ studentId: student._id });
return { ...student.toObject(), enrollments };
}),
);
const students = await StudentModel.find().populate("enrollments");

res.status(200).json(hydratedStudents);
res.status(200).json(students);
} catch (error) {
next(error);
}
7 changes: 7 additions & 0 deletions backend/src/models/student.ts
Original file line number Diff line number Diff line change
@@ -25,6 +25,13 @@ const studentSchema = new Schema({
phoneNumber: { type: String, required: true },
},

enrollments: {
type: [Schema.Types.ObjectId],
ref: "Enrollment",
default: [],
required: false,
},

//Address of student
location: { type: String, required: true },

4 changes: 2 additions & 2 deletions backend/src/util/enrollment.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import { Enrollment } from "../types/enrollment";

export const createEnrollment = async (req: Enrollment) => {
try {
await EnrollmentModel.create(req);
return await EnrollmentModel.create(req);
} catch (e) {
console.log(e);
throw e;
@@ -13,7 +13,7 @@ export const createEnrollment = async (req: Enrollment) => {
export const editEnrollment = async (req: Enrollment) => {
try {
console.log(req);
await EnrollmentModel.findByIdAndUpdate(req._id, req);
return await EnrollmentModel.findByIdAndUpdate(req._id, req);
} catch (e) {
console.log(e);
throw e;
7 changes: 4 additions & 3 deletions frontend/src/components/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ type DropdownProps<T extends FieldValues> = BaseProps<T> & {

export function Dropdown<T extends FieldValues>({
setDropdownValue,
label,
placeholder,
name,
options, // list of options - should be memoized
onChange = () => void 0,
@@ -64,8 +64,9 @@ export function Dropdown<T extends FieldValues>({
className,
)}
>
{label && <span className="text-neutral-400">{label + ": "}</span>}
<span className="text-neutral-800">{selectedOption ?? ""}</span>
<span className={`text-neutral-${selectedOption ? 800 : 400}`}>
{selectedOption ? selectedOption : placeholder}
</span>
<Image
src="/ic_round-arrow-drop-up.svg"
width={40}
25 changes: 14 additions & 11 deletions frontend/src/components/StudentForm/EnrollmentsEdit.tsx
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ import { Textfield } from "../Textfield";
import { convertDateToString } from "./StudentBackground";
import { StatusOptions, StudentFormData } from "./types";

import { timeToAmPm } from "@/lib/sessionTimeParsing";
import { amPmToTime, timeToAmPm } from "@/lib/sessionTimeParsing";

type EnrollmentsEditProps = {
classname?: string;
@@ -32,7 +32,10 @@ export const emptyEnrollment = {
dateUpdated: new Date(),
hoursLeft: 8,
schedule: [] as string[],
sessionTime: [] as string[],
sessionTime: {
start_time: "",
end_time: "",
},
startDate: "",
renewalDate: "",
authNumber: "",
@@ -114,8 +117,12 @@ const EnrollmentFormItem = ({
const { allPrograms: programsMap } = useContext(ProgramsContext);
const { register, setValue } = useFormContext<StudentFormData>();

const initialTime = `${timeToAmPm(item.sessionTime[0])} - ${timeToAmPm(item.sessionTime[1])}`;
const [selectedSession, setSelectedSession] = useState<string>(initialTime);
const initialTime =
item.sessionTime.start_time === ""
? ""
: `${timeToAmPm(item.sessionTime.start_time)} - ${timeToAmPm(item.sessionTime.end_time)}`;
// const initialTime = "0:00 AM - 0:00 AM";
const [selectedSession, setSelectedSession] = useState(item.sessionTime);
const [selectedStatus, setSelectedStatus] = useState<string>(item.status);
const [selectedProgram, setSelectedProgram] = useState<string>("");

@@ -129,8 +136,7 @@ const EnrollmentFormItem = ({

// these 3 useEffects keep our custom dropdown and react-hook-form in sync
useEffect(() => {
item.sessionTime = [selectedSession];
setValue(`${fieldName}.${index}.sessionTime`, [selectedSession]);
setValue(`${fieldName}.${index}.sessionTime`, selectedSession);
}, [selectedSession]);

useEffect(() => {
@@ -141,11 +147,10 @@ const EnrollmentFormItem = ({
item.programId = progId;
setValue(`${fieldName}.${index}.programId`, progId);
}
setSelectedSession(sessionOptions[0] || "");
setSelectedSession({ start_time: "", end_time: "" });
}, [selectedProgram]);

useEffect(() => {
item.sessionTime = [selectedStatus];
setValue(`${fieldName}.${index}.status`, selectedStatus);
}, [selectedStatus]);

@@ -157,7 +162,6 @@ const EnrollmentFormItem = ({
<Dropdown
name="name"
placeholder="Select Program"
label="Select Program"
className={`h-[50px] w-full rounded-md`}
options={allPrograms}
initialValue={programsMap[item.programId]?.abbreviation}
@@ -171,7 +175,6 @@ const EnrollmentFormItem = ({
<Dropdown
name="status"
placeholder="Select Status"
label="Select Status"
className={`h-[50px] w-full rounded-md`}
initialValue={item.status}
options={useMemo(() => Object.values(StatusOptions), [StatusOptions])}
@@ -204,7 +207,7 @@ const EnrollmentFormItem = ({
options={sessionOptions}
initialValue={initialTime}
onChange={(value): void => {
setSelectedSession(value);
setSelectedSession(amPmToTime(value));
}}
/>
</div>
5 changes: 4 additions & 1 deletion frontend/src/components/StudentForm/types.ts
Original file line number Diff line number Diff line change
@@ -65,7 +65,10 @@ export type Enrollment = {
dateUpdated: Date;
hoursLeft: number;
schedule: string[];
sessionTime: string[];
sessionTime: {
start_time: string;
end_time: string;
};
startDate: Date;
renewalDate: Date;
authNumber: string;
12 changes: 8 additions & 4 deletions frontend/src/components/StudentFormButton.tsx
Original file line number Diff line number Diff line change
@@ -17,7 +17,6 @@ import { Dialog, DialogClose, DialogContent, DialogTrigger } from "./ui/dialog";

import { ProgramsContext } from "@/contexts/program";
import { UserContext } from "@/contexts/user";
import { amPmToTime } from "@/lib/sessionTimeParsing";

type BaseProps = {
classname?: string;
@@ -92,19 +91,24 @@ export default function StudentFormButton({
dateUpdated: new Date(enrollment.dateUpdated),
startDate: new Date(enrollment.startDate),
renewalDate: new Date(enrollment.renewalDate),
sessionTime: amPmToTime(enrollment.sessionTime)[0],
sessionTime: {
start_time: enrollment.sessionTime.start_time,
end_time: enrollment.sessionTime.end_time,
},
};
})
.concat(
formData?.varyingEnrollments.map((enrollment) => {
console.log("enrollment varying: ", enrollment);

return {
...enrollment,
dateUpdated: new Date(enrollment.dateUpdated),
startDate: new Date(enrollment.startDate),
renewalDate: new Date(enrollment.renewalDate),
sessionTime: amPmToTime(enrollment.sessionTime)[0],
sessionTime: {
start_time: enrollment.sessionTime.start_time,
end_time: enrollment.sessionTime.end_time,
},
};
}),
),
2 changes: 0 additions & 2 deletions frontend/src/components/StudentsTable/FilterFns.tsx
Original file line number Diff line number Diff line change
@@ -64,7 +64,6 @@ export function ProgramFilter({

return (
<Dropdown
label="Program"
name="program"
placeholder="Program"
className={cn(`rounded-md`, className)}
@@ -94,7 +93,6 @@ export function StatusFilter({ column }: { column: Column<StudentTableRow> }) {

return (
<Dropdown
label="Status"
name="status"
placeholder="Status"
className={`rounded-md`}
35 changes: 11 additions & 24 deletions frontend/src/lib/sessionTimeParsing.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,16 @@
export const timeToAmPm = (time: string) => {
import { format, parse } from "date-fns";
export const timeToAmPm = (time: string): string => {
if (!time) return "";
const [hour, minute] = time.split(":");
const hourNum = parseInt(hour);
const minuteNum = parseInt(minute);
if (hourNum > 12) {
return `${hourNum - 12}:${minuteNum.toString().padStart(2, "0")} PM`;
} else {
return `${hourNum}:${minuteNum.toString().padStart(2, "0")} AM`;
}
const parsedTime = parse(time, "HH:mm", new Date());
console.log({ time });
return format(parsedTime, "hh:mm a");
};

// provide the reverse function of timeToAmPm - convert XX:YY AM - AA:BB PM to [XX:YY, AA:BB]
export const amPmToTime = (times: string[]) => {
if (!times.every((time) => time.includes("AM") || time.includes("PM"))) return [];
return times.map((time) => {
const [t1, t2] = time.split("-");
const [hour1, minute1] = t1.split(":");
const [hour2, minute2] = t2.split(":");
const hour1Num = parseInt(hour1);
const minute1Num = parseInt(minute1);
const hour2Num = parseInt(hour2);
const minute2Num = parseInt(minute2);
return [
`${hour1Num > 12 ? hour1Num + 12 : hour1Num}:${minute1Num.toString().padStart(2, "0")}`,
`${hour2Num > 12 ? hour2Num + 12 : hour2Num}:${minute2Num.toString().padStart(2, "0")}`,
];
});
export const amPmToTime = (time: string): { start_time: string; end_time: string } => {
if (!time.includes("-")) return { start_time: "", end_time: "" };
const [start, end] = time.split("-");
const startTime = parse(start.trim(), "hh:mm a", new Date());
const endTime = parse(end.trim(), "hh:mm a", new Date());
return { start_time: format(startTime, "HH:mm"), end_time: format(endTime, "HH:mm") };
};