Skip to content

Commit 37f8782

Browse files
LSKprkexanaDinoxh
authored
Styling (#45)
* Course page merged with main * some styling changes * more styling * more styling * added fav button on the popup almost works * small fixes * popup fav button works properly now * fixed searchbar, added inf scroll * clicking fav item opens a popup * whitespace-nowrap * fix * mb-4 in the course ListView --------- Co-authored-by: Kacper Lisik <lisik@kth.se> Co-authored-by: Dean Tsankov <deotsts@gmail.com> Co-authored-by: Sami Al Saati <sami.alsaati@hotmail.com>
1 parent 52dc7e9 commit 37f8782

File tree

8 files changed

+2041
-335
lines changed

8 files changed

+2041
-335
lines changed

my-app/package-lock.json

Lines changed: 1760 additions & 138 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

my-app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"pdfjs-dist": "^5.1.91",
2020
"react": "^19.0.0",
2121
"react-dom": "^19.0.0",
22+
"react-infinite-scroll-component": "^6.1.0",
2223
"react-router-dom": "^7.4.0",
2324
"reactflow": "^11.11.4",
2425
"tailwindcss": "^4.0.17"

my-app/src/presenters/ListViewPresenter.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const ListViewPresenter = observer(({ model }) => {
1010
const addFavourite = (course) => {
1111
model.addFavourite(course);
1212
}
13-
const removeFavourite = (course) => {
13+
const removeFavourite = (course) => {
1414
model.removeFavourite(course);
1515
}
1616
const handleFavouriteClick = (course) => {
@@ -30,6 +30,7 @@ const ListViewPresenter = observer(({ model }) => {
3030
favouriteCourses={model.favourites}
3131
addFavourite={addFavourite}
3232
removeFavourite={removeFavourite}
33+
handleFavouriteClick={handleFavouriteClick}
3334
isOpen={isPopupOpen} onClose={() => setIsPopupOpen(false)}
3435
course={selectedCourse}
3536
prerequisiteTree={preP}
@@ -46,7 +47,7 @@ const ListViewPresenter = observer(({ model }) => {
4647
isPopupOpen={isPopupOpen}
4748
setIsPopupOpen={setIsPopupOpen}
4849
setSelectedCourse={setSelectedCourse}
49-
popUp={popup}
50+
popup={popup}
5051
handleFavouriteClick={handleFavouriteClick}
5152

5253
/>;

my-app/src/presenters/SearchbarPresenter.jsx

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,68 @@
11
import React from 'react';
22
import { observer } from "mobx-react-lite";
3+
import { useState } from 'react';
4+
import CoursePagePopup from '../views/Components/CoursePagePopup.jsx';
5+
import PrerequisitePresenter from './PrerequisitePresenter.jsx';
36
import SearchbarView from "../views/SearchbarView.jsx";
47

58
const SearchbarPresenter = observer(({ model }) => {
69
const searchCourses = (query) => {
710
const searchResults = model.courses.filter(course =>
8-
course.code.toLowerCase() === query.toLowerCase() ||
11+
course.code.toLowerCase().includes(query.toLowerCase()) ||
912
course.name.toLowerCase().includes(query.toLowerCase()) ||
1013
course.description.toLowerCase().includes(query.toLowerCase())
1114
);
1215
model.setCurrentSearch(searchResults);
1316
}
1417

18+
const addFavourite = (course) => {
19+
model.addFavourite(course);
20+
}
1521
const removeFavourite = (course) => {
1622
model.removeFavourite(course);
1723
}
24+
const handleFavouriteClick = (course) => {
25+
if (model.favourites.some(fav => fav.code === course.code)) {
26+
model.removeFavourite(course);
27+
} else {
28+
model.addFavourite(course);
29+
}
30+
};
31+
1832

19-
function removeAllFavourites(){
33+
function removeAllFavourites() {
2034
model.setFavourite([]);
2135
}
2236

37+
const [isPopupOpen, setIsPopupOpen] = useState(false);
38+
const [selectedCourse, setSelectedCourse] = useState(null);
39+
const preP = <PrerequisitePresenter model={model} selectedCourse={selectedCourse} />
40+
41+
const popup = <CoursePagePopup
42+
favouriteCourses={model.favourites}
43+
addFavourite={addFavourite}
44+
removeFavourite={removeFavourite}
45+
handleFavouriteClick={handleFavouriteClick}
46+
isOpen={isPopupOpen} onClose={() => setIsPopupOpen(false)}
47+
course={selectedCourse}
48+
prerequisiteTree={preP} />
49+
50+
51+
52+
2353
return (
2454
<SearchbarView
2555
model={model}
2656
searchCourses={searchCourses}
27-
favouriteCourses = {model.favourites}
28-
removeFavourite={removeFavourite}
57+
favouriteCourses={model.favourites}
2958
removeAllFavourites={removeAllFavourites}
59+
addFavourite={addFavourite}
60+
removeFavourite={removeFavourite}
61+
isPopupOpen={isPopupOpen}
62+
setIsPopupOpen={setIsPopupOpen}
63+
setSelectedCourse={setSelectedCourse}
64+
popup={popup}
65+
handleFavouriteClick={handleFavouriteClick}
3066
/>
3167
);
3268
});
Lines changed: 101 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
1-
import React, { useEffect, useRef } from "react";
1+
import React, { useEffect, useRef } from 'react';
22

33
function CoursePagePopup({
4-
favouriteCourses,
5-
addFavourite,
6-
removeFavourite,
7-
isOpen,
8-
onClose,
9-
course,
10-
prerequisiteTree,
11-
reviewPresenter,
4+
favouriteCourses,
5+
addFavourite,
6+
removeFavourite,
7+
handleFavouriteClick,
8+
isOpen,
9+
onClose,
10+
course,
11+
prerequisiteTree
1212
}) {
13-
const treeRef = useRef(null);
14-
useEffect(() => {
15-
const handleKeyDown = (event) => {
16-
if (event.key === "Escape") {
17-
onClose();
18-
}
19-
};
13+
const treeRef = useRef(null);
14+
15+
useEffect(() => {
16+
const handleKeyDown = (event) => {
17+
if (event.key === 'Escape') {
18+
onClose();
19+
}
20+
};
2021

2122
if (isOpen) {
2223
window.addEventListener("keydown", handleKeyDown);
@@ -27,96 +28,93 @@ function CoursePagePopup({
2728
};
2829
}, [isOpen, onClose]);
2930

30-
const handleFavouriteClick = (course) => {
31-
if (favouriteCourses.some((fav) => fav.code === course.code)) {
32-
removeFavourite(course);
33-
} else {
34-
addFavourite(course);
35-
}
36-
};
31+
const handleTreeClick = () => {
32+
if (treeRef.current) {
33+
treeRef.current.focus(); // gives it focus
34+
}
35+
};
3736

38-
const handleTreeClick = () => {
39-
if (treeRef.current) {
40-
treeRef.current.focus(); // gives it focus
41-
}
42-
};
37+
if (!isOpen || !course) return null; // Don't render if not open or course not selected
38+
39+
return (
40+
<div
41+
className="fixed backdrop-blur-sm inset-0 bg-transparent flex justify-end z-50"
42+
onClick={onClose}
43+
>
44+
<div
45+
className="bg-indigo-300/75 backdrop-blur-lg h-full w-3/4 flex flex-col overflow-auto"
46+
onClick={(e) => e.stopPropagation()}
47+
>
48+
<div className="flex-1">
49+
<div className="px-10 py-10 md:px-20 md:py-16 text-slate-900 space-y-12 font-sans">
50+
{/* Course Title Section */}
51+
<div>
52+
<h2 className="text-5xl font-extrabold text-[#2e2e4f] ">
53+
<span className="text-violet-700">{course.code}</span> - {course.name}
54+
<span className="ml-4 text-lg text-violet-700 whitespace-nowrap">({course.credits} Credits)</span>
55+
</h2>
56+
<div className="my-6 h-1.5 w-full bg-violet-500"></div>
57+
</div>
58+
<div>
59+
<button
60+
className="text-yellow-100 bg-yellow-400 cursor-pointer"
61+
onClick={(e) => {
62+
e.stopPropagation(); // prevent popup from opening
63+
// Pass the full course object now instead of course.code:
64+
handleFavouriteClick(course);
65+
}}
66+
>
67+
{favouriteCourses.some(fav => fav.code === course.code)
68+
? 'Remove from Favourites'
69+
: 'Add to Favourites'}
70+
</button>
71+
</div>
72+
73+
{/* Description Section */}
74+
<div>
75+
<h3 className="text-2xl font-bold text-[#2e2e4f] mb-0.5">Course Description</h3>
76+
<div className="mb-3 h-0.5 w-full bg-violet-500"></div>
77+
<div
78+
className="text-lg leading-8 text-[#2e2e4f] font-semibold tracking-wide prose prose-slate max-w-full"
79+
dangerouslySetInnerHTML={{ __html: course.description }}
80+
/>
81+
</div>
4382

44-
if (!isOpen || !course) return null; // Don't render if not open or course not selected
45-
return (
46-
<div
47-
className="fixed backdrop-blur-sm inset-0 bg-transparent flex justify-end z-50"
48-
onClick={onClose}
49-
>
50-
<div
51-
className="bg-indigo-300/75 backdrop-blur-lg h-full w-3/4 flex flex-col overflow-auto"
52-
onClick={(e) => e.stopPropagation()}
53-
>
54-
<div className="flex-1">
55-
<div className="px-10 py-10 md:px-20 md:py-16 text-slate-900 space-y-12 font-sans">
56-
{/* Course Title Section */}
57-
<div>
58-
<h2 className="text-5xl font-extrabold text-[#2e2e4f] ">
59-
<span className="text-violet-700">{course.code}</span> -{" "}
60-
{course.name}
61-
<span className="ml-4 text-lg text-violet-700 whitespace-nowrap">
62-
({course.credits} Credits)
63-
</span>
64-
</h2>
65-
<div className="my-6 h-1.5 w-full bg-violet-500"></div>
66-
</div>
67-
<div>
68-
<button
69-
className="text-yellow-500 bg-yellow-400 cursor-pointer"
70-
onClick={(e) => {
71-
e.stopPropagation(); // prevent popup from opening
72-
handleFavouriteClick(course.code);
73-
}}
74-
>
75-
{favouriteCourses.includes(course.code)
76-
? "Remove from Favourites"
77-
: "Add to Favourites"}
78-
</button>
79-
</div>
83+
{/* Prerequisite Graph Tree Section */}
84+
<div>
8085

81-
{/* Description Section */}
82-
<div>
83-
<h3 className="text-2xl font-bold text-[#2e2e4f] mb-0.5">
84-
Course Description
85-
</h3>
86-
<div className="mb-3 h-0.5 w-full bg-violet-500"></div>
87-
<div
88-
className="text-lg leading-8 text-[#2e2e4f] font-semibold tracking-wide prose prose-slate max-w-full"
89-
dangerouslySetInnerHTML={{ __html: course.description }}
90-
/>
91-
</div>
86+
<h3 className="text-2xl font-semibold text-[#2e2e4f] mb-0.5">
87+
Prerequisite Graph Tree
88+
</h3>
89+
<div className="mb-4 h-0.5 w-full bg-violet-500"></div>
90+
<div
91+
className="bg-indigo-300/50 outline-none focus:outline-none focus:ring-2 focus:ring-violet-600 rounded-lg transition-shadow"
92+
ref={treeRef}
93+
onClick={handleTreeClick}
94+
tabIndex={0} // allows the div to receive focus
95+
>
96+
{prerequisiteTree}
97+
</div>
98+
</div>
9299

93-
{/* Prerequisite Graph Tree Section */}
94-
<div>
95-
<h3 className="text-2xl font-semibold text-[#2e2e4f] mb-0.5">
96-
Prerequisite Graph Tree
97-
</h3>
98-
<div className="mb-4 h-0.5 w-full bg-violet-500"></div>
99-
<div
100-
className="bg-indigo-300/50 outline-none focus:outline-none focus:ring-2 focus:ring-violet-600 rounded-lg transition-shadow"
101-
ref={treeRef}
102-
onClick={handleTreeClick}
103-
tabIndex={0} // allows the div to receive focus
104-
>
105-
{prerequisiteTree}
106-
</div>
107-
</div>
108-
{reviewPresenter}
109-
</div>
110-
</div>
111-
<button
112-
onClick={onClose}
113-
className="px-4 py-2 bg-violet-500 text-white"
114-
>
115-
Close
116-
</button>
117-
</div>
118-
</div>
119-
);
120-
}
100+
{/* Reviews Section */}
101+
<div>
102+
<h3 className="text-2xl font-semibold text-[#2e2e4f] mb-0.5">Reviews</h3>
103+
<div className="mb-4 h-0.5 w-full bg-violet-500"></div>
104+
<p className="text-lg text-slate-700 leading-7">
105+
Here would be some reviews of the course...
106+
</p>
107+
</div>
108+
</div>
109+
</div>
110+
<button
111+
onClick={onClose}
112+
className="px-4 py-2 bg-violet-500 text-white"
113+
>
114+
Close
115+
</button>
116+
</div>
117+
</div>
118+
);
121119

122120
export default CoursePagePopup;

my-app/src/views/Components/FavouriteDropdown.jsx

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,47 @@
11
import React from 'react';
22

33
function FavouritesDropdown(props) {
4+
console.log('Popup:', props.popup);
5+
console.log('isPopupOpen:', props.isPopupOpen);
46
return (
5-
<div className="absolute mt-2 w-48 bg-white border border-solid border-black rounded-lg z-50 overflow-y-auto">
7+
<div className="absolute mt-2 w-48 bg-white border border-solid border-black rounded-lg z-50 overflow-y-auto max-h-60">
68
{props.favouriteCourses.length > 0 ? (
7-
8-
props.favouriteCourses.map(course => (
9-
<div
10-
key={course.code}
11-
className="p-2 flex justify-between items-center w-full border border-solid border-black">
12-
<p
13-
className="text-black">
14-
{course.name}
15-
</p>
16-
<button
17-
className="text-red-500 cursor-pointer"
18-
onClick={() => props.removeFavourite(course)}>
19-
X
20-
</button>
21-
</div>
22-
))
23-
24-
25-
) : (
9+
props.favouriteCourses.map(course => (
10+
<div
11+
onClick={() => {
12+
console.log('Clicked:', course); // check browser console
13+
props.setSelectedCourse(course);
14+
props.setIsPopupOpen(true); // Ensure popup state is updated
15+
}}
16+
key={course.code}
17+
className="p-2 flex justify-between items-center w-full border border-solid border-black">
18+
<p className="text-black">{course.name}</p>
19+
<button
20+
className="text-red-500 cursor-pointer"
21+
onClick={(e) => {
22+
e.stopPropagation(); // Prevent parent click
23+
props.removeFavourite(course);
24+
}}>
25+
X
26+
</button>
27+
</div>
28+
))
29+
) : (
2630
<div className="p-2 text-[#000061]">
2731
No favourites
2832
</div>
2933
)}
30-
{props.favouriteCourses.length > 0 ? <button onClick={props.removeAllFavourites}>Clear Favourites</button> : ""}
34+
{/* Ensure popup is conditionally rendered */}
35+
<div style={{ position: 'relative', zIndex: 1000 }}>
36+
{props.isPopupOpen && props.popup}
37+
</div>
38+
{props.favouriteCourses.length > 0 && (
39+
<button
40+
onClick={props.removeAllFavourites}
41+
className="mt-2 text-red-500">
42+
Clear Favourites
43+
</button>
44+
)}
3145
</div>
3246
);
3347
}

0 commit comments

Comments
 (0)