Skip to content

Commit 28be6ca

Browse files
committed
added no duplicate courses in search and dont show courses that are already completed.
1 parent 4d547ac commit 28be6ca

2 files changed

Lines changed: 55 additions & 5 deletions

File tree

entrypoints/components/course-add-modal.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getCurrentSemester } from "@/lib/backend/audit-scraper";
22
import { searchCatalogCourses } from "@/lib/backend/db";
33
import type {
44
CatalogCourse,
5+
Course,
56
CourseCode,
67
PlannedCourseOutline,
78
} from "@/lib/general-types";
@@ -54,11 +55,33 @@ const DEPARTMENTS = [
5455
"History",
5556
];
5657

58+
// Course statuses that mean the student already has the course, so it should be
59+
// hidden from search results.
60+
const EXCLUDED_SEARCH_STATUSES = new Set<string>(["Completed", "In Progress"]);
61+
62+
// Build the set of course codes to hide from search based on the student's audit.
63+
function getExcludedCourseCodes(courses: Course[]): Set<string> {
64+
const excludedCodes = new Set<string>();
65+
for (const course of courses) {
66+
if (EXCLUDED_SEARCH_STATUSES.has(course.status)) {
67+
excludedCodes.add(course.code.trim());
68+
}
69+
}
70+
return excludedCodes;
71+
}
72+
5773
async function SearchCourses(
5874
searchData: CourseSearchData,
75+
excludeCodes?: Set<string>,
5976
): Promise<CatalogCourse[]> {
6077
// Pass the modal form data into the DB search helper and return the results.
61-
return searchCatalogCourses(searchData);
78+
return searchCatalogCourses({
79+
searchQuery: searchData.searchQuery,
80+
department: searchData.department,
81+
lowerDivision: searchData.lowerDivision,
82+
upperDivision: searchData.upperDivision,
83+
excludeCodes,
84+
});
6285
}
6386

6487
function waitForNextPaint(): Promise<void> {
@@ -543,6 +566,7 @@ export function CourseSearchResults({
543566
}
544567

545568
export function CourseSearchPanel() {
569+
const { courses: studentCourses } = useAuditContext();
546570
const [view, setView] = useState(false);
547571
const [courses, setCourses] = useState<CatalogCourse[]>([]);
548572

@@ -551,7 +575,10 @@ export function CourseSearchPanel() {
551575
) : (
552576
<CourseSearchContent
553577
onSearchSubmit={async (formData) => {
554-
const results = await SearchCourses(formData);
578+
const results = await SearchCourses(
579+
formData,
580+
getExcludedCourseCodes(studentCourses),
581+
);
555582
setCourses(results);
556583
setView(true);
557584
}}

lib/backend/db.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,19 +175,23 @@ function getDepartmentCodesByName(departmentName: string): string[] {
175175
}
176176

177177
// Searches catalog courses by text, department, and lower/upper division filters.
178-
export function searchCatalogCourses(filters: {
178+
// Skips any course codes passed in `excludeCodes` (e.g. courses the student has
179+
// already taken) and collapses duplicate sections so each course appears once.
180+
export async function searchCatalogCourses(filters: {
179181
searchQuery: string;
180182
department: string;
181183
lowerDivision: boolean;
182184
upperDivision: boolean;
185+
excludeCodes?: Set<string>;
183186
}): Promise<CatalogCourse[]> {
184187
// Normalize the search inputs once before filtering the collection.
185188
const normalizedQuery = filters.searchQuery.trim().toLowerCase();
186189
const departmentCodes = filters.department
187190
? getDepartmentCodesByName(filters.department)
188191
: [];
192+
const excludeCodes = filters.excludeCodes ?? new Set<string>();
189193

190-
return db.courses
194+
const matches = await db.courses
191195
.toCollection()
192196
.filter((course) => {
193197
// Match the search text against the catalog title fields.
@@ -208,7 +212,26 @@ export function searchCatalogCourses(filters: {
208212
(filters.lowerDivision && courseLevel >= 1 && courseLevel <= 3) ||
209213
(filters.upperDivision && courseLevel >= 4 && courseLevel <= 6);
210214

211-
return matchesQuery && matchesDepartment && matchesDivision;
215+
// Drop courses the student has already taken.
216+
const isAlreadyTaken = excludeCodes.has(
217+
`${course.department} ${course.number}`,
218+
);
219+
220+
return (
221+
matchesQuery && matchesDepartment && matchesDivision && !isAlreadyTaken
222+
);
212223
})
213224
.toArray();
225+
226+
// Collapse duplicate sections/teachers so each course code appears only once.
227+
const seenCourseCodes = new Set<string>();
228+
return matches.filter((course) => {
229+
const courseCode = `${course.department} ${course.number}`;
230+
if (seenCourseCodes.has(courseCode)) {
231+
return false;
232+
}
233+
234+
seenCourseCodes.add(courseCode);
235+
return true;
236+
});
214237
}

0 commit comments

Comments
 (0)