Skip to content

Commit 23b5104

Browse files
authored
Merge pull request #7084 from StoDevX/drew/course-terms-filter-debugging
Fix course search filtering by term, level, and gereq
2 parents 1d0f088 + 0851c68 commit 23b5104

File tree

3 files changed

+112
-54
lines changed

3 files changed

+112
-54
lines changed

source/lib/course-search/urls.ts

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ky, {Options} from 'ky'
22
import {CourseType, RawCourseType, TermInfoType, TermType} from './types'
3+
import intersection from 'lodash/intersection'
34

45
const BASE_URL = 'https://stolaf.dev'
56
export const COURSE_DATA_PAGE = `${BASE_URL}/course-data/`
@@ -19,13 +20,49 @@ export let timeData = (options?: Options): Promise<string[]> =>
1920
client.get('data-lists/valid_times.json', options).json()
2021
export let coursesForTerm = async (
2122
term: TermType,
23+
levels: Array<CourseType['level']>,
24+
gereqs: string[] = [],
2225
options?: Options,
2326
): Promise<Array<CourseType>> => {
2427
let data = (await client
2528
.get(`${term.path}`, options)
2629
.json()) as RawCourseType[]
27-
return data.map((course) => ({
28-
spaceAvailable: course.enrolled < course.max,
29-
...course,
30-
})) as CourseType[]
30+
return data
31+
.map((course) => ({
32+
spaceAvailable: course.enrolled < course.max,
33+
...course,
34+
}))
35+
.filter((c) => {
36+
return findMatches(c.level, levels, c.gereqs, gereqs)
37+
}) as CourseType[]
38+
}
39+
40+
const findMatches = (
41+
findLevel: number,
42+
levels: Array<number>,
43+
findgereqs: Array<string> = [],
44+
gereqs: Array<string>,
45+
) => {
46+
if (!levels.length && !gereqs.length) {
47+
return true
48+
}
49+
50+
if (levels.length && gereqs.length) {
51+
return matchesLevels(findLevel, levels) && matchesGEs(findgereqs, gereqs)
52+
} else if (levels.length) {
53+
return matchesLevels(findLevel, levels)
54+
} else if (gereqs.length) {
55+
return matchesGEs(findgereqs, gereqs)
56+
}
57+
58+
return true
59+
}
60+
61+
const matchesLevels = (item: number, levels: Array<CourseType['level']>) => {
62+
let levelRoundedDown = Math.floor(item / 100) * 100
63+
return levels.includes(levelRoundedDown)
64+
}
65+
66+
const matchesGEs = (items: string[] | undefined, gereqs: string[]) => {
67+
return items ? intersection(gereqs, items).length > 0 : false
3168
}

source/views/sis/course-search/query.ts

Lines changed: 16 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
deptData,
1111
geData,
1212
infoJson,
13-
timeData,
1413
} from '../../../lib/course-search/urls'
1514

1615
const ONE_SECOND = 1000
@@ -20,10 +19,12 @@ const ONE_DAY = ONE_HOUR * 24
2019

2120
export const keys = {
2221
terms: ['catalog', 'terms'] as const,
23-
courses: (term: TermType) => ['catalog', 'courses', term] as const,
22+
courses: (term: TermType, levels: Array<number>, gereqs: Array<string>) =>
23+
['catalog', 'courses', term, levels, gereqs] as const,
2424
gereqs: ['catalog', 'gereqs'] as const,
2525
departments: ['catalog', 'departments'] as const,
2626
times: ['catalog', 'times'] as const,
27+
levels: ['catalog', 'levels'] as const,
2728
}
2829

2930
export function useAvailableTerms(): UseQueryResult<TermType[], unknown> {
@@ -43,59 +44,35 @@ export function useAvailableTerms(): UseQueryResult<TermType[], unknown> {
4344
})
4445
}
4546

46-
export function useCourseDataForTerm(
47-
term: TermType,
48-
): UseQueryResult<CourseType[], unknown> {
49-
return useQuery({
50-
queryKey: keys.courses(term),
51-
queryFn: ({queryKey: [_group, _courses, term], signal}) =>
52-
coursesForTerm(term, {signal}),
53-
staleTime: ONE_HOUR,
54-
})
55-
}
56-
57-
export function useCourseDataForTerms(
58-
terms: TermType[],
47+
export function useCourseData(
48+
selectedTerms: Array<number> = [],
49+
levels: Array<number> = [],
50+
gereqs: Array<string> = [],
5951
): UseQueryResult<CourseType[], unknown>[] {
60-
let query = (
61-
term: TermType,
62-
): UseQueryOptions<
63-
CourseType[],
64-
unknown,
65-
CourseType[],
66-
ReturnType<(typeof keys)['courses']>
67-
> => ({
68-
queryKey: keys.courses(term),
69-
queryFn: ({queryKey: [_group, _courses, term], signal}) =>
70-
coursesForTerm(term, {signal}),
71-
staleTime: ONE_HOUR,
72-
})
73-
74-
return useQueries({
75-
queries: terms.map(query),
76-
})
77-
}
78-
79-
export function useCourseData(): UseQueryResult<CourseType[], unknown>[] {
8052
let {data: terms = []} = useAvailableTerms()
53+
let filteredTerms = terms.filter((t) => selectedTerms.includes(t.term))
8154

8255
let query = (
8356
term: TermType,
57+
levels: Array<number> = [],
58+
gereqs: Array<string> = [],
8459
): UseQueryOptions<
8560
CourseType[],
8661
unknown,
8762
CourseType[],
8863
ReturnType<(typeof keys)['courses']>
8964
> => ({
90-
queryKey: keys.courses(term),
91-
queryFn: ({queryKey: [_group, _courses, term], signal}) =>
92-
coursesForTerm(term, {signal}),
65+
queryKey: keys.courses(term, levels, gereqs),
66+
queryFn: ({queryKey: [_group, _courses, term, levels, gereqs], signal}) =>
67+
coursesForTerm(term, levels, gereqs, {signal}),
9368
staleTime: ONE_HOUR,
9469
enabled: terms.length > 0,
9570
})
9671

9772
return useQueries({
98-
queries: terms.map(query),
73+
queries: filteredTerms.length
74+
? filteredTerms.map((term) => query(term, levels, gereqs))
75+
: terms.map((term) => query(term, levels, gereqs)),
9976
})
10077
}
10178

@@ -114,11 +91,3 @@ export function useDepartments(): UseQueryResult<string[]> {
11491
staleTime: ONE_DAY,
11592
})
11693
}
117-
118-
export function useTimes(): UseQueryResult<string[]> {
119-
return useQuery({
120-
queryKey: keys.times,
121-
queryFn: ({signal}) => timeData({signal}),
122-
staleTime: ONE_DAY,
123-
})
124-
}

