Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
eed8eff
visualization method
calebwongg Apr 6, 2026
d0c5976
add mock filters / placeholders
calebwongg Apr 6, 2026
36d98a8
add clear and delete logic
calebwongg Apr 6, 2026
4399bf9
add search filter logic
calebwongg Apr 6, 2026
c127008
add working test scripts
calebwongg Apr 6, 2026
63d6250
adjust sizing
calebwongg Apr 6, 2026
d26ea78
change styling
calebwongg Apr 7, 2026
cb999f0
adjust size of the filter button
calebwongg Apr 7, 2026
62a787c
display sort filter as all uppercase
calebwongg Apr 7, 2026
6fa9bab
move search filter to course pane button row
calebwongg Apr 14, 2026
56ef37c
remove from sectiontable row
calebwongg Apr 14, 2026
12507c5
consistent styling
calebwongg Apr 14, 2026
71cbb09
Merge branch 'main' of https://github.com/icssc/AntAlmanac into cwong…
calebwongg Apr 14, 2026
d5f633a
remove enrollment filter
calebwongg Apr 14, 2026
80c5004
update search options
calebwongg Apr 14, 2026
9a27222
feat: search by gpa
calebwongg Apr 21, 2026
b520f19
delete day filtering
calebwongg Apr 21, 2026
8f2fa33
refactor grade query logic to avoid dupe queries and race conditions'
calebwongg Apr 21, 2026
962dec9
prevent promise from failing on grade queries
calebwongg Apr 21, 2026
e05251e
fix: merge conflicts
calebwongg May 5, 2026
2ffc12d
feat: filter sorts whole courses instead of individual sections
calebwongg May 5, 2026
9e8d5e4
undo random prettier formats to clean diff
calebwongg May 5, 2026
870d9f1
fix: set gpa filter hook to falback onto the cache
calebwongg May 6, 2026
b1c0ca1
fix truthiness check on averagegpa
calebwongg May 6, 2026
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 @@ -16,6 +16,7 @@ import { usePostHog } from 'posthog-js/react';
import { useCallback, useMemo, useState } from 'react';

import { NotificationsDialog } from '$components/RightPane/AddedCourses/Notifications/NotificationsDialog';
import { SearchFilter } from '$components/RightPane/SectionTable/SearchFilter';
import analyticsEnum, { logAnalytics } from '$lib/analytics/analytics';
import { useColumnStore, SECTION_TABLE_COLUMNS, type SectionTableColumn } from '$stores/ColumnStore';

Expand Down Expand Up @@ -180,6 +181,7 @@ export function CoursePaneButtonRow(props: CoursePaneButtonRowProps) {

<ColumnToggleDropdown />
<NotificationsDialog buttonSx={buttonSx} />
<SearchFilter buttonSx={buttonSx} />
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Sort } from '@mui/icons-material';
import { IconButton, Menu, MenuItem, Tooltip, type SxProps } from '@mui/material';
import { useCallback, useState } from 'react';

import { SORT_OPTIONS, useSectionFilterStore, type SortOption } from '$stores/SectionFilterStore';

interface SearchFilterProps {
buttonSx?: SxProps;
}

export function SearchFilter({ buttonSx }: SearchFilterProps) {
const { sortBy, setSortBy } = useSectionFilterStore();
const [anchorEl, setAnchorEl] = useState<HTMLElement>();
const open = Boolean(anchorEl);

const handleClick = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
}, []);

const handleClose = useCallback(() => {
setAnchorEl(undefined);
}, []);

const handleSelect = useCallback(
(value: SortOption) => {
setSortBy(value);
handleClose();
},
[setSortBy, handleClose]
);

const currentLabel = SORT_OPTIONS.find((o) => o.value === sortBy)?.label ?? 'Default';

