Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
448b426
Optimize AddedSectionsGrid courses initialization
Choollol Mar 7, 2026
ee5c649
Move drag and drop components into higher level folder
Choollol Mar 7, 2026
2bfec35
Move ScheduleSelect schedule reordering logic out of SortableList
Choollol Mar 7, 2026
a252df3
Add sx and disableHorizontalScroll props to SortableList
Choollol Mar 7, 2026
1325dce
Implement added courses grid reordering (ephemeral)
Choollol Mar 7, 2026
2925084
Create getCourseId utility
Choollol Mar 8, 2026
ebef1d0
Create moveArrayElement utility
Choollol Mar 8, 2026
1be36e9
Save course ordering
Choollol Mar 8, 2026
1a75259
Clean up handleCourseOrderChange
Choollol Mar 8, 2026
ae2fe90
Fix insert index
Choollol Mar 8, 2026
9cab57b
Fix drag handle to work with mobile touch
Choollol Mar 8, 2026
b395dfc
Merge branch 'main' of https://github.com/icssc/AntAlmanac into AA-90…
Choollol Mar 11, 2026
8d4846b
Update added courses reordering logic
Choollol Mar 11, 2026
01ad30a
Use mergeSx instead of spreading sx
Choollol Mar 12, 2026
520cb3d
Microoptimize course searching in AddedCoursePane
Choollol Mar 12, 2026
72e1bbe
Use verticalListSortingStrategy
Choollol Mar 12, 2026
8fb2e35
Merge branch 'main' of https://github.com/icssc/AntAlmanac into AA-90…
Choollol Apr 1, 2026
9019578
Move handle to left side and change styles
Choollol Apr 6, 2026
4ebd4ba
Merge branch 'main' of https://github.com/icssc/AntAlmanac into AA-90…
Choollol Apr 14, 2026
9c4f8ee
Change drag handle to inherit color instead of hardcoded
Choollol Apr 14, 2026
d4e32e4
Merge branch 'main' of https://github.com/icssc/AntAlmanac into AA-90…
Choollol Apr 15, 2026
3f8eee2
Fix missing moveArrayElements import
Choollol Apr 18, 2026
ea9c0e1
Merge branch 'main' of https://github.com/icssc/AntAlmanac into AA-90…
Choollol Apr 27, 2026
3f689ec
Make cursor "grab" when hovering and dragging
Choollol Apr 27, 2026
b0eff19
Reset cursor when drag is cancelled
Choollol Apr 27, 2026
94af198
Merge branch 'main' of https://github.com/icssc/AntAlmanac into AA-90…
Choollol Apr 28, 2026
69e3afa
Fix missing import
Choollol Apr 28, 2026
88a9f68
Merge branch 'main' of https://github.com/icssc/AntAlmanac into AA-90…
Choollol May 4, 2026
b221b3e
Store indexes of courses
Choollol May 6, 2026
fb55b4e
Fix drag icon being smaller than button
Choollol May 6, 2026
0d5fec0
Delete unused SectionTableProps duplication
Choollol May 6, 2026
02f8b60
Handle collapsible sortable items
Choollol May 7, 2026
11c39c4
Merge branch 'main' of https://github.com/icssc/AntAlmanac into AA-90…
Choollol May 7, 2026
a9abd7c
Make draggingState nullable
Choollol May 7, 2026
77e49db
Show grabbing cursor when dragging
Choollol May 7, 2026
e6453f0
Make missing sections part of collapsible
Choollol May 7, 2026
e8aade1
Treat null-index courses as equal
Choollol May 7, 2026
f937b3e
Fix horizontal scrolling behavior to be consistent with before
Choollol May 7, 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
8 changes: 8 additions & 0 deletions apps/antalmanac/src/actions/ActionTypesStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ export interface ReorderScheduleAction {
to: number;
}

export interface ReorderAddedCoursesAction {
type: 'reorderAddedCourses';
scheduleIndex: number;
movedCourseId: string;
nextCourseId: string | null;
}