source/views/sis/course-search/results.tsx

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'
1414
import {ChangeTextEvent, RootStackParamList} from '../../../navigation/types'
1515
import {NativeStackNavigationOptions} from '@react-navigation/native-stack'
1616
import {useDebounce} from '@frogpond/use-debounce'
17-
import {ListSeparator, ListSectionHeader} from '@frogpond/lists'
17+
import {ListSeparator, ListSectionHeader, largeListProps} from '@frogpond/lists'
1818
import * as c from '@frogpond/colors'
1919
import {CourseRow} from './row'
2020
import memoize from 'lodash/memoize'
2121
import {parseTerm} from '../../../lib/course-search'
2222
import {NoticeView} from '@frogpond/notice'
2323
import {FilterToolbar} from '@frogpond/filter'
24+
import {ListSpecType} from '@frogpond/filter/types'
2425
import {applySearch, sortAndGroupResults} from './lib/execute-search'
2526
import {useCourseData} from './query'
2627
import {UseQueryResult} from '@tanstack/react-query'
@@ -58,6 +59,49 @@ function queriesToCourses(
5859
.filter((data) => data !== undefined) as CourseType[]
5960
}
6061

62+
const useSelectedFilter = (
63+
filterKey: string,
64+
filters: FilterType<CourseType>[],
65+
) => {
66+
return React.useMemo(
67+
() => filters.find((f) => f.key === filterKey),
68+
[filterKey, filters],
69+
)
70+
}
71+
72+
const useSelectedTerm = (filters: FilterType<CourseType>[]) => {
73+
let termFilter = useSelectedFilter('term', filters)
74+
75+
if (termFilter?.enabled) {
76+
let termFilterSpec = termFilter.spec as ListSpecType
77+
return termFilterSpec.selected.map((spec) => Number(spec.title))
78+
}
79+
80+
return []
81+
}
82+
83+
const useSelectedLevel = (filters: FilterType<CourseType>[]) => {
84+
let levelFilter = useSelectedFilter('level', filters)
85+
86+
if (levelFilter?.enabled) {
87+
let levelFilterSpec = levelFilter.spec as ListSpecType
88+
return levelFilterSpec.selected.map((spec) => Number(spec.title))
89+
}
90+
91+
return []
92+
}
93+
94+
const useSelectedGE = (filters: FilterType<CourseType>[]) => {
95+
let geFilter = useSelectedFilter('gereqs', filters)
96+
97+
if (geFilter?.enabled) {
98+
let geFilterSpec = geFilter.spec as ListSpecType
99+
return geFilterSpec.selected.map((spec) => spec.title)
100+
}
101+
102+
return []
103+
}
104+
61105
export const CourseSearchResultsView = (): JSX.Element => {
62106
let dispatch = useAppDispatch()
63107
let navigation = useNavigation()
@@ -78,7 +122,15 @@ export const CourseSearchResultsView = (): JSX.Element => {
78122
let [searchQuery, setSearchQuery] = React.useState(initialQuery)
79123
let delayedQuery = useDebounce(searchQuery, 500)
80124

81-
let allCoursesByTerm = useCourseData()
125+
let selectedTerms = useSelectedTerm(filters)
126+
let selectedLevels = useSelectedLevel(filters)
127+
let selectedGEs = useSelectedGE(filters)
128+
129+
let allCoursesByTerm = useCourseData(
130+
selectedTerms,
131+
selectedLevels,
132+
selectedGEs,
133+
)
82134
let areCoursesLoading = allCoursesByTerm.some((r) => r.isLoading)
83135
let areCoursesInError = allCoursesByTerm.some((r) => r.isError)
84136

@@ -188,7 +240,7 @@ export const CourseSearchResultsView = (): JSX.Element => {
188240
<ListSectionHeader title={parseTerm(title)} />
189241
)}
190242
sections={results}
191-
windowSize={10}
243+
{...largeListProps}
192244
/>
193245
)
194246
}

0 commit comments

Comments
 (0)