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
5 changes: 4 additions & 1 deletion my-app/src/presenters/SearchbarPresenter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const SearchbarPresenter = observer(({ model }) => {
const [selectedCourse, setSelectedCourse] = useState(null);
const preP = <PrerequisitePresenter model={model} selectedCourse={selectedCourse} />;
const reviewPresenter = <ReviewPresenter model={model} course={selectedCourse} />;
const [searchQuery, setSearchQuery] = useState("");

const popup = <CoursePagePopup
favouriteCourses={model.favourites}
Expand All @@ -69,7 +70,7 @@ const SearchbarPresenter = observer(({ model }) => {


if(model.filtersCalculated){
searchCourses("");
searchCourses(searchQuery);
model.filtersCalculated = false;
}

Expand All @@ -84,6 +85,8 @@ const SearchbarPresenter = observer(({ model }) => {
setIsPopupOpen={setIsPopupOpen}
setSelectedCourse={setSelectedCourse}
popup={popup}
setSearchQuery={setSearchQuery}
searchQuery={searchQuery} // Add this line
handleFavouriteClick={handleFavouriteClick}
totalCredits={creditsSum(model.favourites)}
resetScrollPosition={resetScoll}
Expand Down
54 changes: 28 additions & 26 deletions my-app/src/views/Components/FavouriteDropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,13 @@ const FavouritesDropdown = observer((props) => {
navigator.clipboard.writeText(url)
.then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2500); // revert after 2.5 seconds
setTimeout(() => setCopied(false), 2500);
})
.catch(err => {
console.error("Copy failed:", err);
});
}

function handleCopy() {
navigator.clipboard.writeText(shareUrl)
.catch(err => console.error("Copy failed:", err));
}

return (
<div>
<div className="fixed mt-3 w-[32rem] right-4 bg-indigo-300/75 backdrop-blur-lg border border-violet-500 rounded-lg z-20 shadow-lg flex flex-col max-h-[calc(100vh-8rem)]">
Expand Down Expand Up @@ -70,28 +65,35 @@ const FavouritesDropdown = observer((props) => {
)}
</div>

<div className="flex border-t border-solid border-violet-400">
{props.favouriteCourses.length > 0 && (
<>
<button
onClick={props.removeAllFavourites}
className="w-1/2 p-3 cursor-pointer text-red-600 hover:bg-red-600 hover:text-white border-r border-solid border-violet-400 font-semibold transition-colors"
>
Clear All
</button>

<button
onClick={handleShareCourses}
className={`w-1/2 p-3 cursor-pointer ${copied ? "bg-violet-600 text-white" : "text-violet-700 hover:bg-blue-500 hover:text-white"
} flex items-center justify-center gap-2 font-semibold transition-colors duration-300`}
>
{copied ? "Copied to Clipboard!" : "Share Courses"}
</button>
</>
)}
{/* Fixed Footer */}
<div className="sticky bottom-0 border-t border-solid border-violet-400 bg-indigo-300/75 backdrop-blur-lg">
<div className='p-3 flex justify-between items-center bg-violet-400/30'>
<p className='text-slate-900 font-bold w-1/2'>Total:</p>
<p className='text-slate-900 font-bold w-1/4'></p>
<p className='text-slate-900 font-bold w-1/4 text-center'>{props.totalCredits} hp</p>
<div className="w-8"></div>
</div>
<div className="flex border-t border-solid border-violet-400">
{props.favouriteCourses.length > 0 && (
<>
<button
onClick={props.removeAllFavourites}
className="w-1/2 p-3 cursor-pointer text-red-600 hover:bg-red-600 hover:text-white border-r border-solid border-violet-400 font-semibold transition-colors"
>
Clear All
</button>
<button
onClick={handleShareCourses}
className={`w-1/2 p-3 cursor-pointer ${copied ? "bg-violet-600 text-white" : "text-violet-700 hover:bg-blue-500 hover:text-white"} flex items-center justify-center gap-2 font-semibold transition-colors duration-300`}
>
{copied ? "Copied to Clipboard!" : "Share Courses"}
</button>
</>
)}
</div>
</div>

</div>

{/* Optional course popup */}
<div className="relative z-50">
{props.isPopupOpen && props.popup}
Expand Down
83 changes: 71 additions & 12 deletions my-app/src/views/ListView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ function ListView(props) {
const [hasMore, setHasMore] = useState(true);
const [readMore, setReadMore] = useState({});
const [isLoading, setIsLoading] = useState(true);
const [sortBy, setSortBy] = useState('relevance');
const [sortDirection, setSortDirection] = useState('asc');

const toggleReadMore = (courseCode) => {
setReadMore(prevState => ({
Expand All @@ -25,20 +27,45 @@ function ListView(props) {
}
};

const sortCourses = (courses, sortType) => {
const sortedCourses = [...courses];
const direction = sortDirection === 'asc' ? 1 : -1;

switch (sortType) {
case 'name':
return sortedCourses.sort((a, b) =>
direction * a.name.localeCompare(b.name));
case 'credits':
return sortedCourses.sort((a, b) =>
direction * (parseFloat(a.credits) - parseFloat(b.credits)));
case 'relevance':
default:
return direction === 1 ? courses : [...courses].reverse();
}
};

useEffect(() => {
setIsLoading(true);
const initialCourses = coursesToDisplay.slice(0, 10);
const sortedCourses = sortCourses(coursesToDisplay, sortBy);
const initialCourses = sortedCourses.slice(0, 10);
setDisplayedCourses(initialCourses);
setHasMore(coursesToDisplay.length > 10);
setHasMore(sortedCourses.length > 10);
setIsLoading(false);
}, [props.courses, props.searchResults]);
}, [props.courses, props.searchResults, sortBy, sortDirection]);

const fetchMoreCourses = useCallback(() => {
if (!hasMore) return;

// Get the next batch of unsorted courses
const nextItems = coursesToDisplay.slice(displayedCourses.length, displayedCourses.length + 50);
setDisplayedCourses(prevCourses => [...prevCourses, ...nextItems]);

// Sort the combined courses (existing + new) to maintain consistency
const allCourses = [...displayedCourses, ...nextItems];
const sortedCourses = sortCourses(allCourses, sortBy);

setDisplayedCourses(sortedCourses);
setHasMore(displayedCourses.length + nextItems.length < coursesToDisplay.length);
}, [displayedCourses.length, coursesToDisplay, hasMore]);
}, [displayedCourses.length, coursesToDisplay, hasMore, sortBy, sortDirection]);

const [isRestoringScroll, setIsRestoringScroll] = useState(false);
useEffect(() => {
Expand Down Expand Up @@ -73,13 +100,45 @@ function ListView(props) {
</div>
) : (
<div className="overflow-y-auto h-full" id="scrollableDiv" ref={props.scrollContainerRef}>
<p className="text-base font-semibold text-gray-600 mb-2">
Found
<span className="font-bold text-[#000061] mx-1">
{props.currentSearchLenght}
</span>
courses
</p>
<div className="flex justify-between items-center mb-4">
<p className="text-base font-semibold text-gray-600">
Found
<span className="font-bold text-[#000061] mx-1">
{props.currentSearchLenght}
</span>
courses
</p>

<div className="flex items-center gap-2">
<select
value={sortBy}
onChange={(e) => {
setSortBy(e.target.value);
}}
className="bg-white border-2 border-[#000061] text-[#000061] font-semibold py-2 px-4 rounded-lg cursor-pointer hover:bg-blue-50 transition-colors duration-200"
>
<option value="relevance">Sort by Relevance</option>
<option value="name">Sort by Name</option>
<option value="credits">Sort by Credits</option>
</select>

<button
onClick={() => setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc')}
className="bg-white border-2 border-[#000061] text-[#000061] font-semibold p-2 rounded-lg cursor-pointer hover:bg-blue-50 transition-colors duration-200"
aria-label={`Sort ${sortDirection === 'asc' ? 'ascending' : 'descending'}`}
>
{sortDirection === 'asc' ? (
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
</svg>
)}
</button>
</div>
</div>

<InfiniteScroll
dataLength={displayedCourses.length}
Expand Down
11 changes: 5 additions & 6 deletions my-app/src/views/SearchbarView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import project_logo from "../assets/project_icon.png";
import FavouritesDropdown from "./Components/FavouriteDropdown.jsx";

function SearchbarView(props) {
const [searchQuery, setSearchQuery] = useState("");
// const [searchQuery, setSearchQuery] = useState("");
Copy link

Copilot AI May 7, 2025

Choose a reason for hiding this comment

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

[nitpick] Consider removing the commented-out state declaration if it is no longer needed to reduce clutter and improve code clarity.

Suggested change
// const [searchQuery, setSearchQuery] = useState("");
// (Line removed)

Copilot uses AI. Check for mistakes.
const [user, setUser] = useState(null);
const [showFavourites, setShowFavourites] = useState(false);

Expand All @@ -21,7 +21,7 @@ function SearchbarView(props) {

const handleSearch = (query) => {
props.resetScrollPosition();
setSearchQuery(query);
props.setSearchQuery(query);
props.searchCourses(query);
};

Expand Down Expand Up @@ -52,10 +52,9 @@ function SearchbarView(props) {
<input
type="text"
placeholder="What course are you looking for?"
value={searchQuery}
onChange={(e) =>
handleSearch(e.target.value)}
onClick={(e)=>e.stopPropagation()} //TODO decide if we want to close the fav list after clicking the searchbar
value={props.searchQuery} // Changed from searchQuery to props.searchQuery
onChange={(e) => handleSearch(e.target.value)}
onClick={(e) => e.stopPropagation()}
className="w-[400px] h-[44px] pl-14 pr-4 bg-white text-black rounded-full"
/>

Expand Down