Skip to content
Open
Show file tree
Hide file tree
Changes from 11 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
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ import {
WebsocSectionType,
GE,
} from '@packages/antalmanac-types';
import { useQuery } from '@tanstack/react-query';
import Image from 'next/image';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import LazyLoad from 'react-lazyload';

function getColors() {
Expand All @@ -48,9 +49,10 @@ function getColors() {
return courseColors;
}

const flattenSOCObject = (SOCObject: WebsocAPIResponse): (WebsocSchool | WebsocDepartment | AACourse)[] => {
const courseColors = getColors();

const flattenSOCObject = (
SOCObject: WebsocAPIResponse,
courseColors: ReturnType<typeof getColors>
): (WebsocSchool | WebsocDepartment | AACourse)[] => {
return SOCObject.schools.reduce((accumulator: (WebsocSchool | WebsocDepartment | AACourse)[], school) => {
accumulator.push(school);

Expand Down Expand Up @@ -264,16 +266,74 @@ const ErrorMessage = () => {
};

export default function CourseRenderPane(props: { id?: number }) {
const [websocResp, setWebsocResp] = useState<WebsocAPIResponse>();
const [courseData, setCourseData] = useState<(WebsocSchool | WebsocDepartment | AACourse)[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [courseColors, setCourseColors] = useState(getColors);
const [scheduleNames, setScheduleNames] = useState(AppStore.getScheduleNames());
const [unofferedCourses, setUnofferedCourses] = useState<CourseSearchParams[]>([]);
const [searchedTerm, setSearchedTerm] = useState(() => getTermLongName(RightPaneStore.getFormData().term));

const setHoveredEvent = useHoveredStore((store) => store.setHoveredEvent);

const {
data: websocResp,
isLoading,
isError,
} = useQuery({
staleTime: 5 * 60 * 1000,
queryKey: ['searchResults', RightPaneStore.getFormData(), RightPaneStore.getMultiSearchData()],
queryFn: async (): Promise<WebsocAPIResponse | null> => {
setUnofferedCourses([]);

try {
Comment thread
Choollol marked this conversation as resolved.
const multiSearchData = RightPaneStore.getMultiSearchData();
let websocJsonResp;
if (multiSearchData.length > 0) {
const { year, quarter } = RightPaneStore.getTermParts();
const offeredCourses: Record<string, string>[] = [];
const unofferedCourses: CourseSearchParams[] = [];
const gradeQueries: Promise<unknown>[] = [];
const offeredCoursesMapping = await trpc.search.filterOfferedCourses.query({
year: year,
quarter: quarter,
courses: multiSearchData.map((params) => ({ ...params, department: params.deptValue })),
});
for (const course of multiSearchData) {
if (offeredCoursesMapping[course.deptValue]?.has(course.courseNumber)) {
const { websocQueryParams, gradesQueryParams } = getQueryParams(course);
offeredCourses.push(websocQueryParams);
gradeQueries.push(queryGrades(gradesQueryParams));
} else {
unofferedCourses.push(course);
}
}
setUnofferedCourses(unofferedCourses);
websocJsonResp = await WebSOC.queryMultiple(offeredCourses);
await Promise.all(gradeQueries);
} else {
const formData = RightPaneStore.getFormData();
const { websocQueryParams, gradesQueryParams } = getQueryParams(formData);
const [websocJsonResponse, _] = await Promise.all([
websocQueryParams.units.includes(',')
? WebSOC.queryMultipleOfField(websocQueryParams, 'units')
: WebSOC.query(websocQueryParams),
queryGrades(gradesQueryParams),
]);
websocJsonResp = websocJsonResponse;
}
setSearchedTerm(getTermLongName(RightPaneStore.getFormData().term));
return websocJsonResp;
} catch (error) {
console.error(error);
openSnackbar('error', 'We ran into an error while looking up class info');
return null;
}
},
});

const courseData = useMemo(
() => (websocResp ? getFilteredCourses(flattenSOCObject(websocResp, courseColors)) : []),
Comment thread
Choollol marked this conversation as resolved.
[websocResp, courseColors]
);

const getQueryParams = useCallback((searchData: CourseSearchParams) => {
const websocQueryParams = {
department: searchData.deptValue,
Expand Down Expand Up @@ -311,88 +371,23 @@ export default function CourseRenderPane(props: { id?: number }) {
});
}, []);

const loadCourses = useCallback(async () => {
setLoading(true);
setError(false);
setUnofferedCourses([]);

try {
const multiSearchData = RightPaneStore.getMultiSearchData();
let websocJsonResp;
if (multiSearchData.length > 0) {
const { year, quarter } = RightPaneStore.getTermParts();
const offeredCourses: Record<string, string>[] = [];
const unofferedCourses: CourseSearchParams[] = [];
const gradeQueries: Promise<unknown>[] = [];
const offeredCoursesMapping = await trpc.search.filterOfferedCourses.query({
year: year,
quarter: quarter,
courses: multiSearchData.map((params) => ({ ...params, department: params.deptValue })),
});
for (const course of multiSearchData) {
if (offeredCoursesMapping[course.deptValue]?.has(course.courseNumber)) {
const { websocQueryParams, gradesQueryParams } = getQueryParams(course);
offeredCourses.push(websocQueryParams);
gradeQueries.push(queryGrades(gradesQueryParams));
} else {
unofferedCourses.push(course);
}
}
setUnofferedCourses(unofferedCourses);
websocJsonResp = await WebSOC.queryMultiple(offeredCourses);
await Promise.all(gradeQueries);
} else {
const formData = RightPaneStore.getFormData();
const { websocQueryParams, gradesQueryParams } = getQueryParams(formData);
const [websocJsonResponse, _] = await Promise.all([
websocQueryParams.units.includes(',')
? WebSOC.queryMultipleOfField(websocQueryParams, 'units')
: WebSOC.query(websocQueryParams),
queryGrades(gradesQueryParams),
]);
websocJsonResp = websocJsonResponse;
}
setWebsocResp(websocJsonResp);
const allCourses = flattenSOCObject(websocJsonResp);
setCourseData(getFilteredCourses(allCourses));
setSearchedTerm(getTermLongName(RightPaneStore.getFormData().term));
} catch (error) {
console.error(error);
setError(true);
openSnackbar('error', 'We ran into an error while looking up class info');
}

setLoading(false);
}, []);

const updateScheduleNames = () => {
setScheduleNames(AppStore.getScheduleNames());
};

useEffect(() => {
const changeColors = () => {
if (websocResp == null) {
return;
}
const flattened = flattenSOCObject(websocResp);
setCourseData(getFilteredCourses(flattened));
setCourseColors(getColors());
};

AppStore.on('currentScheduleIndexChange', changeColors);

return () => {
AppStore.off('currentScheduleIndexChange', changeColors);
};
}, [websocResp]);

useEffect(() => {
loadCourses();
AppStore.on('scheduleNamesChange', updateScheduleNames);
AppStore.on('currentScheduleIndexChange', changeColors);

return () => {
AppStore.off('scheduleNamesChange', updateScheduleNames);
AppStore.off('currentScheduleIndexChange', changeColors);
};
}, [loadCourses, props.id]);
}, [props.id]);

/**
* Removes hovered course when component unmounts
Expand Down Expand Up @@ -429,9 +424,9 @@ export default function CourseRenderPane(props: { id?: number }) {
</WarningAlert>
);
})}
{loading ? (
{isLoading ? (
<LoadingMessage />
) : error || courseData.length === 0 ? (
) : isError || courseData.length === 0 ? (
<ErrorMessage />
) : (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,6 @@ const FuzzySearch = ({ toggleSearch, postHog, labelProps }: FuzzySearchProps) =>
fullWidth: true,
onFocus: onFocus,
}}
isAligned
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface QuickSearchProps {
export const QuickSearch = ({ toggleSearch, labelProps }: QuickSearchProps) => {
const postHog = usePostHog();
const theme = useTheme();
const quickSearchStack = `@container quick-search (max-width: ${theme.breakpoints.values.sm}px)`;
const quickSearchStack = `@container quick-search (max-width: ${theme.breakpoints.values.xs}px)`;

return (
<Box
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const SearchForm = ({ toggleSearch }: SearchFormProps) => {
<ToggleButton value="manual">Manual Search</ToggleButton>
</ToggleButtonGroup>
<Box sx={{ display: 'flex', gap: 1 }}>
<TermSelector />
<TermSelector isAligned={manualSearchEnabled} />
</Box>

{!manualSearchEnabled ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,19 +136,33 @@ export const SearchWithPlanner = ({ labelProps }: SearchWithPlannerProps) => {

const renderOption = (props: HTMLAttributes<HTMLLIElement>, roadmap: Roadmap) => {
const menuItem = (
<Box display="flex" alignItems="center" justifyContent="space-between" sx={{ paddingRight: 1 }}>
<Box
key={roadmap.id}
display="flex"
alignItems="center"
justifyContent="space-between"
sx={{ paddingRight: 1 }}
>
<MenuItem
{...props}
key={roadmap.id}
onClick={() => search(roadmap.id)}
disabled={!doesRoadmapIncludeTerm(roadmap.id)}
sx={{ width: '100%' }}
>
<Typography sx={{ marginLeft: 1 }}>{roadmap.name}</Typography>
<Typography
sx={{ marginLeft: 1, overflow: 'hidden', textOverflow: 'ellipsis' }}
Comment thread
Choollol marked this conversation as resolved.
Outdated
title={roadmap.name}
>
{roadmap.name}
</Typography>
</MenuItem>

<IconButton href={PLANNER_LINK} size="small" aria-label="Open Planner">
<OpenInBrowser fontSize="small" />
</IconButton>
<Tooltip title="Open Planner">
<IconButton href={PLANNER_LINK} size="small" aria-label="Open Planner">
<OpenInBrowser fontSize="small" />
</IconButton>
</Tooltip>
</Box>
);
if (termRoadmapGrouping[RoadmapTermRelation.NoCourses].has(roadmap.id.toString())) {
Expand Down Expand Up @@ -235,7 +249,6 @@ export const SearchWithPlanner = ({ labelProps }: SearchWithPlannerProps) => {
}}
labelProps={labelProps}
loading={isLoadingSearch}
isAligned
/>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ComponentProps, useCallback, useEffect, useState } from 'react';

type TermSelectorProps = Omit<
ComponentProps<typeof LabeledAutocomplete>,
'label' | 'autocompleteProps' | 'textFieldProps' | 'isAligned'
'label' | 'autocompleteProps' | 'textFieldProps'
>;

export function TermSelector(props: TermSelectorProps) {
Expand Down Expand Up @@ -53,7 +53,7 @@ export function TermSelector(props: TermSelectorProps) {
textFieldProps={{
fullWidth: true,
}}
isAligned
isAligned={props?.isAligned ?? true}
/>
);
}
5 changes: 3 additions & 2 deletions apps/antalmanac/src/lib/api/trpc.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { AppRouter } from '$src/backend/routers';
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import superjson from 'superjson';

import { AppRouter } from '$src/backend/routers';

const trpc = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
Expand All @@ -13,6 +12,8 @@ const trpc = createTRPCProxyClient<AppRouter>({
credentials: 'include', // Send cookies with requests
});
},
// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-general
maxURLLength: 8192,
}),
],
transformer: superjson,
Expand Down
Loading