export interface ChangeCourseColorAction {
type: 'changeCourseColor';
sectionCode: string;
Expand All @@ -103,6 +110,7 @@ export type ActionType =
| DeleteScheduleAction
| CopyScheduleAction
| ReorderScheduleAction
| ReorderAddedCoursesAction
| ChangeCourseColorAction
| UndoRedoAction;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import { addCustomEvent, editCustomEvent } from '$actions/AppStoreActions';
import { DaySelector } from '$components/Calendar/Toolbar/CustomEventDialog/DaySelector';
import { ScheduleSelector } from '$components/Calendar/Toolbar/CustomEventDialog/ScheduleSelector';
import { BuildingSelect, ExtendedBuilding } from '$components/inputs/BuildingSelect';
import analyticsEnum, { logAnalytics } from '$lib/analytics/analytics';
import AppStore from '$stores/AppStore';
import { Add, Edit } from '@mui/icons-material';
import {
Button,
Expand All @@ -14,13 +20,6 @@ import type { RepeatingCustomEvent } from '@packages/antalmanac-types';
import { usePostHog } from 'posthog-js/react';
import { useCallback, useEffect, useState } from 'react';

import { addCustomEvent, editCustomEvent } from '$actions/AppStoreActions';
import { DaySelector } from '$components/Calendar/Toolbar/CustomEventDialog/DaySelector';
import { ScheduleSelector } from '$components/Calendar/Toolbar/CustomEventDialog/ScheduleSelector';
import { BuildingSelect, ExtendedBuilding } from '$components/inputs/BuildingSelect';
import analyticsEnum, { logAnalytics } from '$lib/analytics/analytics';
import AppStore from '$stores/AppStore';

interface CustomEventDialogProps {
customEvent?: RepeatingCustomEvent;
onDialogClose?: () => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { PostHog, usePostHog } from 'posthog-js/react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';

import { changeCurrentSchedule } from '$actions/AppStoreActions';
import { SortableList } from '$components/Calendar/Toolbar/ScheduleSelect/drag-and-drop/SortableList';
import { AddScheduleButton } from '$components/Calendar/Toolbar/ScheduleSelect/schedule-select-buttons/AddScheduleButton';
import { DeleteScheduleButton } from '$components/Calendar/Toolbar/ScheduleSelect/schedule-select-buttons/DeleteScheduleButton';
import { RenameScheduleButton } from '$components/Calendar/Toolbar/ScheduleSelect/schedule-select-buttons/RenameScheduleButton';
import { CopyScheduleButton } from '$components/buttons/Copy';
import { SortableList } from '$components/drag-and-drop/SortableList';
import analyticsEnum, { logAnalytics } from '$lib/analytics/analytics';
import AppStore from '$stores/AppStore';
import { scheduleComponentsToggleStore } from '$stores/ScheduleComponentsToggleStore';
Expand Down Expand Up @@ -80,6 +80,11 @@ export function SelectSchedulePopover() {
setCurrentScheduleIndex(AppStore.getCurrentScheduleIndex());
}, []);

const handleSortableListChange = (schedules: ScheduleItem[], activeIndex: number, overIndex: number) => {
setScheduleMapping(schedules);
AppStore.reorderSchedule(activeIndex, overIndex);
};

useEffect(() => {
AppStore.on('addedCoursesChange', handleScheduleIndexChange);
AppStore.on('customEventsChange', handleScheduleIndexChange);
Expand Down Expand Up @@ -161,7 +166,7 @@ export function SelectSchedulePopover() {
<Box padding={1}>
<SortableList
items={scheduleMappingToUse}
onChange={setScheduleMapping}
onChange={handleSortableListChange}
renderItem={(item) => {
const index = scheduleMappingToUse.indexOf(item);
return (
Expand Down
3 changes: 1 addition & 2 deletions apps/antalmanac/src/components/LoadingScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { BLUE } from '$src/globals';
import { Dialog, DialogContent, LinearProgress, Stack, Box } from '@mui/material';
import { useState, useEffect } from 'react';

import { Logo } from './Header/Logo';

import { BLUE } from '$src/globals';

const FUN_FACTS = [
'Did you know? AntAlmanac is maintained by the ICS Student Council at UCI!',
'AntAlmanac was created in 2018 by a small group of students under the leadership of @the-rango.',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { updateScheduleNote } from '$actions/AppStoreActions';
import { ClearScheduleButton } from '$components/buttons/Clear';
import { CopyScheduleButton } from '$components/buttons/Copy';
import { SortableList } from '$components/drag-and-drop/SortableList';
import { EmptyState } from '$components/EmptyState';
import CustomEventDetailView from '$components/RightPane/AddedCourses/CustomEventDetailView';
import { getMissingSections } from '$components/RightPane/AddedCourses/getMissingSections';
Expand All @@ -12,7 +13,9 @@ import { clickToCopy } from '$lib/helpers';
import { LIGHT_BLUE } from '$src/globals';
import AppStore from '$stores/AppStore';
import { scheduleComponentsToggleStore } from '$stores/ScheduleComponentsToggleStore';
import { getCourseId } from '$stores/scheduleHelpers';
import { useTabStore } from '$stores/TabStore';
import { verticalListSortingStrategy } from '@dnd-kit/sortable';
import { MenuBook } from '@mui/icons-material';
import { Box, Chip, Paper, SxProps, TextField, Tooltip, Typography, useTheme } from '@mui/material';
import { AACourse } from '@packages/antalmanac-types';
Expand All @@ -36,6 +39,7 @@ const buttonSx: SxProps = {

export interface CourseWithTerm extends AACourse {
term: string;
id: string;
}

const NOTE_MAX_LEN = 5000;
Expand All @@ -46,12 +50,8 @@ function getCourses() {
const formattedCourses: CourseWithTerm[] = [];

for (const course of currentCourses) {
let formattedCourse = formattedCourses.find(
(needleCourse) =>
needleCourse.courseNumber === course.courseNumber &&
needleCourse.deptCode === course.deptCode &&
needleCourse.courseTitle === course.courseTitle
);
const courseId = getCourseId(course);
let formattedCourse = formattedCourses.find((needleCourse) => getCourseId(needleCourse) === courseId);

const sectionUpdatedAt = course.section?.updatedAt ?? null;

Expand All @@ -75,6 +75,7 @@ function getCourses() {
},
],
updatedAt: sectionUpdatedAt ?? null,
id: getCourseId(course),
};
formattedCourses.push(formattedCourse);
}
Expand Down Expand Up @@ -304,10 +305,23 @@ function SkeletonSchedule() {
}

function AddedSectionsGrid() {
const [courses, setCourses] = useState(getCourses());
const [courses, setCourses] = useState(getCourses);
const [scheduleNames, setScheduleNames] = useState(AppStore.getScheduleNames());
const [scheduleIndex, setScheduleIndex] = useState(AppStore.getCurrentScheduleIndex());

const handleCourseOrderChange = (updatedCourses: CourseWithTerm[], _activeIndex: number, overIndex: number) => {
setCourses(updatedCourses);

const movedCourse = updatedCourses[overIndex];
const nextConsecutiveCourse = overIndex + 1 !== updatedCourses.length ? updatedCourses[overIndex + 1] : null;

AppStore.reorderAddedCourses(
AppStore.getCurrentScheduleIndex(),
getCourseId(movedCourse),
nextConsecutiveCourse !== null ? getCourseId(nextConsecutiveCourse) : null
);
};

useEffect(() => {
const handleCoursesChange = () => {
setCourses(getCourses());
Expand Down Expand Up @@ -377,24 +391,30 @@ function AddedSectionsGrid() {
}}
/>
)}
<Box display="flex" flexDirection="column" gap={1}>
{courses.map((course) => {
<SortableList
disableHorizontalScroll
items={courses}
onChange={handleCourseOrderChange}
sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}
sortingStrategy={verticalListSortingStrategy}
renderItem={(course: CourseWithTerm) => {
const missingSections = getMissingSections(course);

return (
<Box key={course.deptCode + course.courseNumber + course.courseTitle}>
<SortableList.Item id={course.id}>
<SectionTableLazyWrapper
courseDetails={course}
term={course.term}
allowHighlight={false}
analyticsCategory={analyticsEnum.addedClasses}
scheduleNames={scheduleNames}
missingSections={missingSections}
sortable
/>
</Box>
</SortableList.Item>
);
})}
</Box>
}}
/>
</Box>

<CustomEventsBox />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { useCallback, useEffect, useState } from 'react';

import { LabeledAutocomplete } from '$components/RightPane/CoursePane/SearchForm/LabeledInputs/LabeledAutocomplete';
import RightPaneStore from '$components/RightPane/RightPaneStore';
import { useDepartments } from '$hooks/useDepartments';
import { getLocalStorageRecentlySearched, setLocalStorageRecentlySearched } from '$lib/localStorage';
import { useCallback, useEffect, useState } from 'react';

const DEFAULT_DEPARTMENTS: Record<string, string> = {
ALL: 'ALL: Include All Departments',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import PrereqTree from '$components/RightPane/SectionTable/PrereqTree';
import { useIsMobile } from '$hooks/useIsMobile';
import analyticsEnum, { AnalyticsCategory, logAnalytics } from '$lib/analytics/analytics';
import trpc from '$lib/api/trpc';
import { InfoOutlined } from '@mui/icons-material';
import { Box, Button, Popover, Skeleton } from '@mui/material';
import type { PrerequisiteTree } from '@packages/antalmanac-types';
import { usePostHog } from 'posthog-js/react';
import { useState } from 'react';

import PrereqTree from '$components/RightPane/SectionTable/PrereqTree';
import { useIsMobile } from '$hooks/useIsMobile';
import analyticsEnum, { AnalyticsCategory, logAnalytics } from '$lib/analytics/analytics';
import trpc from '$lib/api/trpc';

const noCourseInfo = {
id: '',
department: '',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Assessment, Route, ShowChart as ShowChartIcon } from '@mui/icons-material';
import { Alert, Box, Paper, Table, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
import { Alert, Box, Button, Paper, Table, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
import { useMemo } from 'react';

import { CourseInfoBar } from '$components/RightPane/SectionTable/CourseInfo/CourseInfoBar';
Expand All @@ -10,6 +10,7 @@ import { EnrollmentHistoryPopup } from '$components/RightPane/SectionTable/Enrol
import GradesPopup from '$components/RightPane/SectionTable/GradesPopup';
import { SectionTableProps } from '$components/RightPane/SectionTable/SectionTable.types';
import { SectionTableBody } from '$components/RightPane/SectionTable/SectionTableBody/SectionTableBody';
import { SortableList } from '$components/drag-and-drop/SortableList';
import { useIsMobile } from '$hooks/useIsMobile';
import analyticsEnum from '$lib/analytics/analytics';
import { useColumnStore, SECTION_TABLE_COLUMNS, type SectionTableColumn } from '$stores/ColumnStore';
Expand Down Expand Up @@ -67,8 +68,15 @@ const tableHeaderColumns: Record<Exclude<SectionTableColumn, 'action'>, TableHea
};
const tableHeaderColumnEntries = Object.entries(tableHeaderColumns);

function SectionTable(props: SectionTableProps) {
const { courseDetails, term, allowHighlight, scheduleNames, analyticsCategory, missingSections = [] } = props;
function SectionTable({
courseDetails,
term,
allowHighlight,
scheduleNames,
analyticsCategory,
missingSections = [],
sortable = false,
}: SectionTableProps) {
const { isMilitaryTime } = useTimeFormatStore();

const [activeColumns] = useColumnStore((store) => [store.activeColumns]);
Expand Down Expand Up @@ -101,7 +109,7 @@ function SectionTable(props: SectionTableProps) {
}, [activeColumns]);

return (
<>
<Box>
<Box
sx={{
display: 'flex',
Expand All @@ -110,6 +118,12 @@ function SectionTable(props: SectionTableProps) {
marginTop: '4px',
}}
>
{sortable ? (
Comment thread
Choollol marked this conversation as resolved.
<Button variant="contained" color="secondary" sx={{ padding: 0, minWidth: 0, minHeight: 0 }}>
<SortableList.DragHandle iconSx={{ color: 'inherit' }} />
</Button>
) : null}

<CourseInfoBar
deptCode={courseDetails.deptCode}
courseTitle={courseDetails.courseTitle}
Expand Down Expand Up @@ -218,7 +232,7 @@ function SectionTable(props: SectionTableProps) {
/>
</Table>
</TableContainer>
</>
</Box>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export interface SectionTableProps {
analyticsCategory: AnalyticsCategory;
updatedAt?: string;
missingSections?: string[];
sortable?: boolean;
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
import { Box } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { SxProps, Theme, useTheme } from '@mui/material/styles';
import { mergeSx } from '@mui/x-date-pickers/internals';
import { useContext } from 'react';

import { SortableItemContext } from '$components/Calendar/Toolbar/ScheduleSelect/drag-and-drop/SortableItem';
import { SortableItemContext } from '$components/drag-and-drop/SortableItem';

interface DragHandleProps {
disabled?: boolean;
iconSx?: SxProps<Theme>;
}

export function DragHandle({ disabled = false }: DragHandleProps) {
export function DragHandle({ disabled = false, iconSx }: DragHandleProps) {
const { attributes, listeners, ref } = useContext(SortableItemContext);
const theme = useTheme();

Expand All @@ -24,6 +26,7 @@ export function DragHandle({ disabled = false }: DragHandleProps) {
justifyContent: 'center',
cursor: disabled ? 'auto' : 'pointer',
borderRadius: 1,
touchAction: 'none',
'&:hover': {
backgroundColor: disabled ? 'transparent' : 'rgba(0, 0, 0, 0.1)',
},
Expand All @@ -33,9 +36,12 @@ export function DragHandle({ disabled = false }: DragHandleProps) {
}}
>
<DragIndicatorIcon
sx={{
color: disabled ? 'gray' : theme.palette.mode === 'light' ? 'black' : 'white',
}}
sx={mergeSx(
{
color: disabled ? 'gray' : theme.palette.mode === 'light' ? 'black' : 'white',
},
iconSx
)}
/>
</Box>
);
Expand Down
Loading
Loading