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

Course Tree for Staging Deployment #189

Merged
merged 60 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
1fe1bab
create component ReqTreeCard.tsx
aattiyah Dec 4, 2024
77252cd
create card for pre.post.corequisite tree
aattiyah Dec 4, 2024
99d6421
add comments
aattiyah Dec 4, 2024
a85c07f
import ReqTreeCard into CourseDetail.tsx
aattiyah Dec 4, 2024
90cdc26
add card to course details
aattiyah Dec 4, 2024
62757ec
change heading title of ReqTreeCard
aattiyah Dec 4, 2024
cad2d2d
add guiding comments
aattiyah Dec 4, 2024
80c0f9f
Add postrequisites fetching
mohamed-elzeni Dec 9, 2024
25d847a
Add postreqs to tree card
mohamed-elzeni Dec 9, 2024
8291273
Update ReqTreeCard to render the prerequisite list
akobaidan Dec 9, 2024
d282973
Create ReqTreeDetail to render the tree diagram
akobaidan Dec 9, 2024
4819f35
Update ReqTreeCard to build tree diagram and CourseDetail to fetch data
akobaidan Dec 9, 2024
5e36e55
Update ReqTreeDetail for UI enhancements and link courses to pages
akobaidan Dec 9, 2024
1c68659
Updated ReqTreeCard to display message if tree unavailable
akobaidan Dec 9, 2024
ebbb419
Merge pull request #1 from mohamed-elzeni/add-tree-diagram
akobaidan Dec 9, 2024
101a1f5
slight bug fixes and the addetion to an expanding pre req tree
Dec 9, 2024
e33d68b
adjusting the arrow feature aand adding a collapse
Dec 9, 2024
8c158e0
adjusting the arrow feature and fixing the bugs in it
Dec 9, 2024
b967bcc
fixing the bugs in it
Dec 9, 2024
d79cff6
add PostReqCourses for tree
aattiyah Dec 9, 2024
7dd2456
modify ReqTreeDetail for more postreq courses
aattiyah Dec 9, 2024
7c7c496
link ReqTreeCard for more postreq courses
aattiyah Dec 9, 2024
47fb612
properly adding the view more feature
Dec 9, 2024
14ede1c
fixing it slightly
Dec 9, 2024
380ff71
organize visualization of buttons
aattiyah Dec 9, 2024
554b2a9
organize the post-reqs of post-reqs
aattiyah Dec 9, 2024
3688001
remove redudant code
aattiyah Dec 9, 2024
b4bd11d
resolving conflicts
Dec 9, 2024
bf146fd
resolving conflicts
Dec 9, 2024
16babe7
resolving conflicts
Dec 10, 2024
e20f5e0
resolving conflicts
Dec 10, 2024
8395f96
resolving conflicts
Dec 10, 2024
79b60f6
resolving conflicts
Dec 10, 2024
09ecb36
resolving conflicts
Dec 10, 2024
0aaad61
resolving conflicts
Dec 10, 2024
79252e9
resolving conflicts
Dec 10, 2024
0b6b2b7
resolving minor bugs
Dec 10, 2024
b77dcde
resolving minor bugs
Dec 10, 2024
91a9c75
Merge branch 'main' into prereqtree
lhitmi Dec 10, 2024
913d0bd
add lines to the postreq nodes
aattiyah Dec 10, 2024
0b1a467
Merge pull request #2 from mohamed-elzeni/add-tree-diagram
aattiyah Dec 10, 2024
6fc686a
adding the link to the traversal of prereq
Dec 10, 2024
f6d22dd
adjusting imports and functionality
Dec 10, 2024
a0f01d5
adding corereq to the req card
Dec 10, 2024
426cd74
fixing bugs
Dec 10, 2024
e352eb1
updating changes
Dec 10, 2024
760978d
Merge branch 'main' into prereqtree
lhitmi Dec 10, 2024
6884b59
centered course req
Dec 10, 2024
6fbcbe7
updating changes
Dec 10, 2024
a75d38d
fixing rendering issue
Dec 10, 2024
21021d3
shifting the view more to the left side
Dec 11, 2024
5d45e93
Merge pull request #3 from mohamed-elzeni/prereqtree
lhitmi Dec 11, 2024
b2968ad
added parser function in utils.ts for prereqString
Dec 11, 2024
8b6ae54
added a function that get the postreqs and prereqs elements of the tree
Dec 11, 2024
175b792
added a new route for course/relations endpoint
Dec 11, 2024
a315151
fix coreqs placement
aattiyah Dec 11, 2024
85cf02e
fix pre-req placements
aattiyah Dec 11, 2024
f4a66ad
Merge pull request #4 from mohamed-elzeni/treefunction
mohamed-elzeni Dec 12, 2024
936a778
Enhance tree branches
mohamed-elzeni Dec 12, 2024
3ad0863
Minor requisite tree branches fix
mohamed-elzeni Dec 12, 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
3 changes: 2 additions & 1 deletion apps/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import morgan from "morgan";
import express, { ErrorRequestHandler } from "express";
import cors from "cors";
import { isUser } from "~/controllers/user";
import { getAllCourses, getCourseByID, getCourses, getFilteredCourses } from "~/controllers/courses";
import { getAllCourses, getCourseByID, getCourses, getFilteredCourses, getCourseRelations } from "~/controllers/courses";
import { getFCEs } from "~/controllers/fces";
import { getInstructors } from "~/controllers/instructors";
import { getGeneds } from "~/controllers/geneds";
Expand All @@ -21,6 +21,7 @@ app.route("/course/:courseID").get(getCourseByID);
app.route("/courses").get(getCourses);
app.route("/courses").post(isUser, getCourses);
app.route("/courses/all").get(getAllCourses);
app.route("/courses/relations").get(getCourseRelations);
app.route("/courses/search/").get(getFilteredCourses);
app.route("/courses/search/").post(isUser, getFilteredCourses);

