Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1,898 changes: 1,760 additions & 138 deletions my-app/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions my-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"pdfjs-dist": "^5.1.91",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-infinite-scroll-component": "^6.1.0",
"react-router-dom": "^7.4.0",
"reactflow": "^11.11.4",
"tailwindcss": "^4.0.17"
Expand Down
5 changes: 3 additions & 2 deletions my-app/src/presenters/ListViewPresenter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const ListViewPresenter = observer(({ model }) => {
const addFavourite = (course) => {
model.addFavourite(course);
}
const removeFavourite = (course) => {
const removeFavourite = (course) => {
model.removeFavourite(course);
}
const handleFavouriteClick = (course) => {
Expand All @@ -30,6 +30,7 @@ const ListViewPresenter = observer(({ model }) => {
favouriteCourses={model.favourites}
addFavourite={addFavourite}
removeFavourite={removeFavourite}
handleFavouriteClick={handleFavouriteClick}
isOpen={isPopupOpen} onClose={() => setIsPopupOpen(false)}
course={selectedCourse}
prerequisiteTree={preP}
Expand All @@ -46,7 +47,7 @@ const ListViewPresenter = observer(({ model }) => {
isPopupOpen={isPopupOpen}
setIsPopupOpen={setIsPopupOpen}
setSelectedCourse={setSelectedCourse}
popUp={popup}
popup={popup}
handleFavouriteClick={handleFavouriteClick}

/>;
Expand Down
44 changes: 40 additions & 4 deletions my-app/src/presenters/SearchbarPresenter.jsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,68 @@
import React from 'react';
import { observer } from "mobx-react-lite";
import { useState } from 'react';
import CoursePagePopup from '../views/Components/CoursePagePopup.jsx';
import PrerequisitePresenter from './PrerequisitePresenter.jsx';
import SearchbarView from "../views/SearchbarView.jsx";

const SearchbarPresenter = observer(({ model }) => {
const searchCourses = (query) => {
const searchResults = model.courses.filter(course =>
course.code.toLowerCase() === query.toLowerCase() ||
course.code.toLowerCase().includes(query.toLowerCase()) ||
course.name.toLowerCase().includes(query.toLowerCase()) ||
course.description.toLowerCase().includes(query.toLowerCase())
);
model.setCurrentSearch(searchResults);
}

const addFavourite = (course) => {
model.addFavourite(course);
}
const removeFavourite = (course) => {
model.removeFavourite(course);
}
const handleFavouriteClick = (course) => {
if (model.favourites.some(fav => fav.code === course.code)) {
model.removeFavourite(course);
} else {
model.addFavourite(course);
}
};


function removeAllFavourites(){
function removeAllFavourites() {
model.setFavourite([]);
}

const [isPopupOpen, setIsPopupOpen] = useState(false);
const [selectedCourse, setSelectedCourse] = useState(null);
const preP = <PrerequisitePresenter model={model} selectedCourse={selectedCourse} />

const popup = <CoursePagePopup
favouriteCourses={model.favourites}
addFavourite={addFavourite}
removeFavourite={removeFavourite}
handleFavouriteClick={handleFavouriteClick}
isOpen={isPopupOpen} onClose={() => setIsPopupOpen(false)}
course={selectedCourse}
prerequisiteTree={preP} />




return (
<SearchbarView
model={model}
searchCourses={searchCourses}
favouriteCourses = {model.favourites}
removeFavourite={removeFavourite}
favouriteCourses={model.favourites}
removeAllFavourites={removeAllFavourites}
addFavourite={addFavourite}
removeFavourite={removeFavourite}
isPopupOpen={isPopupOpen}
setIsPopupOpen={setIsPopupOpen}
setSelectedCourse={setSelectedCourse}
popup={popup}
handleFavouriteClick={handleFavouriteClick}
/>
);
});
Expand Down
204 changes: 101 additions & 103 deletions my-app/src/views/Components/CoursePagePopup.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import React, { useEffect, useRef } from "react";
import React, { useEffect, useRef } from 'react';

function CoursePagePopup({
favouriteCourses,
addFavourite,
removeFavourite,
isOpen,
onClose,
course,
prerequisiteTree,
reviewPresenter,
favouriteCourses,
addFavourite,
removeFavourite,
handleFavouriteClick,
isOpen,
onClose,
course,
prerequisiteTree
}) {
const treeRef = useRef(null);
useEffect(() => {
const handleKeyDown = (event) => {
if (event.key === "Escape") {
onClose();
}
};
const treeRef = useRef(null);

useEffect(() => {
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
onClose();
}
};

if (isOpen) {
window.addEventListener("keydown", handleKeyDown);
Expand All @@ -27,96 +28,93 @@ function CoursePagePopup({
};
}, [isOpen, onClose]);

const handleFavouriteClick = (course) => {
if (favouriteCourses.some((fav) => fav.code === course.code)) {
removeFavourite(course);
} else {
addFavourite(course);
}
};
const handleTreeClick = () => {
if (treeRef.current) {
treeRef.current.focus(); // gives it focus
}
};

const handleTreeClick = () => {
if (treeRef.current) {
treeRef.current.focus(); // gives it focus
}
};
if (!isOpen || !course) return null; // Don't render if not open or course not selected

return (
<div
className="fixed backdrop-blur-sm inset-0 bg-transparent flex justify-end z-50"
onClick={onClose}
>
<div
className="bg-indigo-300/75 backdrop-blur-lg h-full w-3/4 flex flex-col overflow-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="flex-1">
<div className="px-10 py-10 md:px-20 md:py-16 text-slate-900 space-y-12 font-sans">
{/* Course Title Section */}
<div>
<h2 className="text-5xl font-extrabold text-[#2e2e4f] ">
<span className="text-violet-700">{course.code}</span> - {course.name}
<span className="ml-4 text-lg text-violet-700 whitespace-nowrap">({course.credits} Credits)</span>
</h2>
<div className="my-6 h-1.5 w-full bg-violet-500"></div>
</div>
<div>
<button
className="text-yellow-100 bg-yellow-400 cursor-pointer"
onClick={(e) => {
e.stopPropagation(); // prevent popup from opening
// Pass the full course object now instead of course.code:
handleFavouriteClick(course);
}}
>
{favouriteCourses.some(fav => fav.code === course.code)
? 'Remove from Favourites'
: 'Add to Favourites'}
</button>
</div>

{/* Description Section */}
<div>
<h3 className="text-2xl font-bold text-[#2e2e4f] mb-0.5">Course Description</h3>
<div className="mb-3 h-0.5 w-full bg-violet-500"></div>
<div
className="text-lg leading-8 text-[#2e2e4f] font-semibold tracking-wide prose prose-slate max-w-full"
dangerouslySetInnerHTML={{ __html: course.description }}
/>
</div>

if (!isOpen || !course) return null; // Don't render if not open or course not selected
return (
<div
className="fixed backdrop-blur-sm inset-0 bg-transparent flex justify-end z-50"
onClick={onClose}
>
<div
className="bg-indigo-300/75 backdrop-blur-lg h-full w-3/4 flex flex-col overflow-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="flex-1">
<div className="px-10 py-10 md:px-20 md:py-16 text-slate-900 space-y-12 font-sans">
{/* Course Title Section */}
<div>
<h2 className="text-5xl font-extrabold text-[#2e2e4f] ">
<span className="text-violet-700">{course.code}</span> -{" "}
{course.name}
<span className="ml-4 text-lg text-violet-700 whitespace-nowrap">
({course.credits} Credits)
</span>
</h2>
<div className="my-6 h-1.5 w-full bg-violet-500"></div>
</div>
<div>
<button
className="text-yellow-500 bg-yellow-400 cursor-pointer"
onClick={(e) => {
e.stopPropagation(); // prevent popup from opening
handleFavouriteClick(course.code);
}}
>
{favouriteCourses.includes(course.code)
? "Remove from Favourites"
: "Add to Favourites"}
</button>
</div>
{/* Prerequisite Graph Tree Section */}
<div>

{/* Description Section */}
<div>
<h3 className="text-2xl font-bold text-[#2e2e4f] mb-0.5">
Course Description
</h3>
<div className="mb-3 h-0.5 w-full bg-violet-500"></div>
<div
className="text-lg leading-8 text-[#2e2e4f] font-semibold tracking-wide prose prose-slate max-w-full"
dangerouslySetInnerHTML={{ __html: course.description }}
/>
</div>
<h3 className="text-2xl font-semibold text-[#2e2e4f] mb-0.5">
Prerequisite Graph Tree
</h3>
<div className="mb-4 h-0.5 w-full bg-violet-500"></div>
<div
className="bg-indigo-300/50 outline-none focus:outline-none focus:ring-2 focus:ring-violet-600 rounded-lg transition-shadow"
ref={treeRef}
onClick={handleTreeClick}
tabIndex={0} // allows the div to receive focus
>
{prerequisiteTree}
</div>
</div>

{/* Prerequisite Graph Tree Section */}
<div>
<h3 className="text-2xl font-semibold text-[#2e2e4f] mb-0.5">
Prerequisite Graph Tree
</h3>
<div className="mb-4 h-0.5 w-full bg-violet-500"></div>
<div
className="bg-indigo-300/50 outline-none focus:outline-none focus:ring-2 focus:ring-violet-600 rounded-lg transition-shadow"
ref={treeRef}
onClick={handleTreeClick}
tabIndex={0} // allows the div to receive focus
>
{prerequisiteTree}
</div>
</div>
{reviewPresenter}
</div>
</div>
<button
onClick={onClose}
className="px-4 py-2 bg-violet-500 text-white"
>
Close
</button>
</div>
</div>
);
}
{/* Reviews Section */}
<div>
<h3 className="text-2xl font-semibold text-[#2e2e4f] mb-0.5">Reviews</h3>
<div className="mb-4 h-0.5 w-full bg-violet-500"></div>
<p className="text-lg text-slate-700 leading-7">
Here would be some reviews of the course...
</p>
</div>
</div>
</div>
<button
onClick={onClose}
className="px-4 py-2 bg-violet-500 text-white"
>
Close
</button>
</div>
</div>
);

export default CoursePagePopup;
56 changes: 35 additions & 21 deletions my-app/src/views/Components/FavouriteDropdown.jsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
import React from 'react';

function FavouritesDropdown(props) {
console.log('Popup:', props.popup);
console.log('isPopupOpen:', props.isPopupOpen);
return (
<div className="absolute mt-2 w-48 bg-white border border-solid border-black rounded-lg z-50 overflow-y-auto">
<div className="absolute mt-2 w-48 bg-white border border-solid border-black rounded-lg z-50 overflow-y-auto max-h-60">
{props.favouriteCourses.length > 0 ? (

props.favouriteCourses.map(course => (
<div
key={course.code}
className="p-2 flex justify-between items-center w-full border border-solid border-black">
<p
className="text-black">
{course.name}
</p>
<button
className="text-red-500 cursor-pointer"
onClick={() => props.removeFavourite(course)}>
X
</button>
</div>
))


) : (
props.favouriteCourses.map(course => (
<div
onClick={() => {
console.log('Clicked:', course); // check browser console
props.setSelectedCourse(course);
props.setIsPopupOpen(true); // Ensure popup state is updated
}}
key={course.code}
className="p-2 flex justify-between items-center w-full border border-solid border-black">
<p className="text-black">{course.name}</p>
<button
className="text-red-500 cursor-pointer"
onClick={(e) => {
e.stopPropagation(); // Prevent parent click
props.removeFavourite(course);
}}>
X
</button>
</div>
))
) : (
<div className="p-2 text-[#000061]">
No favourites
</div>
)}
{props.favouriteCourses.length > 0 ? <button onClick={props.removeAllFavourites}>Clear Favourites</button> : ""}
{/* Ensure popup is conditionally rendered */}
<div style={{ position: 'relative', zIndex: 1000 }}>
{props.isPopupOpen && props.popup}
</div>
{props.favouriteCourses.length > 0 && (
<button
onClick={props.removeAllFavourites}
className="mt-2 text-red-500">
Clear Favourites
</button>
)}
</div>
);
}
Expand Down
Loading
Loading