From e078081b6db25244399eabf3a65f8cf4dc8903dc Mon Sep 17 00:00:00 2001
From: Aaron Chan <42254254+aaronchan32@users.noreply.github.com>
Date: Mon, 20 May 2024 01:27:26 -0700
Subject: [PATCH] Squashed commit of the following:
commit 0b133b8a0f9465224f927564ff42c7ee95798d38
Author: Michael Sullivan <96799955+mraysu@users.noreply.github.com>
Date: Tue May 14 09:09:16 2024 -0700
Feature/mraysu/program form v2 (#100)
* Update Backend Program Schema
* V2 UI
* Disabled Editing Program Type
* Frontend-backend integration
* Lint fixes
---------
Co-authored-by: mraysu
Co-authored-by: Adhithya Ananthan <85322002+adhi0331@users.noreply.github.com>
commit e17b509601b89d1a34eaddf55d652b5213ea9d8d
Author: parth4apple <72187062+parth4apple@users.noreply.github.com>
Date: Tue May 14 09:01:15 2024 -0700
Student and Enrollment Schema modifications (#101)
* feat: initial schema
* feat: edit routes
* feat: test and fix routes
---
backend/src/controllers/program.ts | 5 -
backend/src/controllers/student.ts | 115 ++++-------
backend/src/models/enrollment.ts | 28 +++
backend/src/models/program.ts | 8 +-
backend/src/models/student.ts | 18 +-
backend/src/types/enrollment.ts | 13 ++
backend/src/types/programLink.ts | 8 -
backend/src/util/enrollment.ts | 21 ++
backend/src/util/student.ts | 67 +++----
backend/src/validators/program.ts | 49 +++--
backend/src/validators/student.ts | 59 +++++-
frontend/src/api/programs.ts | 2 +-
frontend/src/components/ProgramCard.tsx | 22 ++-
.../components/ProgramForm/ProgramArchive.tsx | 103 +++++++++-
.../components/ProgramForm/ProgramCancel.tsx | 23 ++-
.../components/ProgramForm/ProgramInfo.tsx | 86 +++------
frontend/src/components/ProgramForm/types.ts | 17 +-
frontend/src/components/ProgramFormButton.tsx | 179 +++---------------
.../StudentsTable/useColumnSchema.tsx | 4 +-
19 files changed, 424 insertions(+), 403 deletions(-)
create mode 100644 backend/src/models/enrollment.ts
create mode 100644 backend/src/types/enrollment.ts
delete mode 100644 backend/src/types/programLink.ts
create mode 100644 backend/src/util/enrollment.ts
diff --git a/backend/src/controllers/program.ts b/backend/src/controllers/program.ts
index 581f4db2..a2e93391 100644
--- a/backend/src/controllers/program.ts
+++ b/backend/src/controllers/program.ts
@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import { RequestHandler } from "express";
import { validationResult } from "express-validator";
-import { Schema } from "mongoose";
//import { error } from "firebase-functions/logger";
import ProgramModel from "../models/program";
@@ -13,11 +12,7 @@ export type Program = {
abbreviation: string;
type: string;
daysOfWeek: string[];
- startDate: Date;
- endDate: Date;
color: string; //colorValueHex;
- studentUIDs: Schema.Types.ObjectId[];
- renewalDate: Date;
hourlyPay: string;
sessions: [string[]];
};
diff --git a/backend/src/controllers/student.ts b/backend/src/controllers/student.ts
index a93056ae..ef5e965e 100644
--- a/backend/src/controllers/student.ts
+++ b/backend/src/controllers/student.ts
@@ -5,54 +5,16 @@
import { RequestHandler } from "express";
import { validationResult } from "express-validator";
+import mongoose, { HydratedDocument } from "mongoose";
+import EnrollmentModel from "../models/enrollment";
import StudentModel from "../models/student";
-import { programLink } from "../types/programLink";
-import { addStudentToPrograms, removeStudentFromPrograms } from "../util/student";
+import { Enrollment } from "../types/enrollment";
+import { createEnrollment, editEnrollment } from "../util/enrollment";
import validationErrorParser from "../util/validationErrorParser";
-export type contact = {
- lastName: string;
- firstName: string;
- email: string;
- phoneNumber: string;
-};
-
-export type typedModel = {
- student: contact;
- emergency: contact;
- serviceCoordinator: contact;
- location: string;
- medication: string;
- birthday: string;
- intakeDate: string;
- tourDate: string;
- programs: programLink[];
- dietary: string[];
- otherString: string;
-};
-
-type Contact = {
- lastName: string;
- firstName: string;
- email: string;
- phoneNumber: string;
-};
-
-type Student = {
- _id: string;
- student: Contact;
- emergency: Contact;
- serviceCoordinator: Contact;
- location: string;
- medication?: string;
- birthday: Date;
- intakeDate: Date;
- tourDate: Date;
- programs: programLink[];
- dietary: string[];
- otherString?: string;
-};
+type Student = HydratedDocument;
+type StudentRequest = Student & { enrollments: Enrollment[] };
export const createStudent: RequestHandler = async (req, res, next) => {
try {
@@ -60,9 +22,14 @@ export const createStudent: RequestHandler = async (req, res, next) => {
validationErrorParser(errors);
- const newStudent = await StudentModel.create(req.body as typedModel);
- const programIds = newStudent.programs.map((programObj: programLink) => programObj.programId);
- await addStudentToPrograms(newStudent._id, programIds);
+ const { enrollments, ...studentData } = req.body as StudentRequest;
+ const newStudent = await StudentModel.create(studentData);
+ // create enrollments for the student
+ await Promise.all(
+ enrollments.map(async (program: Enrollment) => {
+ await createEnrollment({ ...program, studentId: newStudent._id });
+ }),
+ );
res.status(201).json(newStudent);
} catch (error) {
@@ -77,34 +44,29 @@ export const editStudent: RequestHandler = async (req, res, next) => {
validationErrorParser(errors);
const studentId = req.params.id;
- const studentData = req.body as Student;
+ const { enrollments, ...studentData } = req.body as StudentRequest;
- if (studentId !== studentData._id) {
+ if (studentId !== studentData._id.toString()) {
return res.status(400).json({ message: "Invalid student ID" });
}
-
- const prevStudent = await StudentModel.findById(studentId);
- const editedStudent = await StudentModel.findOneAndUpdate({ _id: studentId }, studentData, {
+ const updatedStudent = await StudentModel.findByIdAndUpdate(studentId, studentData, {
new: true,
});
-
- if (!prevStudent || !editedStudent) {
- return res.status(404).json({ message: "No object in database with provided ID" });
+ if (!updatedStudent) {
+ return res.status(404).json({ message: "Student not found" });
}
- // remove student from possibly stale programs
- const prevProgramIds = prevStudent.programs.map(
- (programObj: programLink) => programObj.programId,
- );
- await removeStudentFromPrograms(prevStudent._id, prevProgramIds);
-
- // add student to new programs
- const newProgramIds = editedStudent.programs.map(
- (programObj: programLink) => programObj.programId,
+ // update enrollments for the student
+ 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);
+ }),
);
- await addStudentToPrograms(editedStudent._id, newProgramIds);
- res.status(200).json(editedStudent);
+ res.status(200).json({ ...updatedStudent, enrollments });
} catch (error) {
next(error);
}
@@ -114,26 +76,15 @@ export const getAllStudents: RequestHandler = async (_, res, next) => {
try {
const students = await StudentModel.find();
- res.status(200).json(students);
- } catch (error) {
- next(error);
- }
-};
-
-export const deleteAllStudents: RequestHandler = async (_, res, next) => {
- try {
- // remove students from all programs
- const students = await StudentModel.find();
- await Promise.all(
+ // gather all enrollments for each student and put them in student.programs
+ const hydratedStudents = await Promise.all(
students.map(async (student) => {
- const programIds = student.programs.map((programObj: programLink) => programObj.programId);
- await removeStudentFromPrograms(student._id, programIds);
+ const enrollments = await EnrollmentModel.find({ studentId: student._id });
+ return { ...student.toObject(), programs: enrollments };
}),
);
- await StudentModel.deleteMany();
-
- res.status(204).end();
+ res.status(200).json(hydratedStudents);
} catch (error) {
next(error);
}
diff --git a/backend/src/models/enrollment.ts b/backend/src/models/enrollment.ts
new file mode 100644
index 00000000..8d7b7d9b
--- /dev/null
+++ b/backend/src/models/enrollment.ts
@@ -0,0 +1,28 @@
+import mongoose, { InferSchemaType } from "mongoose";
+
+const enrollmentSchema = new mongoose.Schema({
+ studentId: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: "Student",
+ required: true,
+ unique: false,
+ },
+ programId: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: "Program",
+ required: true,
+ unique: false,
+ },
+ status: { type: String, required: true },
+ dateUpdated: { type: Date, required: true, default: Date.now() },
+ hoursLeft: { type: Number, required: true },
+ schedule: { type: [String], required: true },
+ sessionTime: { type: [String], required: true },
+ startDate: { type: Date, required: true },
+ renewalDate: { type: Date, required: true },
+ authNumber: { type: String, required: true },
+});
+
+type Enrollment = InferSchemaType;
+
+export default mongoose.model("Enrollment", enrollmentSchema);
diff --git a/backend/src/models/program.ts b/backend/src/models/program.ts
index 7d558e09..bc39d1d7 100644
--- a/backend/src/models/program.ts
+++ b/backend/src/models/program.ts
@@ -2,15 +2,11 @@ import { InferSchemaType, Schema, model } from "mongoose";
const programSchema = new Schema({
name: { type: String, required: true },
- abbreviation: { type: String, required: true }, // e.g. ENTR
+ abbreviation: { type: String, required: true }, // e.g. ENTR, should be unique
type: { type: String, required: true }, // regular vs. varying
daysOfWeek: { type: [String], required: true }, // M, T, W, TH, F
- startDate: { type: Date, required: true },
- endDate: { type: Date, required: true },
color: { type: String, required: true },
- students: { type: [Schema.Types.ObjectId], ref: "Students", required: false },
- renewalDate: { type: Date, required: true },
- hourly: { type: Number, required: true },
+ hourlyPay: { type: Number, required: true },
sessions: { type: [[String]], required: true },
});
diff --git a/backend/src/models/student.ts b/backend/src/models/student.ts
index f530f8c9..db6ad8b9 100644
--- a/backend/src/models/student.ts
+++ b/backend/src/models/student.ts
@@ -35,17 +35,11 @@ const studentSchema = new Schema({
intakeDate: { type: Date, required: true },
tourDate: { type: Date, required: true },
- programs: {
- type: [
- {
- programId: { type: Schema.Types.ObjectId, ref: "Program", required: true },
- status: { type: String, required: true },
- dateUpdated: { type: Date, required: true, default: Date.now() },
- hoursLeft: { type: Number, required: true },
- },
- ],
- required: true,
- },
+ conservation: { type: Boolean, required: true },
+ UCINumber: { type: String, required: true },
+ incidentForm: { type: String, required: true },
+ documents: { type: [String], required: true },
+ profilePicture: { type: Schema.Types.ObjectId, ref: "Image", required: false },
progressNotes: {
type: [Schema.Types.ObjectId],
@@ -56,8 +50,6 @@ const studentSchema = new Schema({
//Will contain list of all dietary restrictions
dietary: { type: [String] },
-
- otherString: { type: String, default: "" },
});
type Student = InferSchemaType;
diff --git a/backend/src/types/enrollment.ts b/backend/src/types/enrollment.ts
new file mode 100644
index 00000000..82149754
--- /dev/null
+++ b/backend/src/types/enrollment.ts
@@ -0,0 +1,13 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import mongoose, { Schema } from "mongoose";
+
+import EnrollmentModel from "../models/enrollment";
+
+// get the enrollment type from the enrollment model
+export type Enrollment = Extract<
+ typeof EnrollmentModel,
+ mongoose.Model
+> extends mongoose.Model
+ ? U & { _id: Schema.Types.ObjectId }
+ : never;
diff --git a/backend/src/types/programLink.ts b/backend/src/types/programLink.ts
deleted file mode 100644
index 43d0088b..00000000
--- a/backend/src/types/programLink.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import mongoose from "mongoose";
-
-export type programLink = {
- programId: mongoose.Types.ObjectId;
- status: string;
- dateUpdated: Date;
- hoursLeft: number;
-};
diff --git a/backend/src/util/enrollment.ts b/backend/src/util/enrollment.ts
new file mode 100644
index 00000000..0c92e3c1
--- /dev/null
+++ b/backend/src/util/enrollment.ts
@@ -0,0 +1,21 @@
+import EnrollmentModel from "../models/enrollment";
+import { Enrollment } from "../types/enrollment";
+
+export const createEnrollment = async (req: Enrollment) => {
+ try {
+ await EnrollmentModel.create(req);
+ } catch (e) {
+ console.log(e);
+ throw e;
+ }
+};
+
+export const editEnrollment = async (req: Enrollment) => {
+ try {
+ console.log(req);
+ await EnrollmentModel.findByIdAndUpdate(req._id, req);
+ } catch (e) {
+ console.log(e);
+ throw e;
+ }
+};
diff --git a/backend/src/util/student.ts b/backend/src/util/student.ts
index 13c0224d..39c564ec 100644
--- a/backend/src/util/student.ts
+++ b/backend/src/util/student.ts
@@ -1,58 +1,53 @@
import mongoose from "mongoose";
import ProgramModel from "../models/program";
-import { programLink } from "../types/programLink";
-
-type ObjectId = mongoose.Types.ObjectId;
-
-export const programValidatorUtil = async (programs: programLink[]) => {
+import { Enrollment } from "../types/enrollment";
+
+export const programValidatorUtil = async (enrollments: Enrollment[]) => {
+ // verify all fields are present
+ const requiredFields = [
+ "programId",
+ "status",
+ "hoursLeft",
+ "schedule",
+ "sessionTime",
+ "startDate",
+ "renewalDate",
+ "authNumber",
+ ];
+ enrollments.forEach((enrollment: Enrollment) => {
+ requiredFields.forEach((field) => {
+ if (!enrollment[field as keyof Enrollment])
+ throw new Error(`Field ${field} is required on enrollment`);
+ });
+ });
+
+ // verify statuses are correct and student is not in more than 2 programs
const allowedStatuses = ["Joined", "Waitlisted", "Archived", "Not a fit"];
const programIds = new Set();
let active = 0;
let varying = 0;
await Promise.all(
- programs.map(async (program) => {
- programIds.add(program.programId);
- if (!mongoose.Types.ObjectId.isValid(program.programId))
+ enrollments.map(async (enrollment) => {
+ programIds.add(enrollment.programId);
+ if (!mongoose.Types.ObjectId.isValid(enrollment.programId))
throw new Error("Program ID format is invalid");
- if (!allowedStatuses.includes(program.status))
+ if (!allowedStatuses.includes(enrollment.status))
throw new Error("Status must be one of: " + allowedStatuses.join(", "));
- const programType = (await ProgramModel.findById(program.programId))?.type;
- if (program.status === "Joined") {
+ const programType = (await ProgramModel.findById(enrollment.programId))?.type;
+ if (enrollment.status === "Joined") {
active++;
if (programType === "varying") varying++;
}
}),
);
- if (programIds.size !== programs.length) throw new Error("Programs must be unique");
+
+ // handle error reporting
+ if (programIds.size !== enrollments.length) throw new Error("Programs must be unique");
if (active > 2) throw new Error("Student can only be active in 2 programs");
if (varying > 1) throw new Error("Student can only be in 1 varying program");
return true;
};
-
-export const addStudentToPrograms = async (studentId: ObjectId, programIds: ObjectId[]) => {
- await Promise.all(
- programIds.map(async (programId) => {
- await ProgramModel.findByIdAndUpdate(
- programId,
- { $push: { students: studentId } },
- { new: true },
- );
- }),
- );
-};
-
-export const removeStudentFromPrograms = async (studentId: ObjectId, programIds: ObjectId[]) => {
- await Promise.all(
- programIds.map(async (programId) => {
- await ProgramModel.findByIdAndUpdate(
- programId,
- { $pull: { students: studentId } },
- { new: true },
- );
- }),
- );
-};
diff --git a/backend/src/validators/program.ts b/backend/src/validators/program.ts
index 702113c4..0b30bb70 100644
--- a/backend/src/validators/program.ts
+++ b/backend/src/validators/program.ts
@@ -2,6 +2,7 @@ import { body } from "express-validator";
//import mongoose from "mongoose";
import { Program } from "../controllers/program";
+import ProgramModel from "../models/program";
const makeNameValidator = () =>
body("name")
@@ -27,7 +28,15 @@ const makeAbbreviationValidator = () =>
.withMessage("abbreviation must be a string")
.bail()
.notEmpty()
- .withMessage("abbreviation must not be empty");
+ .withMessage("abbreviation must not be empty")
+ .custom(async (value: string, { req }) => {
+ const program = await ProgramModel.findOne({
+ _id: { $ne: (req.body as Program)._id },
+ abbreviation: value,
+ });
+ if (program) throw new Error("Program Abbreviation must be unique");
+ return true;
+ });
const makeTypeValidator = () =>
body("type")
.exists()
@@ -50,7 +59,12 @@ const makeDaysOfWeekValidator = () =>
.bail()
.isArray()
.withMessage("days of week selection must be an array")
- .custom((value: string[]) => {
+ .custom((value: string[], { req }) => {
+ if ((req.body as Program).type === "varying") {
+ if (value.length !== 0) throw new Error("Varying sessions cannot assigned Days of Week");
+ return true;
+ }
+
if (value.length === 0) throw new Error("days of week selection needed");
for (const valuei of value) {
if (
@@ -66,7 +80,7 @@ const makeDaysOfWeekValidator = () =>
}
return true;
});
-const makeStartDateValidator = () =>
+/*const makeStartDateValidator = () =>
body("startDate")
.exists()
.withMessage("start date needed")
@@ -86,7 +100,8 @@ const makeEndDateValidator = () =>
if (new Date(value) < new Date(reqBody.startDate))
throw new Error("end date must be after start date");
return true;
- });
+ });*/
+
const makeColorValidator = () =>
body("color")
.exists()
@@ -133,15 +148,16 @@ const makeColorValidator = () =>
})
.bail()
.withMessage("students must be valid student ids");*/
-const makeRenewalDateValidator = () =>
+/*const makeRenewalDateValidator = () =>
body("renewalDate")
.exists()
.withMessage("renewal date needed")
.bail()
.isISO8601()
- .withMessage("renewal date must be a valid date-time string");
+ .withMessage("renewal date must be a valid date-time string");*/
+
const makeHourlyPayValidator = () =>
- body("hourly")
+ body("hourlyPay")
.exists()
.withMessage("hourly pay needed")
.bail()
@@ -157,7 +173,12 @@ const makeSessionsValidator = () =>
.isArray()
.withMessage("Sessions must be a 2D String Array")
.bail()
- .custom((sessions: string[][]) => {
+ .custom((sessions: string[][], { req }) => {
+ if ((req.body as Program).type === "varying") {
+ if (sessions.length !== 0) throw new Error("Varying sessions cannot have session times");
+ return true;
+ }
+ // Assumes program type is regular
if (sessions.length === 0) throw new Error("Must specify a session time");
sessions.forEach((session) => {
if (!Array.isArray(session)) throw new Error("Session must be an array");
@@ -178,10 +199,10 @@ export const createProgram = [
makeAbbreviationValidator(),
makeTypeValidator(),
makeDaysOfWeekValidator(),
- makeStartDateValidator(),
- makeEndDateValidator(),
+ //makeStartDateValidator(),
+ //makeEndDateValidator(),
makeColorValidator(),
- makeRenewalDateValidator(),
+ //makeRenewalDateValidator(),
makeHourlyPayValidator(),
makeSessionsValidator(),
];
@@ -191,10 +212,10 @@ export const updateProgram = [
makeAbbreviationValidator(),
makeTypeValidator(),
makeDaysOfWeekValidator(),
- makeStartDateValidator(),
- makeEndDateValidator(),
+ //makeStartDateValidator(),
+ //makeEndDateValidator(),
makeColorValidator(),
- makeRenewalDateValidator(),
+ //makeRenewalDateValidator(),
makeHourlyPayValidator(),
makeSessionsValidator(),
//makeStudentUIDsValidator(),
diff --git a/backend/src/validators/student.ts b/backend/src/validators/student.ts
index 302bcba6..aa7254d8 100644
--- a/backend/src/validators/student.ts
+++ b/backend/src/validators/student.ts
@@ -115,13 +115,57 @@ const makeTourDateValidator = () =>
.toDate()
.withMessage("Tour Date string must be a valid date-time string");
-const makePrograms = () =>
- body("programs")
+const makeConservationValidator = () =>
+ body("conservation")
.exists()
- .withMessage("Programs field required")
+ .withMessage("Conservation field required")
+ .bail()
+ .isBoolean()
+ .withMessage("Conservation must be a boolean");
+
+const makeUCINumberValidator = () =>
+ body("UCINumber")
+ .exists()
+ .withMessage("UCI Number field required")
+ .bail()
+ .isString()
+ .withMessage("UCI Number must be a string")
+ .bail()
+ .notEmpty()
+ .withMessage("UCI Number field required");
+
+const makeIncidentFormValidator = () =>
+ body("incidentForm")
+ .exists()
+ .withMessage("Incident Form field required")
+ .bail()
+ .isString()
+ .withMessage("Incident Form must be a string")
+ .bail()
+ .notEmpty()
+ .withMessage("Incident Form field required");
+
+const makeDocumentsValidator = () =>
+ body("documents")
+ .exists()
+ .withMessage("Documents field required")
+ .bail()
+ .isArray()
+ .withMessage("Documents must be an array")
+ .bail()
+ .custom((value: string[]) => value.every((doc) => typeof doc === "string"))
+ .withMessage("Documents must be an array of strings");
+
+const makeProfilePictureValidator = () =>
+ body("profilePicture").optional().isString().withMessage("Profile picture must be a string");
+
+const makeEnrollments = () =>
+ body("enrollments")
+ .exists()
+ .withMessage("Enrollments field required")
.bail()
.isArray()
- .withMessage("Programs must be a non-empty array")
+ .withMessage("Enrollments must be a non-empty array")
.bail()
.custom(programValidatorUtil);
@@ -153,7 +197,12 @@ export const createStudent = [
makeBirthdayValidator(),
makeIntakeDateValidator(),
makeTourDateValidator(),
- makePrograms(),
+ makeConservationValidator(),
+ makeUCINumberValidator(),
+ makeIncidentFormValidator(),
+ makeDocumentsValidator(),
+ makeProfilePictureValidator(),
+ makeEnrollments(),
makeDietaryArrayValidator(),
makeDietaryItemsValidator(),
makeDietaryOtherValidator(),
diff --git a/frontend/src/api/programs.ts b/frontend/src/api/programs.ts
index 7bafeda0..83b82f2f 100644
--- a/frontend/src/api/programs.ts
+++ b/frontend/src/api/programs.ts
@@ -3,7 +3,7 @@ import { CreateProgramRequest } from "../components/ProgramForm/types";
import type { APIResult } from "../api/requests";
-export type Program = CreateProgramRequest & { _id: string; students: string[] };
+export type Program = CreateProgramRequest & { _id: string };
export async function createProgram(program: CreateProgramRequest): Promise> {
try {
diff --git a/frontend/src/components/ProgramCard.tsx b/frontend/src/components/ProgramCard.tsx
index e406d9c2..b54da3b8 100644
--- a/frontend/src/components/ProgramCard.tsx
+++ b/frontend/src/components/ProgramCard.tsx
@@ -87,13 +87,13 @@ export function ProgramCard({ program, isAdmin, className, setPrograms }: CardPr
abbreviation: program.abbreviation,
type: program.type,
daysOfWeek: program.daysOfWeek,
- startDate: program.startDate,
- endDate: program.endDate,
+ //startDate: program.startDate,
+ //endDate: program.endDate,
color: program.color,
- renewalDate: program.renewalDate,
- hourly: program.hourly,
+ //renewalDate: program.renewalDate,
+ hourlyPay: program.hourlyPay,
sessions: program.sessions,
- students: program.students,
+ //students: program.students,
};
if (isTablet) {
@@ -196,11 +196,13 @@ export function ProgramCard({ program, isAdmin, className, setPrograms }: CardPr
width={18}
className={iconClass}
/>
- {program.students.length === 0 && No Students
}
- {program.students.length === 1 && 1 Student
}
- {program.students.length > 1 && (
- {program.students.length} Students
- )}
+ {/*program.students.length === 0 && No Students
*/}
+ {/*program.students.length === 1 && 1 Student
*/}
+ {
+ //program.students.length > 1 && (
+ {/*program.students.length*/}0 Students
+ //)
+ }
diff --git a/frontend/src/components/ProgramForm/ProgramArchive.tsx b/frontend/src/components/ProgramForm/ProgramArchive.tsx
index a81b9b5f..480421f8 100644
--- a/frontend/src/components/ProgramForm/ProgramArchive.tsx
+++ b/frontend/src/components/ProgramForm/ProgramArchive.tsx
@@ -1,8 +1,22 @@
+import { useState } from "react";
+import { useForm } from "react-hook-form";
+
+import { Program } from "../../api/programs";
+import { Button } from "../Button";
+import { Textfield } from "../Textfield";
+import { Dialog, DialogClose, DialogContentSlide, DialogTrigger } from "../ui/dialog";
+
type props = {
label: string;
};
-export default function ProgramArchiveHeader({ label }: props) {
+type archiveProps = {
+ setOpenParent: React.Dispatch>;
+ data: Program;
+ isMobile?: boolean;
+};
+
+function ProgramArchiveHeader({ label }: props) {
return (
@@ -32,3 +46,90 @@ export default function ProgramArchiveHeader({ label }: props) {
);
}
+
+//Currently no functionality, just a button that closes the form
+export default function ProgramArchive({ setOpenParent, data, isMobile = false }: archiveProps) {
+ const [openArchive, setOpenArchive] = useState(false);
+ const {
+ register: archiveRegister,
+ reset: archiveReset,
+ setValue: setArchiveCalendarValue,
+ getValues: getArchiveValue,
+ } = useForm<{ date: string }>();
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/ProgramForm/ProgramCancel.tsx b/frontend/src/components/ProgramForm/ProgramCancel.tsx
index e3aa585a..d3be0663 100644
--- a/frontend/src/components/ProgramForm/ProgramCancel.tsx
+++ b/frontend/src/components/ProgramForm/ProgramCancel.tsx
@@ -3,15 +3,24 @@ import { Dialog, DialogClose, DialogContent, DialogTrigger } from "../ui/dialog"
type cancelProps = {
isMobile?: boolean;
+ open: boolean;
+ setOpen: React.Dispatch>;
onCancel: () => void;
};
-export default function ProgramCancel({ isMobile = false, onCancel }: cancelProps) {
+export default function ProgramCancel({ isMobile = false, open, setOpen, onCancel }: cancelProps) {
return (
-
-
+
diff --git a/frontend/src/components/ProgramForm/ProgramInfo.tsx b/frontend/src/components/ProgramForm/ProgramInfo.tsx
index 896234bf..831b31ed 100644
--- a/frontend/src/components/ProgramForm/ProgramInfo.tsx
+++ b/frontend/src/components/ProgramForm/ProgramInfo.tsx
@@ -1,9 +1,9 @@
+import { useState } from "react";
import { Path, UseFormRegister, UseFormSetValue } from "react-hook-form";
import { Program } from "../../api/programs";
import { cn } from "../../lib/utils";
import { ColorRadio } from "../Radio";
-import { convertDateToString } from "../StudentForm/StudentBackground";
import { Textfield } from "../Textfield";
import { SessionList } from "./ProgramSession";
@@ -14,6 +14,7 @@ type ProgramInfoProperties = {
classname?: string;
setCalendarValue: UseFormSetValue;
data: Program | null;
+ mode: string;
};
type CheckcircleProps = {
@@ -92,12 +93,10 @@ export function Checkcircle({ options, className, name, register, data }: Checkc
);
}
-export function ProgramInfo({
- register,
- classname,
- setCalendarValue,
- data,
-}: ProgramInfoProperties) {
+export function ProgramInfo({ register, classname, data, mode }: ProgramInfoProperties) {
+ // ie, program form will either be "regular" or "not regular"
+ const [regularType, setRegularType] = useState(data ? data.type === "regular" : true);
+
return (
{
+ setRegularType(true);
+ }}
+ disabled={mode === "edit"}
/>
@@ -159,7 +162,11 @@ export function ProgramInfo({
id={"varying"}
type="radio"
value={"varying"}
- defaultChecked={data?.type === "varying"}
+ defaultChecked={data ? data?.type === "varying" : false}
+ onInput={() => {
+ setRegularType(false);
+ }}
+ disabled={mode === "edit"}
/>
@@ -171,63 +178,30 @@ export function ProgramInfo({
-
-
-
-
End Date
-
+
+ Days of the week
+
+
-
-
-
+ )}
-
-
- Days of the week
-
-
-
-
-
+ {regularType && (
+
+ )}
Color (Cover)
diff --git a/frontend/src/components/ProgramForm/types.ts b/frontend/src/components/ProgramForm/types.ts
index 29578950..424a8d7c 100644
--- a/frontend/src/components/ProgramForm/types.ts
+++ b/frontend/src/components/ProgramForm/types.ts
@@ -2,13 +2,10 @@ export type ProgramData = {
name: string;
abbreviation: string;
type: string;
- days: string[];
- startDate: Date;
- endDate: Date;
- color: string;
- renewalDate: Date;
- hourly: string;
- sessions: string[][];
+ daysOfWeek: string[];
+ color: string; //colorValueHex;
+ hourlyPay: string;
+ sessions: [string[]];
};
export type CreateProgramRequest = {
@@ -16,11 +13,7 @@ export type CreateProgramRequest = {
abbreviation: string;
type: string;
daysOfWeek: string[];
- startDate: Date;
- endDate: Date;
color: string;
- renewalDate: Date;
- hourly: string;
+ hourlyPay: string;
sessions: string[][];
- students?: string[];
};
diff --git a/frontend/src/components/ProgramFormButton.tsx b/frontend/src/components/ProgramFormButton.tsx
index d6ab5fe8..b4431a22 100644
--- a/frontend/src/components/ProgramFormButton.tsx
+++ b/frontend/src/components/ProgramFormButton.tsx
@@ -6,12 +6,11 @@ import { useWindowSize } from "../hooks/useWindowSize";
import { cn } from "../lib/utils";
import { Button } from "./Button";
-import ProgramArchiveHeader from "./ProgramForm/ProgramArchive";
+import ProgramArchive from "./ProgramForm/ProgramArchive";
import ProgramCancel from "./ProgramForm/ProgramCancel";
import { ProgramInfo } from "./ProgramForm/ProgramInfo";
import { CreateProgramRequest, ProgramData } from "./ProgramForm/types";
import { ProgramMap } from "./StudentsTable/types";
-import { Textfield } from "./Textfield";
import { Dialog, DialogClose, DialogContent, DialogContentSlide, DialogTrigger } from "./ui/dialog";
type BaseProperties = {
@@ -40,15 +39,9 @@ export default function ProgramFormButton({
classname,
}: ProgramFormProperties) {
const { register, setValue: setCalendarValue, reset, handleSubmit } = useForm
();
- const {
- register: archiveRegister,
- reset: archiveReset,
- setValue: setArchiveCalendarValue,
- getValues: getArchiveValue,
- } = useForm<{ date: string }>();
const [openForm, setOpenForm] = useState(false);
- const [openArchive, setOpenArchive] = useState(false);
+ const [openCancel, setOpenCancel] = useState(false);
const { width } = useWindowSize().windowSize;
const isMobile = useMemo(() => width <= 640, [width]);
@@ -57,18 +50,20 @@ export default function ProgramFormButton({
? formData.sessions.filter((session: string[]) => session[0] || session[1])
: [["", ""]];
+ const programRequestType = data ? data.type : formData.type; //type selector is disabled when editing
+
+ //If the program type is not regular, then daysOfWeek and sessions will be empty lists.
const programRequest: CreateProgramRequest = {
name: formData.name,
abbreviation: formData.abbreviation,
- type: formData.type,
- daysOfWeek: formData.days ? formData.days : [],
- startDate: new Date(formData.startDate),
- endDate: new Date(formData.endDate),
+ type: programRequestType,
+ daysOfWeek:
+ formData.daysOfWeek && programRequestType === "regular" ? formData.daysOfWeek : [],
color: formData.color,
- renewalDate: new Date(formData.renewalDate),
- hourly: formData.hourly,
- sessions: sanitizedSessions,
+ hourlyPay: formData.hourlyPay,
+ sessions: programRequestType === "regular" ? sanitizedSessions : [],
};
+
console.log(`${type} program`, programRequest);
if (type === "add") {
createProgram(programRequest)
@@ -90,7 +85,7 @@ export default function ProgramFormButton({
});
}
if (type === "edit" && data) {
- const updatedProgram: Program = { ...programRequest, _id: data._id, students: data.students };
+ const updatedProgram: Program = { ...programRequest, _id: data._id };
console.log(`${type} program`, updatedProgram);
editProgram(updatedProgram)
.then((result) => {
@@ -119,72 +114,15 @@ export default function ProgramFormButton({
<>
{component}
-
+ {
+ event.preventDefault();
+ setOpenCancel(true);
+ }}
+ >