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

Feature/mraysu/program archive #96

Merged
merged 14 commits into from
Jun 15, 2024
56 changes: 51 additions & 5 deletions backend/src/controllers/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RequestHandler } from "express";
import { validationResult } from "express-validator";
//import { error } from "firebase-functions/logger";

import EnrollmentModel from "../models/enrollment";
import ProgramModel from "../models/program";
import validationErrorParser from "../util/validationErrorParser";

Expand All @@ -15,6 +16,11 @@ export type Program = {
color: string; //colorValueHex;
hourlyPay: string;
sessions: [string[]];
archived: boolean;
};

export type ExistingProgram = Program & {
dateUpdated: string;
};

export const createProgram: RequestHandler = async (req, res, next) => {
Expand All @@ -23,7 +29,10 @@ export const createProgram: RequestHandler = async (req, res, next) => {
try {
validationErrorParser(errors);

const programForm = await ProgramModel.create(req.body as Program);
const programForm = await ProgramModel.create({
...(req.body as Program),
dateUpdated: new Date().toISOString(),
});

res.status(201).json(programForm);
} catch (error) {
Expand All @@ -37,22 +46,59 @@ export const updateProgram: RequestHandler = async (req, res, next) => {
validationErrorParser(errors);

const programId = req.params.id;
const programData = req.body as Program;
const programData = req.body as ExistingProgram;

const editedProgram = await ProgramModel.findOneAndUpdate({ _id: programId }, programData, {
new: true,
});
const editedProgram = await ProgramModel.findOneAndUpdate(
{ _id: programId },
{ ...programData, archived: false, dateUpdated: new Date().toISOString() }, //stand-in method of un-archiving programs
{
new: true,
},
);

if (!editedProgram) {
return res.status(404).json({ message: "No object in database with provided ID" });
}

// Waitlist all archived students. Making sure to only waitlist Archived students
// will prevent enrollments from being updated every time the program is updated
await EnrollmentModel.updateMany(
{ programId: { $eq: programId }, status: { $eq: "Archived" } },
{ $set: { status: "Waitlisted", dateUpdated: Date.now() } },
);

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

export const archiveProgram: RequestHandler = async (req, res, next) => {
const errors = validationResult(req);
try {
validationErrorParser(errors);

const programId = req.params.id;
const program = await ProgramModel.findByIdAndUpdate(
programId,
{ $set: { archived: true, dateUpdated: new Date().toISOString() } },
{ new: true },
);
if (!program)
return res.status(404).json({ message: "Program with this id not found in database" });

//Archive all students
await EnrollmentModel.updateMany(
{ programId: { $eq: programId } },
{ $set: { status: "Archived", dateUpdated: Date.now() } },
);

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

export const getAllPrograms: RequestHandler = async (req, res, next) => {
try {
const programs = await ProgramModel.find();
Expand Down
3 changes: 3 additions & 0 deletions backend/src/models/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const programSchema = new Schema({
color: { type: String, required: true },
hourlyPay: { type: Number, required: true },
sessions: { type: [[String]], required: true },
archived: { type: Boolean, required: true },

dateUpdated: { type: String, required: true },
});

type Program = InferSchemaType<typeof programSchema>;
Expand Down
1 change: 1 addition & 0 deletions backend/src/routes/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const router = express.Router();

router.patch("/:id", ProgramValidator.updateProgram, ProgramController.updateProgram);
router.post("/create", ProgramValidator.createProgram, ProgramController.createProgram);
router.post("/archive/:id", ProgramController.archiveProgram);
router.get("/all", ProgramController.getAllPrograms);

export default router;
12 changes: 11 additions & 1 deletion frontend/src/api/programs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CreateProgramRequest } from "../components/ProgramForm/types";

import type { APIResult } from "../api/requests";

export type Program = CreateProgramRequest & { _id: string };
export type Program = CreateProgramRequest & { _id: string; dateUpdated: string };

export async function createProgram(program: CreateProgramRequest): Promise<APIResult<Program>> {
try {
Expand Down Expand Up @@ -46,3 +46,13 @@ export async function getAllPrograms(): Promise<APIResult<[Program]>> {
return handleAPIError(error);
}
}

export async function archiveProgram(program: Program): Promise<APIResult<Program>> {
try {
const response = await POST(`/program/archive/${program._id}`, undefined);
const json = (await response.json()) as Program;
return { success: true, data: json };
} catch (error) {
return handleAPIError(error);
}
}
35 changes: 35 additions & 0 deletions frontend/src/components/AlertCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { MouseEventHandler } from "react";

export default function AlertCard({
message,
open,
onClose,
}: {
message: string;
open: boolean;
onClose: MouseEventHandler;
}) {
if (!open) return <></>;
return (
<div className="absolute bottom-0 left-0 flex min-h-12 w-full justify-center sm:min-h-16">
<div className="z-20 flex max-h-8 min-w-[10%] max-w-[90%] items-center rounded-sm bg-black sm:max-h-12 sm:max-w-[40%]">
<div className="flex max-w-full flex-row items-center overflow-hidden " onClick={onClose}>
<svg
width="16"
height="16"
viewBox="0 0 10 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="m-4"
>
<path
d="M1.70711 0.292894C1.31658 -0.0976312 0.683417 -0.0976312 0.292893 0.292894C-0.0976311 0.683418 -0.0976311 1.31658 0.292893 1.70711L3.58579 5L0.292893 8.29289C-0.0976311 8.68342 -0.0976311 9.31658 0.292893 9.70711C0.683417 10.0976 1.31658 10.0976 1.70711 9.70711L5 6.41421L8.29289 9.70711C8.68342 10.0976 9.31658 10.0976 9.70711 9.70711C10.0976 9.31658 10.0976 8.68342 9.70711 8.29289L6.41421 5L9.70711 1.70711C10.0976 1.31658 10.0976 0.683418 9.70711 0.292894C9.31658 -0.0976301 8.68342 -0.0976301 8.29289 0.292894L5 3.58579L1.70711 0.292894Z"
fill="White"
/>
</svg>
<p className="truncate pr-4 text-sm text-white sm:text-lg">{message}</p>
</div>
</div>
</div>
);
}
48 changes: 38 additions & 10 deletions frontend/src/components/ProgramCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export type CardProps = {
isAdmin: boolean;
className?: string;
setPrograms: React.Dispatch<React.SetStateAction<ProgramMap>>;
setAlertState: React.Dispatch<React.SetStateAction<{ open: boolean; message: string }>>;
archiveView?: boolean;
};

// function checkOffscreen(id: string) {
Expand Down Expand Up @@ -53,7 +55,14 @@ function toggleEdit(id: string) {
// }
}

export function ProgramCard({ program, isAdmin, className, setPrograms }: CardProps) {
export function ProgramCard({
program,
isAdmin,
className,
setPrograms,
setAlertState,
archiveView = false,
}: CardProps) {
const { isTablet } = useWindowSize();

const editId = "edit" + program._id;
Expand Down Expand Up @@ -94,8 +103,12 @@ export function ProgramCard({ program, isAdmin, className, setPrograms }: CardPr
hourlyPay: program.hourlyPay,
sessions: program.sessions,
//students: program.students,
archived: program.archived,
dateUpdated: program.dateUpdated,
};

const date = new Date(program.dateUpdated);

if (isTablet) {
editClass += " top-7 w-12 h-5 text-[10px]";
outerDivClass += " rounded-lg h-36";
Expand Down Expand Up @@ -166,6 +179,7 @@ export function ProgramCard({ program, isAdmin, className, setPrograms }: CardPr
component={editButton}
data={programFields}
setPrograms={setPrograms}
setAlertState={setAlertState}
/>
</div>
<div className={outerDivClass}>
Expand All @@ -174,7 +188,7 @@ export function ProgramCard({ program, isAdmin, className, setPrograms }: CardPr
<p className={typeClass}>{program.type} Program</p>
<p className={titleClass}>{program.name}</p>
</div>
{isAdmin && (
{isAdmin && !archiveView && (
<div className={optionsDiv}>
<Image
id={optionsId}
Expand All @@ -189,18 +203,32 @@ export function ProgramCard({ program, isAdmin, className, setPrograms }: CardPr
</div>
<div className={botDivClass}>
<div className={numClass}>
<Image
alt="students"
src="/programs/Students.png"
height={12}
width={18}
className={iconClass}
/>
{!archiveView && (
<Image
alt="students"
src="/programs/Students.png"
height={12}
width={18}
className={iconClass}
/>
)}
{/*program.students.length === 0 && <p className={numTextClass}>No Students</p>*/}
{/*program.students.length === 1 && <p className={numTextClass}>1 Student</p>*/}
{
//program.students.length > 1 && (
<p className={numTextClass}>{/*program.students.length*/}0 Students</p>
<p className={numTextClass}>
{/*program.students.length*/}
{
archiveView
? "Archived on " +
(date.getMonth() + 1) +
"/" +
date.getDate() +
"/" +
date.getFullYear()
: "0 Students" //<---- Change in the future --------
}
</p>
//)
}
</div>
Expand Down
Loading
Loading