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/andrewzpu/program profile pages #105

Merged
merged 26 commits into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9687967
added backend route for getting single program
andrewzpu Apr 22, 2024
e07c07a
added separate pages for each program
andrewzpu Apr 22, 2024
f0b3480
Added program edit popup and "no programs" message
andrewzpu Apr 29, 2024
c3a2a54
Fixed add button display
andrewzpu Apr 29, 2024
bd578f6
Reorganized ProgramFormButton to take take any component
andrewzpu Apr 29, 2024
6f087aa
Merge branch 'feature/andrewzpu/program-page-edit-and-empty' into fea…
andrewzpu May 6, 2024
1476902
Removed start and end date from card
andrewzpu May 6, 2024
06d7180
Connected Program Card to Program Profile Pages
andrewzpu May 6, 2024
196ad33
Fixed Merge Conflicts
andrewzpu May 6, 2024
0af10e8
Set up basic framework of Program Profile Page
andrewzpu May 6, 2024
3cb6b5a
Added feature where clicking anywhere else will close the edit popup …
andrewzpu May 6, 2024
bcb17f7
Updated popup and redirect functionality of program profiles
andrewzpu May 13, 2024
303c0d7
Fixed merge conflicts and lint errors
andrewzpu May 20, 2024
5d4ad29
Finished full screen view of program profile
andrewzpu May 20, 2024
8b35709
Fixed hourly pay references
andrewzpu May 28, 2024
1a13576
Added backend route to get program enrollments
andrewzpu May 28, 2024
6241c0f
Connected enrollments route to program profile page
andrewzpu May 28, 2024
565619d
Merged main into branch
andrewzpu May 28, 2024
027dc06
Updated student count messages on cards and program profile
andrewzpu May 28, 2024
233cc9e
Added Student Names to Enrollment Table
andrewzpu Jun 2, 2024
bbe5eb8
Fixed mobile view
andrewzpu Jun 2, 2024
e846e4e
Fixed minor details
andrewzpu Jun 3, 2024
6e45c21
Minor Table Change
andrewzpu Jun 3, 2024
bce0f03
Merge branch 'main' into feature/andrewzpu/program-profile-pages
adhi0331 Jun 15, 2024
5310409
finish merge
adhi0331 Jun 16, 2024
508047c
fixed try catch error
adhi0331 Jun 16, 2024
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
31 changes: 30 additions & 1 deletion backend/src/controllers/program.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import { RequestHandler } from "express";
import { validationResult } from "express-validator";
//import { error } from "firebase-functions/logger";
import createHttpError from "http-errors";
// import { error } from "firebase-functions/logger";

import EnrollmentModel from "../models/enrollment";
import ProgramModel from "../models/program";
Expand Down Expand Up @@ -73,6 +74,22 @@ export const updateProgram: RequestHandler = async (req, res, next) => {
}
};