Expand Down
68 changes: 67 additions & 1 deletion apps/backend/src/controllers/courses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
SingleOrArray,
singleToArray,
standardizeID,
parsePrereqString,
} from "~/util";
import { RequestHandler } from "express";
import db, { Prisma } from "@cmucourses/db";
Expand Down Expand Up @@ -74,7 +75,28 @@ export const getCourses: RequestHandler<
schedules: fromBoolLiteral(req.query.schedules),
},
});
res.json(courses);

const coursesWithPostreqs = await Promise.all(courses.map(async (course) => {
const postreqs = await db.courses.findMany({
where: {
prereqs: {
has: course.courseID,
},
},
select: {
courseID: true,
},
});

const postreqIDs = postreqs.map(postreq => postreq.courseID);

return {
...course,
postreqs: postreqIDs,
};
}));

res.json(coursesWithPostreqs);
} catch (e) {
next(e);
}
Expand Down Expand Up @@ -272,3 +294,47 @@ export const getAllCourses: RequestHandler<
res.json(allCoursesEntry.allCourses);
}
};

//adding function to get elements of the postreqs and prereqs tree
export const getCourseRelations: RequestHandler = async (req, res, next) => {
try {
// Fetch all courses with their prereq strings
const courses = await db.courses.findMany({
select: {
courseID: true,
prereqString: true,
},
});

const courseRelations: Record<
string,
{ prereqs: string[][]; postreqs: string[] }
> = {};

// Populate the prereqs and initialize postreqs
courses.forEach((course) => {
const parsedPrereqs = course.prereqString
? parsePrereqString(course.prereqString)
: [];
courseRelations[course.courseID] = {
prereqs: parsedPrereqs,
postreqs: [],
};
});

// Populate the postreqs
for (const course of courses) {
const prereqGroups = courseRelations[course.courseID]?.prereqs || [];
prereqGroups.flat().forEach((prereqID) => {
if (courseRelations[prereqID]) {
courseRelations[prereqID].postreqs.push(course.courseID);
}
});
};

res.json(courseRelations);
} catch (e) {
next(e);
}
};

8 changes: 7 additions & 1 deletion apps/backend/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,10 @@ export type PrismaReturn<PrismaFnType extends (...args: any) => any> =
Awaited<ReturnType<PrismaFnType>>;

export type ElemType<ArrayType extends readonly unknown[]> =
ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
ArrayType extends readonly (infer ElementType)[] ? ElementType : never;

