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

Create parent attendance display #130 #132

Merged
merged 3 commits into from
Feb 24, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
7 changes: 6 additions & 1 deletion components/Profile/ParentProfile/ParentProfileView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import axios from "axios";
import { ConnectStudentProfile } from "./ConnectStudentProfile";
import { ConnectedStudentDisplay } from "./ConnectedStudentDisplay";
import { AddStudentModal } from "./AddStudentModal";
import { StudentClasses } from "./StudentClasses";

// component that renders the admin/teacher profile page
type ParentProfileViewProps = {
Expand Down Expand Up @@ -126,7 +127,11 @@ const ParentProfileView: React.FC<ParentProfileViewProps> = ({ otherUser }) => {
renderStudentCards[currentStudentIndex]
)}
</div>
<div className={styles.rightPanel}></div>
<div className={styles.rightPanel}>
<StudentClasses
id={allStudents[currentStudentIndex] ? allStudents[currentStudentIndex].id : ""}
/>
</div>
</div>
</div>
</div>
Expand Down
94 changes: 94 additions & 0 deletions components/Profile/ParentProfile/StudentClasses.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
.spacer {
height: 30px;
}

.accordion {
height: 100%;
font-family: "Inter";
font-style: normal;
font-weight: 700;
font-size: 24px;
line-height: 29px;
padding: 1em 1.5em;
overflow-y: auto;
overflow-x: hidden;
color: #000000;
}

.item {
font-weight: 600;
font-size: 18px;
line-height: 22px;
background: #f3f3f3;
}

.item + .item {
margin-top: 2em;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}

.button {
cursor: pointer;
padding: 18px;
width: 100%;
text-align: left;
border: none;
outline: none;
border-radius: 8px;
box-shadow: 0px 5px 10px -11px;
}

.button:hover {
background-color: #ddd;
}

.button:before {
float: right;
content: "▼";
height: 10px;
width: 10px;
margin-right: 12px;
}

.button[aria-expanded="true"]::before,
.button[aria-selected="true"]::before {
margin-top: 12px;
margin-right: 4px;
transform: rotate(180deg);
}

.panel {
font-size: 15px;
line-height: 18px;
padding: 20px;
animation: fadein 0.35s ease-in;
border-radius: 0px 0px 8px 8px;
}

.panel a {
color: blue;
}

/* -------------------------------------------------- */
/* ---------------- Animation part ------------------ */
/* -------------------------------------------------- */

@keyframes fadein {
0% {
opacity: 0;
}

100% {
opacity: 1;
}
}

.info {
display: inline-block;
vertical-align: super;
margin-bottom: 25px;
}

.info:last-child {
margin-bottom: 0px;
}
110 changes: 110 additions & 0 deletions components/Profile/ParentProfile/StudentClasses.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Class } from "../../../models/";
import { APIContext } from "../../../context/APIContext";
import styles from "./StudentClasses.module.css";
import React, { useContext, useEffect, useState } from "react";
import RRule from "rrule";
import {
Accordion,
AccordionItem,
AccordionItemHeading,
AccordionItemButton,
AccordionItemPanel,
} from "react-accessible-accordion";
import AccessTime from "@mui/icons-material/AccessTime";
import CalendarMonth from "@mui/icons-material/CalendarMonth";
import School from "@mui/icons-material/School";

type StudentClassesProps = {
id: string;
};

const getFormattedTime: string = (time: string) => {
const hours24 = parseInt(time.substring(0, 2));
const hours = ((hours24 + 11) % 12) + 1;
const hoursText = hours < 10 ? "0" + hours.toString() : hours.toString();
const amPm = hours24 > 11 ? " pm" : " am";
const minutes = time.substring(3, 5);

return hoursText + ":" + minutes + amPm;
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just wondering, is there no better way to do this? this forces us to keep the exact same formatting through the entire app. Maybe luxon would help here? B/c now we have to make sure the formatting is correct

Copy link
Contributor Author

@willji0023 willji0023 Feb 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree I think something like luxon is a cleaner option, which should also address your other comment. Added in new iteration


const monthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];

