diff --git a/client/public/surprised_bear.svg b/client/public/surprised_bear.svg new file mode 100644 index 00000000..ee1c59a3 --- /dev/null +++ b/client/public/surprised_bear.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/modules/Results/Components/PreviewCard.tsx b/client/src/modules/Results/Components/PreviewCard.tsx index 4b438ac3..fb8dcd7c 100644 --- a/client/src/modules/Results/Components/PreviewCard.tsx +++ b/client/src/modules/Results/Components/PreviewCard.tsx @@ -9,6 +9,8 @@ import ReviewCard from '../../Course/Components/ReviewCard'; import styles from '../Styles/CoursePreview.module.css'; import { Class } from 'common'; +import Bear from '/surprised_bear.svg'; + const Review = ReviewCard; export const PreviewCard = ({ course }: PreviewCardProps) => { @@ -21,12 +23,12 @@ export const PreviewCard = ({ course }: PreviewCardProps) => { const [topReviewLikes, setTopReviewLikes] = useState(0); useEffect(() => { - updateGauges(); + if (course) { + updateGauges(); + } }, [course]); const updateGauges = () => { - if (!course) return; - setId(course._id); setRating(course.classRating ? String(course.classRating) : '-'); setDiff(course.classDifficulty ? String(course.classDifficulty) : '-'); @@ -35,10 +37,8 @@ export const PreviewCard = ({ course }: PreviewCardProps) => { }; const updateTopReview = () => { - if (!id) return; - axios - .post(`/api/courses/get-reviews`, { courseId: id }) + .post(`/api/courses/get-reviews`, { courseId: course._id }) .then((response) => { const reviews = response.data.result; if (reviews && reviews.length > 0) { @@ -62,15 +62,21 @@ export const PreviewCard = ({ course }: PreviewCardProps) => { const offered = lastOfferedSems(course); return ( -
+
- + {course.classTitle}
- {course.classSub.toUpperCase() + ' ' + course.classNum + ', ' + offered} + {course.classSub.toUpperCase() + + ' ' + + course.classNum + + ', ' + + offered}
@@ -94,14 +100,27 @@ export const PreviewCard = ({ course }: PreviewCardProps) => { )} {numReviews !== 0 && numReviews > 1 && ( - + See {numReviews} more review{numReviews > 1 ? 's' : ''} )} - {numReviews === 0 &&
No reviews yet
} + {numReviews === 0 && ( + <> + Bear Icon +
+ No reviews yet! Why not be the first? +
+ + )} {(numReviews === 0 || numReviews === 1) && ( - + Leave a Review )} diff --git a/client/src/modules/Results/Components/ResultsDisplay.tsx b/client/src/modules/Results/Components/ResultsDisplay.tsx index f9fbe77f..06750d69 100644 --- a/client/src/modules/Results/Components/ResultsDisplay.tsx +++ b/client/src/modules/Results/Components/ResultsDisplay.tsx @@ -21,19 +21,25 @@ import { Class } from 'common'; */ -export const ResultsDisplay = ({courses, loading, type, userInput}: ResultsDisplayProps) => { +export const ResultsDisplay = ({ + courses, + loading, + type, + userInput +}: ResultsDisplayProps) => { const [courseList, setCourseList] = useState(courses); const [filteredItems, setFilteredItems] = useState(courses); const [cardCourse, setCardCourse] = useState(courses[0]); const [activeCard, setActiveCard] = useState(0); - type SortBy = 'relevance' | 'rating' | 'diff' | 'work' + type SortBy = 'relevance' | 'rating' | 'diff' | 'work'; const [selected, setSelected] = useState( type === 'major' ? 'rating' : 'relevance' ); const [transformGauges, setTransformGauges] = useState(false); const [showFilterPopup, setShowFilterPopup] = useState(false); + const [searchListViewEnabled, setSearchListViewEnabled] = useState(true); type FilterValue = Map | string[]; type FilterMap = Map; @@ -76,7 +82,7 @@ export const ResultsDisplay = ({courses, loading, type, userInput}: ResultsDispl useEffect(() => { sort(filteredItems); - }, [selected]) + }, [selected]); /** * Handles selecting different sort filters @@ -116,10 +122,18 @@ export const ResultsDisplay = ({courses, loading, type, userInput}: ResultsDispl */ const sort = (items: Array) => { switch (selected) { - case 'relevance': sortBy(items, 'score', 0, true); break; - case 'rating': sortBy(items, 'classRating', 0, true); break; - case 'diff': sortBy(items, 'classDifficulty', Number.MAX_SAFE_INTEGER, false); break; - case 'work': sortBy(items, 'classDifficulty', 0, true); break; + case 'relevance': + sortBy(items, 'score', 0, true); + break; + case 'rating': + sortBy(items, 'classRating', 0, true); + break; + case 'diff': + sortBy(items, 'classDifficulty', Number.MAX_SAFE_INTEGER, false); + break; + case 'work': + sortBy(items, 'classDifficulty', 0, true); + break; } }; @@ -163,7 +177,7 @@ export const ResultsDisplay = ({courses, loading, type, userInput}: ResultsDispl setFilteredItems(filtered); sort(filtered); - } + }; /** * Updates the list of filtered items when filters are checked/unchecked @@ -220,11 +234,13 @@ export const ResultsDisplay = ({courses, loading, type, userInput}: ResultsDispl />
)); - } + }; const renderCheckboxes = (group: string) => { - let groupList = Array.from((filterMap.get(group) as Map).keys()); - return groupList.map((name, index) => ( + let groupList = Array.from( + (filterMap.get(group) as Map).keys() + ); + return groupList.map((name) => (
)); - } + }; return (
-

Search Results

+ {(loading || courseList.length === 0) &&

Search Results

} {/* Case where results are still being loaded */} {loading && } {/* Case where no results returned */} @@ -260,76 +276,98 @@ export const ResultsDisplay = ({courses, loading, type, userInput}: ResultsDispl {/* Case where results are returned (non-empty) */} {courseList.length !== 0 && !loading && (
-
-
Filter
-
-
Semester
- {renderCheckboxes('semesters')} -
-
-
Level
- {renderCheckboxes('levels')} -
-
- -
-
- We found{' '} - - {filteredItems.length === 0 ? courseList.length : filteredItems.length} - {' '} - courses for "{userInput} - " -
- -
-
- - +
+

Search Results

+ {/* setSearchListViewEnabled(!searchListViewEnabled)}*/} + {/*>*/} + {/* {searchListViewEnabled ? 'Hide' : 'View'} Search Results*/} + {/**/} +
+
+
Filter
+
+
Semester
+ {renderCheckboxes('semesters')} +
+
+
Level
+ {renderCheckboxes('levels')} +
- +
+ {searchListViewEnabled && ( + <> +
+ We found{' '} + + {filteredItems.length === 0 + ? courseList.length + : filteredItems.length} + {' '} + courses for "{userInput} + " +
+
+
+
+ + +
+ + +
+ {showFilterPopup && ( + + setShowFilterPopup(!showFilterPopup) + } + /> + )} +
+
+
+
+
    {renderResults()}
+
+
+
+ + )} +
- {showFilterPopup && ( - setShowFilterPopup(!showFilterPopup)} - /> - )} +
-
-
-
-
    {renderResults()}
-
-
-
- -
+
+
+
)}
); -} +}; interface ResultsDisplayProps { courses: Array; diff --git a/client/src/modules/Results/Styles/CoursePreview.module.css b/client/src/modules/Results/Styles/CoursePreview.module.css index 5effb9b3..0b55f660 100644 --- a/client/src/modules/Results/Styles/CoursePreview.module.css +++ b/client/src/modules/Results/Styles/CoursePreview.module.css @@ -2,7 +2,7 @@ display: flex; flex-flow: column; - gap: 24px; + gap: 10px; width: 100%; } @@ -63,8 +63,23 @@ .noreviews { display: flex; + font-size: 1.25em; justify-content: center; align-items: center; width: 100%; - height: 150px; + height: 50px; } + +.bearicon { + display: flex; + justify-content: center; + align-items: center; + + width: 100%; + max-height: 300px; + padding-top: 40px; + + @media only screen and (max-width: 843px) { + max-height: 200px; + } +} \ No newline at end of file diff --git a/client/src/modules/Results/Styles/Results.module.css b/client/src/modules/Results/Styles/Results.module.css index 52ee1a8c..de4be834 100644 --- a/client/src/modules/Results/Styles/Results.module.css +++ b/client/src/modules/Results/Styles/Results.module.css @@ -1,6 +1,7 @@ .page { background-color: var(--clr-blue-100); - min-height: 100vh; + height: 100%; + max-height: 100vh; padding-bottom: 63px; } @@ -9,7 +10,7 @@ width: 100%; max-width: 1440px; margin: 0 auto; - padding: 36px 25px; + padding: 36px 25px 0 36px; justify-content: center; align-items: center; @@ -18,23 +19,26 @@ flex-flow: column nowrap; } -.container > h1 { +.leftbar > h1 { width: 100%; - margin-bottom: 50px; + margin-bottom: 30px; } .layout { width: 100%; + max-height: 100%; + box-sizing: border-box; + overflow: hidden; display: flex; flex-flow: row nowrap; gap: 24px; justify-content: space-between; - align-items: flex-start; + align-items: stretch; /* Matches heights of left and right sections */ } .columns { - width: 100%; + /*width: 100%;*/ display: flex; flex-flow: column nowrap; gap: 24px; @@ -46,11 +50,43 @@ gap: 16px; width: 100%; max-width: 426px; + height: 100%; /* Take full height of the parent */ + overflow: hidden; /* Avoid adding scroll here */ +} + +.resultslist { + max-height: 100%; + height: 100%; + overflow-y: auto; + padding-right: 8px; } .preview { - width: 100%; - max-width: 620px; + width: 60vw; /* Dynamically adjusts based on viewport width */ + max-width: 900px; /* Prevents growing too wide on larger screens */ + flex: 1; + display: flex; + align-items: center; + justify-content: center; + max-height: 100%; /* Prevent extra height growth */ + height: 100%; + overflow: hidden; /* Avoid extra scrollbars */ + padding: 16px; + + border: 1px solid var(--clr-gray-300); + background-color: color-mix(in srgb, var(--clr-blue-100) 90%, var(--clr-gray-300)); + border-radius: 8px; +} + +.previewcard { + min-width: 100%; + min-height: 100%; +} + +.filtersearch { + display: flex; + flex-flow: row nowrap; + width: 40vw; } .noresultimg { @@ -83,6 +119,7 @@ display: flex; flex-flow: row nowrap; justify-content: space-between; + align-items: center; width: 100%; position: relative; @@ -121,6 +158,28 @@ } @media only screen and (max-width: 843px) { + .page { + max-height: 100%; + } + + .container { + padding: 36px 25px; + } + + .leftbar > h1 { + margin-bottom: 10px; + } + + .filtersearch { + width: 100vw; + } + + .filtersortbuttons { + display: flex; + justify-content: center; + flex-flow: column nowrap; + } + .resultslist { max-height: 40vh; overflow-y: auto; @@ -141,7 +200,7 @@ } .preview { - max-width: none; + width: 100%; } }