export function parsePrereqString(prereqString: string): string[][] {
const normalized = prereqString.replace(/\s+/g, "").replace(/[()]/g, ""); // Remove whitespace and parentheses
const andGroups = normalized.split("and"); // Split by AND groups
return andGroups.map((group) => group.split("or")); // Split each AND group into OR relationships
}
1 change: 1 addition & 0 deletions apps/frontend/src/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface Session {
export interface Course {
prereqs: string[];
prereqString: string;
postreqs: string[];
coreqs: string[];
crosslisted: string[];
name: string;
Expand Down
22 changes: 18 additions & 4 deletions apps/frontend/src/components/CourseDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,40 @@ import { useFetchFCEInfoByCourse } from "~/app/api/fce";
import { SchedulesCard } from "./SchedulesCard";
import { FCECard } from "./FCECard";
import { useFetchCourseInfo } from "~/app/api/course";
import ReqTreeCard from "./ReqTreeCard";

type Props = {
courseID: string;
};

const CourseDetail = ({ courseID }: Props) => {
const { data: { fces } = {} } = useFetchFCEInfoByCourse(courseID);
const { data: { schedules } = {} } = useFetchCourseInfo(courseID);
const { data: info } = useFetchCourseInfo(courseID);

if (!info) {
return <div>Loading...</div>;
}

return (
<div className="m-auto space-y-4 p-6">
<CourseCard courseID={courseID} showFCEs={false} showCourseInfo={true} />

{fces && <FCECard fces={fces} />}
{schedules && (
{info.schedules && (
<SchedulesCard
scheduleInfos={filterSessions([...schedules]).sort(compareSessions)}
scheduleInfos={filterSessions([...info.schedules]).sort(compareSessions)}
/>
)}
{info.prereqs && info.postreqs && (
<ReqTreeCard
courseID={courseID}
prereqs={info.prereqs}
postreqs={info.postreqs}
coreqs={info.coreqs} // Ensure coreqs are passed here
/>
)}
</div>
);
};

export default CourseDetail;
export default CourseDetail;
136 changes: 136 additions & 0 deletions apps/frontend/src/components/PostReqCourses.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React from "react";
import Link from "next/link"; // Import Next.js Link component
import { useFetchCourseInfo } from "~/app/api/course";

interface TreeNode {
courseID: string;
postreqs?: TreeNode[];
}

interface Props {
courseID: string;
}

export const PostReqCourses = ({ courseID }: Props) => {
const { isPending: isCourseInfoPending, data: info } = useFetchCourseInfo(courseID);

if (isCourseInfoPending || !info) {
return null;
}

// Recursive function to render only the child branches
const renderTree = (nodes: TreeNode[]) => {
return (
<div style={{ display: "flex", flexDirection: "column" }}>
{nodes.map((node) => (
<div
key={node.courseID}
style={{
display: "flex",
alignItems: "center",
}}
>

{/* Half vertical line for the first prereq in the list */}
{nodes && nodes.length > 1 && nodes.indexOf(node) === 0 && (
<div
style={{
width: "1px",
height: "20px",
backgroundColor: "#d1d5db",
marginTop: "20px",
}}
></div>
)}

{/* Normal vertical Line connector */}
{nodes && nodes.length > 1 && nodes.indexOf(node) !== 0 && nodes.indexOf(node) !== nodes.length - 1 && (
<div
style={{
width: "1px",
backgroundColor: "#d1d5db",
alignSelf: "stretch",
}}
></div>
)}

{/* Half vertical line for the last prereq in the list */}
{nodes && nodes.length > 1 && nodes.indexOf(node) === nodes.length - 1 && (
<div
style={{
width: "1px",
height: "20px",
backgroundColor: "#d1d5db",
marginBottom: "20px",
}}
></div>
)}

{/* Line connector */}
<div
style={{
width: "20px",
height: "1px",
backgroundColor: "#d1d5db",
}}
></div>

{/* Course ID button */}
<button
onClick={() => window.location.href = `/course/${node.courseID}`}
style={{
fontWeight: "normal",
textAlign: "center",
padding: "5px 10px",
fontSize: "14px",
backgroundColor: "#f9fafb",
color: "#111827",
border: "1px solid #d1d5db",
borderRadius: "4px",
boxShadow: "0px 2px 4px rgba(0, 0, 0, 0.05)",
cursor: "pointer",
textDecoration: "none",
minWidth: "100px", // Ensure consistent width
display: "inline-block",
marginTop: "2px",
marginBottom: "2px",
}}
>
{node.courseID}
</button>

{/* Render child nodes recursively */}
{node.postreqs && renderTree(node.postreqs)}
</div>
))}
</div>
);
};

// Transform fetched data into a tree structure excluding the parent node
const childNodes: TreeNode[] = info.postreqs?.map((postreq: string) => ({
courseID: postreq,
})) || [];

return (
<div>
{childNodes.length > 0 ? (
renderTree(childNodes)
) : (
<div
style={{
fontStyle: "italic",
color: "#000000",
textAlign: "center",
fontSize: "14px",
fontWeight: "bold",
}}
>
No further post-requisites
</div>
)}
</div>
);
};

export default PostReqCourses;
Loading
Loading