diff --git a/app/(main)/compare/page.tsx b/app/(main)/compare/page.tsx index 08dd84b..604fe92 100644 --- a/app/(main)/compare/page.tsx +++ b/app/(main)/compare/page.tsx @@ -1,4 +1,4 @@ -import { Card, LinkBtn } from '@/components/atoms'; +import { Btn, Card, LinkBtn } from '@/components/atoms'; import { CompareItem, FilterGroup, SearchBar } from '@/components/molecules'; import { BarChart } from '@/components/organisms'; import { @@ -8,6 +8,7 @@ import { } from '@/types'; import fetcher from '@/utils/fetcher'; import roundToTenth from '@/utils/round-to-tenth'; +import { EllipsisVerticalIcon, XMarkIcon } from '@heroicons/react/24/solid'; export default async function Page({ searchParams, @@ -108,12 +109,34 @@ export default async function Page({ return (
-
-
-
-

- Search and select professors and/or courses to compare -

+
+

Grade/Rating Analysis

+ + + +
+
+
+
+
+

Filters

+ + + +
+
@@ -149,41 +172,49 @@ export default async function Page({
-
- {professorStats.map((professor) => ( - - ))} - {courseStats.map((course) => ( - - ))} -
+ {professorStats.length + courseStats.length ? ( +
+ {professorStats.map((professor) => ( + + ))} + {courseStats.map((course) => ( + + ))} +
+ ) : ( +

+ No items selected. +
+ Search and select professors/courses to compare. +

+ )}

Rating Distribution

({ name: professor.id, - data: professor.ratingDistribution, + data: professor.ratingDistribution.reverse(), })) ?? []), ...(courseStats.map((course) => ({ name: course.id, - data: course.ratingDistribution, + data: course.ratingDistribution.reverse(), })) ?? []), ]} categories={[5, 4, 3, 2, 1]} diff --git a/app/(main)/courses/[id]/@schedules/page.tsx b/app/(main)/courses/[id]/@schedules/page.tsx index 97d28ce..295b448 100644 --- a/app/(main)/courses/[id]/@schedules/page.tsx +++ b/app/(main)/courses/[id]/@schedules/page.tsx @@ -12,7 +12,7 @@ const getKey = (pageIndex: number, previousPageData: CoursesIDSchedulesResponse) => { if (previousPageData && previousPageData.page === previousPageData.pages) return null; - return `/django/core/courses/${id}/schedules?page=${pageIndex + 1}`; + return `/django/core/courses/${id}/schedules?limit=4&page=${pageIndex + 1}`; }; export default function Page({ params }: { params: { id: string } }) { diff --git a/app/(main)/courses/[id]/@statistics/page.tsx b/app/(main)/courses/[id]/@statistics/page.tsx index a06ee6b..c5166db 100644 --- a/app/(main)/courses/[id]/@statistics/page.tsx +++ b/app/(main)/courses/[id]/@statistics/page.tsx @@ -186,7 +186,12 @@ export default async function Page({

Rating Distribution

diff --git a/app/(main)/professors/[id]/@schedules/page.tsx b/app/(main)/professors/[id]/@schedules/page.tsx index 68d7a0b..471eda3 100644 --- a/app/(main)/professors/[id]/@schedules/page.tsx +++ b/app/(main)/professors/[id]/@schedules/page.tsx @@ -12,7 +12,7 @@ const getKey = (pageIndex: number, previousPageData: ProfessorsIDSchedulesResponse) => { if (previousPageData && previousPageData.page === previousPageData.pages) return null; - return `/django/core/professors/${id}/schedules?page=${pageIndex + 1}`; + return `/django/core/professors/${id}/schedules?limit=4&page=${pageIndex + 1}`; }; export default function Page({ params }: { params: { id: string } }) { diff --git a/app/(main)/professors/[id]/@statistics/page.tsx b/app/(main)/professors/[id]/@statistics/page.tsx index bc51c5c..170fa36 100644 --- a/app/(main)/professors/[id]/@statistics/page.tsx +++ b/app/(main)/professors/[id]/@statistics/page.tsx @@ -194,7 +194,7 @@ export default async function Page({ series={[ { name: 'Rating Distribution', - data: reviewDistribution, + data: reviewDistribution.reverse(), }, ]} categories={[5, 4, 3, 2, 1]} diff --git a/components/molecules/client/compare-search-bar/component.tsx b/components/molecules/client/compare-search-bar/component.tsx new file mode 100644 index 0000000..434e64e --- /dev/null +++ b/components/molecules/client/compare-search-bar/component.tsx @@ -0,0 +1,153 @@ +'use client'; + +import { useSearchParams } from 'next/navigation'; +import useSWR from 'swr'; + +import { Card } from '@/components/atoms'; +import { ParamSelect, SearchBar } from '@/components/molecules'; +import { CoursesSearchResponse, ProfessorsSearchResponse } from '@/types'; +import SWRConfigProvider from '@/wrappers/swr-config'; +import Link from 'next/link'; + +type Error = { message: string }; + +const useCoursesSearchResults = (currentQuery: string) => { + const apiQueryParams = new URLSearchParams(); + apiQueryParams.set('query', currentQuery); + apiQueryParams.set('limit', '3'); + const { data, error, isLoading } = useSWR( + `/django/core/courses/search?${apiQueryParams.toString()}`, + ); + return { data, error, isLoading }; +}; + +const useProfessorsSearchResults = (currentQuery: string) => { + const apiQueryParams = new URLSearchParams(); + apiQueryParams.set('query', currentQuery); + apiQueryParams.set('limit', '3'); + + const { data, error, isLoading } = useSWR( + `/django/core/professors/search?${apiQueryParams.toString()}`, + ); + return { data, error, isLoading }; +}; + +interface StatusMessageProps { + isLoading: boolean; + error: Error | undefined; + data: ({ total_results: number } & unknown) | undefined; +} + +const StatusMessage: React.FC = ({ + isLoading, + error, + data, +}) => { + if (isLoading) { + return
  • Loading...
  • ; + } + if (error) { + return ( +
  • + Error: {error.message} +
  • + ); + } + if (!data || data.total_results === 0) { + return
  • No results found
  • ; + } + return null; +}; + +const CourseSearchResults: React.FC = () => { + const searchParams = useSearchParams(); + const currentOption = searchParams.get('compareOption') ?? 'professors'; + const currentQuery = searchParams.get('compareQuery') ?? ''; + const { data, error, isLoading } = useCoursesSearchResults(currentQuery); + return ( + + + {data && data.total_results > 0 + ? data.items.map((course, i) => ( +
  • + + + {course.department} {course.course_number} + + + {course.name} + + +
  • + )) + : null} +
    + ); +}; + +const ProfessorSearchResults: React.FC = () => { + const searchParams = useSearchParams(); + const currentOption = searchParams.get('compareOption') ?? 'professors'; + const currentQuery = searchParams.get('compareQuery') ?? ''; + const { data, error, isLoading } = useProfessorsSearchResults(currentQuery); + return ( + + + {data && data.total_results > 0 + ? data.items.map((professor, i) => ( +
  • + + + {professor.email} + + + {professor.name} + + +
  • + )) + : null} +
    + ); +}; + +export const CompareSearchBar: React.FC = () => { + const searchParams = useSearchParams(); + const currentOption = searchParams.get('compareOption') ?? 'professors'; + const currentQuery = searchParams.get('compareQuery') ?? ''; + return ( +
    + + + + + + {currentQuery ? ( + + + {currentOption === 'professors' ? ( + + ) : currentOption === 'courses' ? ( + + ) : null} + + + ) : null} +
    + ); +}; diff --git a/components/molecules/client/compare-search-bar/index.ts b/components/molecules/client/compare-search-bar/index.ts new file mode 100644 index 0000000..33b9887 --- /dev/null +++ b/components/molecules/client/compare-search-bar/index.ts @@ -0,0 +1,2 @@ +export { CompareSearchBar as default } from './component'; +export * from './component'; diff --git a/components/molecules/client/index.ts b/components/molecules/client/index.ts index f8c3abe..7ccf433 100644 --- a/components/molecules/client/index.ts +++ b/components/molecules/client/index.ts @@ -3,3 +3,4 @@ export * from './profile-btn'; export * from './nav-search-bar'; export * from './pagination-bar'; export * from './color-mode-picker'; +export * from './compare-search-bar'; diff --git a/components/molecules/client/pagination-bar/component.tsx b/components/molecules/client/pagination-bar/component.tsx index adcada9..8796a6d 100644 --- a/components/molecules/client/pagination-bar/component.tsx +++ b/components/molecules/client/pagination-bar/component.tsx @@ -39,7 +39,7 @@ function PaginationNumber({ isActive: boolean; }) { const className = cn( - 'flex max-lg:h-6 max-lg:min-w-[24px] w-fit px-1 lg:h-10 lg:min-w-[40px] items-center justify-center max-lg:text-small-sm border-border border-2 transition-all duration-100 ease-in-out', + 'flex max-lg:h-8 max-lg:min-w-8 w-fit px-1 lg:h-10 lg:min-w-10 items-center justify-center max-lg:text-small-sm border-border border-2 transition-all duration-100 ease-in-out', { 'rounded-l-sm': position === 'first' || position === 'single', 'rounded-r-sm': position === 'last' || position === 'single', @@ -68,7 +68,7 @@ const PaginationArrow = ({ isDisabled?: boolean; }) => { const className = cn( - 'flex max-lg:h-6 max-lg:w-6 lg:h-10 lg:w-10 items-center justify-center rounded-sm transition-all duration-100 ease-in-out', + 'flex max-lg:h-8 max-lg:w-8 lg:h-10 lg:w-10 items-center justify-center rounded-sm transition-all duration-100 ease-in-out', { 'pointer-events-none text-neutral': isDisabled, 'hover:bg-border': !isDisabled, diff --git a/components/molecules/client/profile-btn/component.tsx b/components/molecules/client/profile-btn/component.tsx index 0ad5261..e812ac8 100644 --- a/components/molecules/client/profile-btn/component.tsx +++ b/components/molecules/client/profile-btn/component.tsx @@ -3,7 +3,7 @@ import { LinkBtn } from '@/components/atoms'; import { cn } from '@/utils/cn'; import { useSession } from '@/wrappers/session-provider'; -import { UserIcon } from '@heroicons/react/20/solid'; +import { UserCircleIcon } from '@heroicons/react/24/solid'; interface Props extends Omit< @@ -20,8 +20,9 @@ export const ProfileBtn: React.FC = ({ className, ...props }) => { {...props} href="/profile" variant="tertiary" + aria-label="Profile" > - Profile + {' '} ); } diff --git a/components/molecules/compare-item/component.tsx b/components/molecules/compare-item/component.tsx new file mode 100644 index 0000000..995c454 --- /dev/null +++ b/components/molecules/compare-item/component.tsx @@ -0,0 +1,91 @@ +import { Card, LinkBtn, Stars } from '@/components/atoms'; +import { cn } from '@/utils/cn'; +import getEvaluation from '@/utils/get-evaluation'; +import { + ArrowPathIcon, + ClipboardDocumentListIcon, +} from '@heroicons/react/16/solid'; + +interface Props { + link: string; + id: string; + review?: number; + totalReviews: number; + avgGrade?: string | null; + takeAgainPercent?: number; +} + +export const CompareItem: React.FC = (props) => ( + + +
    +

    {props.id}

    +
    + {props.review ? ( +

    {props.review}

    + ) : ( +

    -

    + )} + +
    + +
    +

    + {props.totalReviews} Reviews +

    +
    +
    +
    +
    + +

    + Average Grade:{' '} + {props.avgGrade ? ( + + {props.avgGrade} + + ) : ( + - + )} +

    +
    +
    + +

    + Would Take Again:{' '} + {props.takeAgainPercent ? ( + + {props.takeAgainPercent}% + + ) : ( + - + )} +

    +
    +
    +
    +
    +); diff --git a/components/molecules/compare-item/index.ts b/components/molecules/compare-item/index.ts new file mode 100644 index 0000000..64a6f7c --- /dev/null +++ b/components/molecules/compare-item/index.ts @@ -0,0 +1,2 @@ +export {CompareItem as default} from './component'; +export * from './component'; diff --git a/components/molecules/index.ts b/components/molecules/index.ts index 99df5f3..8c587e9 100644 --- a/components/molecules/index.ts +++ b/components/molecules/index.ts @@ -2,3 +2,4 @@ export * from './filter-group'; export * from './param-select'; export * from './search-bar'; export * from './client'; +export * from './compare-item'; diff --git a/components/organisms/client/navbar/component.tsx b/components/organisms/client/navbar/component.tsx index 6d9dedc..673e0ce 100644 --- a/components/organisms/client/navbar/component.tsx +++ b/components/organisms/client/navbar/component.tsx @@ -93,6 +93,9 @@ export const Navbar: React.FC = () => { > Professors + + Compare + diff --git a/components/organisms/schedule/component.tsx b/components/organisms/schedule/component.tsx index 451e61f..9c87ee7 100644 --- a/components/organisms/schedule/component.tsx +++ b/components/organisms/schedule/component.tsx @@ -27,7 +27,7 @@ export const Schedule: React.FC = (props) => (