export const getProgram: RequestHandler = async (req, res, next) => {
const { id } = req.params;

try {
const program = await ProgramModel.findById(id);

if (program === null) {
throw createHttpError(404, "Program not found");
}

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

export const archiveProgram: RequestHandler = async (req, res, next) => {
const errors = validationResult(req);
try {
Expand Down Expand Up @@ -108,3 +125,15 @@ export const getAllPrograms: RequestHandler = async (req, res, next) => {
next(error);
}
};

export const getProgramEnrollments: RequestHandler = async (req, res, next) => {
const { id } = req.params;

try {
const enrollments = await EnrollmentModel.find({ programId: id });

res.status(200).json(enrollments);
} catch (error) {
next(error);
}
};
2 changes: 2 additions & 0 deletions backend/src/routes/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ router.patch("/:id", ProgramValidator.updateProgram, ProgramController.updatePro
router.post("/create", ProgramValidator.createProgram, ProgramController.createProgram);
router.post("/archive/:id", ProgramController.archiveProgram);
router.get("/all", ProgramController.getAllPrograms);
router.get("/:id", ProgramController.getProgram);
router.get("/enrollments/:id", ProgramController.getProgramEnrollments);

export default router;
Binary file added frontend/public/programs/BackArrow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/programs/GreenStudents.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 25 additions & 1 deletion frontend/src/api/programs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ import type { APIResult } from "../api/requests";

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

export type Enrollment = {
_id: string;
studentId: string;
programId: string;
status: string;
dateUpdated: Date;
hoursLeft: number;
schedule: string[];
sessionTime: string[];
startDate: Date;
renewalDate: Date;
authNumber: string;
};

export async function createProgram(program: CreateProgramRequest): Promise<APIResult<Program>> {
try {
const response = await POST("/program/create", program);
Expand All @@ -18,14 +32,24 @@ export async function createProgram(program: CreateProgramRequest): Promise<APIR

export async function getProgram(id: string): Promise<APIResult<Program>> {
try {
const response = await GET(`/api/program/${id}`);
const response = await GET(`/program/${id}`);
const json = (await response.json()) as Program;
return { success: true, data: json };
} catch (error) {
return handleAPIError(error);
}
}

export async function getProgramEnrollments(id: string): Promise<APIResult<[Enrollment]>> {
try {
const response = await GET(`/program/enrollments/${id}`);
const json = (await response.json()) as [Enrollment];
return { success: true, data: json };
} catch (error) {
return handleAPIError(error);
}
}

export async function editProgram(program: Program): Promise<APIResult<Program>> {
try {
const response = await PATCH(`/program/${program._id}`, program);
Expand Down
213 changes: 138 additions & 75 deletions frontend/src/components/ProgramCard.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Poppins } from "next/font/google";
import Image from "next/image";
import React from "react";
import { useRouter } from "next/navigation";
import React, { useEffect, useState } from "react";

import { Program } from "../api/programs";
import { Enrollment, Program, getProgramEnrollments } from "../api/programs";
import ProgramFormButton from "../components/ProgramFormButton";
import { ProgramProfile } from "../components/ProgramProfile";
import { useWindowSize } from "../hooks/useWindowSize";
import { cn } from "../lib/utils";

Expand All @@ -20,21 +22,6 @@ export type CardProps = {
archiveView?: boolean;
};

// function checkOffscreen(id: string) {
// const elem = document.getElementById("edit" + id);
// if (elem === null) {
// return;
// }

// const bounding = elem.getBoundingClientRect();

// console.log(bounding.right);
// console.log(window.innerWidth);
// console.log(document.documentElement.clientWidth);

// return bounding.right + 26 - (window.innerWidth || document.documentElement.clientWidth);
// }

function toggleEdit(id: string) {
const editId = "edit" + id;
console.log(editId);
Expand All @@ -50,9 +37,13 @@ function toggleEdit(id: string) {
editButton.style.display = "none";
}

// if (checkOffscreen(id) > 0) {
// editButton.style.right = "0px";
// }
function temp() {
if (editButton !== null && editButton.style.display === "block") {
editButton.style.display = "none";
}
}

document.body.addEventListener("click", temp, true);
}

export function ProgramCard({
Expand All @@ -65,27 +56,53 @@ export function ProgramCard({
}: CardProps) {
const { isTablet } = useWindowSize();

const cardId = "card" + program._id;
const editId = "edit" + program._id;
const optionsId = "option" + program._id;

const router = useRouter();

const [enrollments, setEnrollments] = useState<[Enrollment]>();

useEffect(() => {
getProgramEnrollments(program._id).then(
(result) => {
if (result.success) {
setEnrollments(result.data);
console.log("enrollments found");
} else {
console.log("error finding enrollments");
}
},
(error) => {
console.log(error);
},
);
}, []);

const optionsButton = document.getElementById(optionsId);
if (optionsButton !== null) {
optionsButton.onclick = function () {
optionsButton.onclick = function (event) {
event.stopPropagation();
toggleEdit(program._id);
};
}

let editClass = "absolute right-2 hidden";
const cardClass = "relative z-0 cursor-pointer";
let editClass = "absolute right-2 hidden z-10";
let outerDivClass = "text-white grow overflow-hidden tracking-wide leading-6";
let topDivClass = "flex flex-row";
let botDivClass = "text-black bg-white";
let typeClass;
let titleClass;
let optionsDiv = "grow";
const optionsClass = "relative float-right hover:cursor-pointer";
const optionsClass = "relative float-right hover:cursor-pointer z-10";
let numClass;
let numTextClass;
let iconClass = "relative";
const dialogBgClass =
"fixed left-0 top-0 h-full w-full py-10 px-40 bg-black bg-opacity-50 z-20 hidden";
const dialogClass = "relative z-20 h-full w-full m-auto overflow-hidden rounded-xl";

let optionsHeight = 18;
let optionsWidth = 16;
Expand Down Expand Up @@ -121,7 +138,7 @@ export function ProgramCard({
optionsWidth /= 2;
numClass = "h-5 gap-x-1.5 flex flex-row relative top-2 left-3";
numTextClass = cn("text-[10px]", poppins.className);
iconClass = "h-2 w-3 mt-[7px]";
iconClass = "h-2 w-3 mt-[7.5px]";
} else {
editClass += " top-12 w-24 h-8";
outerDivClass += " rounded-2xl h-68";
Expand Down Expand Up @@ -171,69 +188,115 @@ export function ProgramCard({
</button>
);

function openDialog() {
console.log("open");
if (isTablet) {
router.push("/program/" + program._id);
} else {
const dialogBox = document.getElementById("dialog" + program._id);
if (dialogBox) {
dialogBox.style.display = "block";
}
}
}

function closeDialog() {
console.log("close");
const dialogBox = document.getElementById("dialog" + program._id);
if (dialogBox) {
dialogBox.style.display = "none";
}
}

function getStudentNum(num: number): string {
if (num === 1) {
return "1 student";
} else {
return num + " students";
}
}

return (
<div className="relative">
<div id={editId} className={editClass}>
<ProgramFormButton
type="edit"
component={editButton}
data={programFields}
setPrograms={setPrograms}
setAlertState={setAlertState}
/>
</div>
<div className={outerDivClass}>
<div className={topDivClass} style={{ backgroundColor: program.color }}>
<div>
<p className={typeClass}>{program.type} Program</p>
<p className={titleClass}>{program.name}</p>
</div>
{isAdmin && !archiveView && (
<div className={optionsDiv}>
<Image
id={optionsId}
alt="options"
src="/programs/Options.png"
height={optionsHeight}
width={optionsWidth}
className={optionsClass}
/>
</div>
)}
<div>
<div id={cardId} className={cardClass} onClick={openDialog}>
<div
id={editId}
className={editClass}
onClick={(event) => {
event.stopPropagation();
}}
>
<ProgramFormButton
type="edit"
component={editButton}
data={programFields}
setPrograms={setPrograms}
setAlertState={setAlertState}
/>
</div>
<div className={botDivClass}>
<div className={numClass}>
{!archiveView && (
<Image
alt="students"
src="/programs/Students.png"
height={12}
width={18}
className={iconClass}
/>
<div className={outerDivClass}>
<div className={topDivClass} style={{ backgroundColor: program.color }}>
<div>
<p className={typeClass}>{program.type} Program</p>
<p className={titleClass}>{program.name}</p>
</div>
{isAdmin && (
<div className={optionsDiv}>
<Image
id={optionsId}
alt="options"
src="/programs/Options.png"
height={optionsHeight}
width={optionsWidth}
className={optionsClass}
/>
</div>
)}
{/*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*/}
{
archiveView
</div>
<div className={botDivClass}>
<div className={numClass}>
{!archiveView && (
<Image
alt="students"
src="/programs/Students.png"
height={12}
width={18}
className={iconClass}
/>
)}
{enrollments && (
<p className={numTextClass}>
{archiveView
? "Archived on " +
(date.getMonth() + 1) +
"/" +
date.getDate() +
"/" +
date.getFullYear()
: "0 Students" //<---- Change in the future --------
}
</p>
//)
}
: getStudentNum(enrollments.length)}
</p>
)}
</div>
</div>
</div>
</div>
<div id={"dialog" + program._id} className={dialogBgClass} onClick={closeDialog}>
<div
className={dialogClass}
onClick={(event) => {
event.stopPropagation();
}}
>
<ProgramProfile id={program._id}></ProgramProfile>
<button
id="closeButton"
className="absolute bottom-[40px] right-[40px] rounded bg-pia_dark_green px-[24px] py-[12px] text-white"
onClick={closeDialog}
>
Close
</button>
</div>
</div>
</div>
);
}
Loading
Loading