diff --git a/apps/frontend/src/app/GradeDistributions/index.tsx b/apps/frontend/src/app/GradeDistributions/index.tsx index 7b3f547f1..aec010b7c 100644 --- a/apps/frontend/src/app/GradeDistributions/index.tsx +++ b/apps/frontend/src/app/GradeDistributions/index.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { useApolloClient } from "@apollo/client"; +import { useSearchParams } from "react-router-dom"; import { Bar, BarChart, @@ -64,85 +65,134 @@ import styles from "./GradeDistributions.module.scss"; // }, // ]; +interface Input { + subject: string; + courseNumber: string; + year?: number; + semester?: Semester; + givenName?: string; + familyName?: string; +} + interface Output { color: string; gradeDistribution: GradeDistribution; - input: { - subject: string; - courseNumber: string; - number?: string; - year?: number; - semester?: Semester; - givenName?: string; - familyName?: string; - }; + input: Input; } -const input = [ - { - subject: "COMPSCI", - courseNumber: "61B", - }, - { - subject: "COMPSCI", - courseNumber: "61B", - number: "001", - year: 2024, - semester: "Spring", - }, - { - subject: "COMPSCI", - courseNumber: "61B", - year: 2024, - semester: "Spring", - }, - { - subject: "COMPSCI", - courseNumber: "61A", - year: 2024, - semester: "Spring", - givenName: "John", - familyName: "DeNero", - }, - // { - // subject: "COMPSCI", - // courseNumber: "61A", - // givenName: "Joshua", - // familyName: "Hug", - // }, -]; +// const input = [ +// { +// subject: "COMPSCI", +// courseNumber: "61B", +// }, +// { +// subject: "COMPSCI", +// courseNumber: "61B", +// year: 2024, +// semester: "Spring", +// }, +// { +// subject: "COMPSCI", +// courseNumber: "61B", +// year: 2024, +// semester: "Spring", +// }, +// { +// subject: "COMPSCI", +// courseNumber: "61A", +// year: 2024, +// semester: "Spring", +// givenName: "John", +// familyName: "DeNero", +// }, +// ]; export default function GradeDistributions() { const client = useApolloClient(); + const [searchParams] = useSearchParams(); + const [loading, setLoading] = useState(false); + const [outputs, setOutputs] = useState(null); + + const inputs = useMemo( + () => + searchParams.getAll("input").reduce((acc, input) => { + const output = input.split(";"); - const [output, setOutput] = useState(null); + // Filter out invalid inputs + if (output.length < 2) return acc; + + // COMPSCI;61B + if (output.length < 4) { + const parsedInput: Input = { + subject: output[0], + courseNumber: output[1], + }; + + return acc.concat(parsedInput); + } + + // Filter out invalid inputs + if (!["T", "P"].includes(output[2])) return acc; + + // COMPSCI;61B;T;2024:Spring;John:DeNero, COMPSCI;61B;T;2024:Spring + const term = output[output[2] === "T" ? 3 : 4]?.split(":"); + + // COMPSCI;61B;P;John:DeNero;2024:Spring, COMPSCI;61B;P;John:DeNero + const professor = output[output[2] === "T" ? 4 : 3]?.split(":"); + + const parsedInput: Input = { + subject: output[0], + courseNumber: output[1], + year: parseInt(term?.[0]), + semester: term?.[1] as Semester, + familyName: professor?.[0], + givenName: professor?.[1], + }; + + return acc.concat(parsedInput); + }, [] as Input[]), + [searchParams] + ); const initialize = useCallback(async () => { setLoading(true); + // TODO: Fetch course data + const responses = await Promise.all( - input.map((variables) => - client.query({ - query: READ_GRADE_DISTRIBUTION, - variables, - }) - ) + inputs.map(async (variables) => { + try { + const response = await client.query({ + query: READ_GRADE_DISTRIBUTION, + variables, + }); + + return response; + } catch (error) { + // TODO: Handle errors + + return null; + } + }) ); - const output = responses.map( - (response, index) => - ({ - color: colors[Math.floor(Math.random() * colors.length)], - gradeDistribution: response.data.grade, - input: input[index], - }) as Output + const output = responses.reduce( + (acc, response, index) => + response + ? acc.concat({ + color: colors[Math.floor(Math.random() * colors.length)], + gradeDistribution: response!.data.grade, + input: inputs[index], + }) + : acc, + [] as Output[] ); - setOutput(output); + setOutputs(output); setLoading(false); - }, []); + }, [inputs]); useEffect(() => { initialize(); @@ -150,7 +200,7 @@ export default function GradeDistributions() { const data = useMemo( () => - output?.reduce( + outputs?.reduce( (acc, output, index) => { output.gradeDistribution.distribution.forEach((grade) => { const column = acc.find((item) => item.letter === grade.letter); @@ -172,11 +222,9 @@ export default function GradeDistributions() { [key: number]: number; }[] ), - [output] + [outputs] ); - console.log(data); - return (
@@ -187,7 +235,12 @@ export default function GradeDistributions() { ) : (
- + - {output?.map((_, index) => ( - + {outputs?.map((_, index) => ( + ))} - + - {output?.map((_, index) => ( + {outputs?.map((_, index) => (
- {output?.map((_, index) => ( + {outputs?.map((_, index) => ( - + - + ))} diff --git a/apps/frontend/src/components/Layout/PinsDrawer/PinsDrawer.module.scss b/apps/frontend/src/components/Layout/PinsDrawer/PinsDrawer.module.scss index 98b6e8faf..9993603e2 100644 --- a/apps/frontend/src/components/Layout/PinsDrawer/PinsDrawer.module.scss +++ b/apps/frontend/src/components/Layout/PinsDrawer/PinsDrawer.module.scss @@ -17,6 +17,16 @@ } } +body[data-theme="dark"] .overlay { + background: linear-gradient(to left, rgb(0 0 0 / 50%) 384px, transparent); +} + +@media (prefers-color-scheme: dark) { + body:not([data-theme]) .overlay { + background: linear-gradient(to left, rgb(0 0 0 / 50%) 384px, transparent); + } +} + .content { width: 384px; border-left: 1px solid var(--border-color); diff --git a/apps/frontend/src/hooks/useDynamicYAxisWidth.ts b/apps/frontend/src/hooks/useDynamicYAxisWidth.ts new file mode 100644 index 000000000..d32e06ca3 --- /dev/null +++ b/apps/frontend/src/hooks/useDynamicYAxisWidth.ts @@ -0,0 +1,46 @@ +import { useCallback, useState } from "react"; + +const RECHART_CERTESIAN_AXIS_TICK_VALUE_SELECTOR = `.recharts-cartesian-axis-tick-value[orientation="left"], +.recharts-cartesian-axis-tick-value[orientation="right"]`; + +const useDynamicYAxisWidth = (tickMargin = 12) => { + const [width, setWidth] = useState(); + + const handleRef = useCallback( + (ref: unknown | null) => { + // @ts-expect-error - LineChart cannot be typed + if (!ref?.container) return; + + // @ts-expect-error - LineChart cannot be typed + const tickValueElements = ref.container.querySelectorAll( + RECHART_CERTESIAN_AXIS_TICK_VALUE_SELECTOR + ); + + const highestWidth = [...tickValueElements] + .map((el) => { + const boundingRect = el.getBoundingClientRect(); + + if (!boundingRect?.width) return 0; + + return boundingRect.width; + }) + .reduce((accumulator, value) => { + if (accumulator < value) { + return value; + } + + return accumulator; + }, 0); + + setWidth(highestWidth + tickMargin); + }, + [tickMargin] + ); + + return { + width, + handleRef, + }; +}; + +export default useDynamicYAxisWidth;