const StudentClasses: React.FC<StudentClassesProps> = ({ id }) => {
const api = useContext(APIContext);
const [classes, setClasses] = useState<Class[]>([]);

useEffect(() => {
(async () => {
await getClasses();
})();
}, [id]);

const getClasses = async (): Promise<void> => {
if (id) {
const classesByUser = await api.getClassesByUser(id);
setClasses(classesByUser);
} else {
setClasses([]);
}
};

return (
<>
<Accordion className={styles.accordion} allowMultipleExpanded={true} allowZeroExpanded={true}>
Classes
<div className={styles.spacer} />
{classes.map((Class) => (
<AccordionItem className={styles.item} key={Class.eventInformationId + id}>
<AccordionItemHeading>
<AccordionItemButton className={styles.button}>
{Class.name}{" "}
{Class.minLevel == Class.maxLevel
? Class.minLevel
: Class.minLevel + "-" + Class.maxLevel}
</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel className={styles.panel}>
<AccessTime />{" "}
<div className={styles.info}>
{getFormattedTime(Class.startTime)} - {getFormattedTime(Class.endTime)}
</div>
<br />
<CalendarMonth />{" "}
<div className={styles.info}>
{RRule.fromString(Class.rrstring).toText().charAt(0).toUpperCase() +
RRule.fromString(Class.rrstring).toText().slice(1) +
", start " +
monthNames[RRule.fromString(Class.rrstring).options.dtstart.getMonth()] +
" " +
RRule.fromString(Class.rrstring).options.dtstart.getDate() +
", " +
RRule.fromString(Class.rrstring).options.dtstart.getFullYear()}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a comment about what this is doing - its kinda hard to understand

</div>
<br />
<School />{" "}
<div className={styles.info}>
<a href={"/class/" + Class.eventInformationId}>Class Link</a>
</div>
</AccordionItemPanel>
</AccordionItem>
))}
</Accordion>
</>
);
};

export { StudentClasses };
6 changes: 6 additions & 0 deletions context/LeagueAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ class LeagueAPI {
const res = await this.client.get("api/class", { params: { userId: userId } });
return res.data;
}

async getClassesByUser(userId: string): Promise<Class[]> {
const res = await this.client.get(`api/users/${userId}/classes`);
return res.data;
}

async deleteClassEvent(userId: string): Promise<Class> {
const res = await this.client.delete(`api/events/class/${userId}`);
return res.data;
Expand Down
48 changes: 47 additions & 1 deletion lib/database/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ type ClassWithoutTeacherInfo = {
endTime: string;
language: string;
};
const ClassWithoutTeacherInfoSchema = t.type({
name: t.string,
eventInformationId: t.string,
minLevel: t.number,
maxLevel: t.number,
rrstring: t.string,
startTime: t.string,
endTime: t.string,
language: t.string,
});
const ClassWithoutTeacherInfoArraySchema = array(ClassWithoutTeacherInfoSchema);

const createClass = async (
eventInformationId: string,
Expand Down Expand Up @@ -196,4 +207,39 @@ const getAllClasses = async (): Promise<Class[]> => {
return classesArray;
};

export { createClass, getClass, updateClass, getAllClasses };
const getClassesByUser = async (id: string): Promise<Class[]> => {
const query = {
text:
"SELECT cl.min_level, cl.max_level, cl.rrstring, cl.start_time, cl.end_time, cl.language, cl.event_information_id, e.name " +
"FROM ((event_information e INNER JOIN classes cl ON e.id = cl.event_information_id) " +
"INNER JOIN commitments ON commitments.event_information_id = e.id) " +
"WHERE commitments.user_id = $1",
values: [id],
};
const res = await client.query(query);
let classesWithoutTeacher: TypeOf<typeof ClassWithoutTeacherInfoArraySchema>;
try {
classesWithoutTeacher = await decode(ClassWithoutTeacherInfoArraySchema, res.rows);
} catch (e) {
throw Error("Fields returned incorrectly from database");
}

const classesArray: Class[] = [];
classesWithoutTeacher.forEach((singleClass) => {
const currClass = {
name: singleClass.name,
eventInformationId: singleClass.eventInformationId,
minLevel: singleClass.minLevel,
maxLevel: singleClass.maxLevel,
rrstring: singleClass.rrstring,
startTime: singleClass.startTime,
endTime: singleClass.endTime,
language: singleClass.language,
teachers: [],
};
classesArray.push(currClass);
});
return classesArray;
};

export { createClass, getClass, updateClass, getAllClasses, getClassesByUser };
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"pino-pretty": "^9.1.1",
"postgres-migrations": "^5.3.0",
"react": "17.0.2",
"react-accessible-accordion": "^5.0.0",
"react-color": "^2.19.3",
"react-datetime-picker": "^3.4.3",
"react-dom": "17.0.2",
Expand Down
Loading