return (
<>
<Tooltip title={`Sort: ${currentLabel}`}>
<IconButton onClick={handleClick} sx={buttonSx}>
<Sort />
</IconButton>
</Tooltip>

<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
{SORT_OPTIONS.map((option) => (
<MenuItem
key={option.value}
selected={option.value === sortBy}
onClick={() => handleSelect(option.value)}
>
{option.label}
</MenuItem>
))}
</Menu>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ function SectionTable(props: SectionTableProps) {
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: '4px',
marginBottom: '8px',
marginTop: '4px',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,55 @@
import { TableBody } from '@mui/material';
import { AACourse, AASection } from '@packages/antalmanac-types';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';

import { SectionTableBodyRow } from '$components/RightPane/SectionTable/SectionTableBody/SectionTableBodyRow';
import { AnalyticsCategory } from '$lib/analytics/analytics';
import AppStore from '$stores/AppStore';
import { useSectionFilterStore, type SortOption } from '$stores/SectionFilterStore';
import { normalizeTime, parseDaysString } from '$stores/calendarizeHelpers';

function getMeetingStartMinutes(section: AASection): number {
const meeting = section.meetings[0];
if (!meeting || meeting.timeIsTBA) return Infinity;
return meeting.startTime.hour * 60 + meeting.startTime.minute;
}

function getMeetingDays(section: AASection): string {
const meeting = section.meetings[0];
if (!meeting || meeting.timeIsTBA) return '';
return meeting.days;
}

const STATUS_ORDER: Record<string, number> = { OPEN: 0, NewOnly: 1, Waitl: 2, FULL: 3, '': 4 };

function sortSections(sections: AASection[], sortBy: SortOption): AASection[] {
Comment thread
calebwongg marked this conversation as resolved.
Outdated
if (sortBy === 'default') return sections;

return [...sections].sort((a, b) => {
switch (sortBy) {
case 'status':
return (STATUS_ORDER[a.status] ?? 4) - (STATUS_ORDER[b.status] ?? 4);

case 'time_asc':
return getMeetingStartMinutes(a) - getMeetingStartMinutes(b);

case 'days_mwf': {
const aMatch = /[MWF]/.test(getMeetingDays(a)) ? 0 : 1;
const bMatch = /[MWF]/.test(getMeetingDays(b)) ? 0 : 1;
return aMatch - bMatch;
}

case 'days_tuth': {
const aMatch = /Tu|Th/.test(getMeetingDays(a)) ? 0 : 1;
const bMatch = /Tu|Th/.test(getMeetingDays(b)) ? 0 : 1;
return aMatch - bMatch;
}
default:
return 0;
}
});
}

interface SectionTableBodyProps {
courseDetails: AACourse;
term: string;
Expand All @@ -24,6 +67,7 @@ export function SectionTableBody({
analyticsCategory,
formattedTime,
}: SectionTableBodyProps) {
const { sortBy } = useSectionFilterStore();
const [calendarEvents, setCalendarEvents] = useState(() => AppStore.getCourseEventsInCalendar());

/**
Expand Down Expand Up @@ -82,9 +126,14 @@ export function SectionTableBody({
};
}, [updateCalendarEvents]);

const sortedSections = useMemo(
() => sortSections(courseDetails.sections, sortBy),
[courseDetails.sections, sortBy]
);

return (
<TableBody>
{courseDetails.sections.map((section) => {
{sortedSections.map((section) => {
const conflict = scheduleConflict(section);

return (
Expand Down
21 changes: 21 additions & 0 deletions apps/antalmanac/src/stores/SectionFilterStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { create } from 'zustand';

export const SORT_OPTIONS = [
{ value: 'default', label: 'Default' },
Comment thread
calebwongg marked this conversation as resolved.
{ value: 'status', label: 'Status' },
{ value: 'time_asc', label: 'Time' },
{ value: 'days_mwf', label: 'Date: MWF' },
Comment thread
calebwongg marked this conversation as resolved.
Outdated
{ value: 'days_tuth', label: 'Date: TuTh' },
] as const;

export type SortOption = (typeof SORT_OPTIONS)[number]['value'];

interface SectionFilterStore {
sortBy: SortOption;
setSortBy: (option: SortOption) => void;
}

export const useSectionFilterStore = create<SectionFilterStore>((set) => ({
sortBy: 'default',
setSortBy: (sortBy) => set({ sortBy }),
}));
Loading