Files
UT-Registration-Plus/src/views/hooks/useFlattenedCourseSchedule.ts
Samuel Gunter 227de53e84 feat: show async courses in the bottom bar (#204)
* feat: show async courses in the bottom bar

* fix: hide "Async/Other" header when there are no async courses, off-by-n error

(where n is the number async courses)

* refactor: move types closer to map instead of weird "as boolean"

* refactor: move satisfies to return type
2024-03-29 00:01:23 -05:00

173 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { Course, StatusType } from '@shared/types/Course';
import type { CourseMeeting } from '@shared/types/CourseMeeting';
import type { UserSchedule } from '@shared/types/UserSchedule';
import type { CalendarCourseCellProps } from '@views/components/calendar/CalendarCourseCell';
import useSchedules from './useSchedules';
const dayToNumber = {
Monday: 0,
Tuesday: 1,
Wednesday: 2,
Thursday: 3,
Friday: 4,
Saturday: 5,
Sunday: 6,
} as const satisfies Record<string, number>;
interface CalendarGridPoint {
dayIndex: number;
startIndex: number;
endIndex: number;
}
// interface componentProps {
// calendarCourseCellProps: CalendarCourseCellProps;
// }
/**
* Return type of useFlattenedCourseSchedule
*/
export interface CalendarGridCourse {
calendarGridPoint: CalendarGridPoint;
componentProps: CalendarCourseCellProps;
course: Course;
async: boolean;
gridColumnStart?: number;
gridColumnEnd?: number;
totalColumns?: number;
}
/**
* Represents a flattened course schedule.
*/
export interface FlattenedCourseSchedule {
courseCells: CalendarGridCourse[];
activeSchedule: UserSchedule;
}
/**
* Converts minutes to an index value.
* @param minutes The number of minutes.
* @returns The index value.
*/
export const convertMinutesToIndex = (minutes: number): number => Math.floor((minutes - 420) / 30);
/**
* Get the active schedule, and convert it to be render-able into a calendar.
* @returns CalendarGridCourse
*/
export function useFlattenedCourseSchedule(): FlattenedCourseSchedule {
const [activeSchedule] = useSchedules();
const processedCourses = activeSchedule.courses
.flatMap(course => {
const { status, courseDeptAndInstr, meetings } = extractCourseInfo(course);
if (meetings.length === 0) {
return processAsyncCourses({ courseDeptAndInstr, status, course });
}
return meetings.flatMap(meeting => processInPersonMeetings(meeting, courseDeptAndInstr, status, course));
})
.sort(sortCourses);
return {
courseCells: processedCourses,
activeSchedule,
};
}
/**
* Function to extract and format basic course information
*/
function extractCourseInfo(course: Course) {
const {
status,
schedule: { meetings },
} = course;
const courseDeptAndInstr = `${course.department} ${course.number} ${course.instructors[0]?.lastName}`;
return { status, courseDeptAndInstr, meetings, course };
}
/**
* Function to process each in-person class into its distinct meeting objects for calendar grid
*/
function processAsyncCourses({
courseDeptAndInstr,
status,
course,
}: {
courseDeptAndInstr: string;
status: StatusType;
course: Course;
}): CalendarGridCourse[] {
return [
{
calendarGridPoint: {
dayIndex: -1,
startIndex: -1,
endIndex: -1,
},
componentProps: {
courseDeptAndInstr,
status,
colors: course.colors,
},
course,
async: true,
},
];
}
/**
* Function to process each in-person class into its distinct meeting objects for calendar grid
*/
function processInPersonMeetings(
meeting: CourseMeeting,
courseDeptAndInstr: string,
status: StatusType,
course: Course
): CalendarGridCourse[] {
const { days, startTime, endTime, location } = meeting;
const midnightIndex = 1440;
const normalizingTimeFactor = 720;
const time = meeting.getTimeString({ separator: '-', capitalize: true });
const timeAndLocation = `${time}${location ? ` - ${location.building}` : ''}`;
const normalizedStartTime = startTime >= midnightIndex ? startTime - normalizingTimeFactor : startTime;
const normalizedEndTime = endTime >= midnightIndex ? endTime - normalizingTimeFactor : endTime;
return days.map(day => ({
calendarGridPoint: {
dayIndex: dayToNumber[day],
startIndex: convertMinutesToIndex(normalizedStartTime),
endIndex: convertMinutesToIndex(normalizedEndTime),
},
componentProps: {
courseDeptAndInstr,
timeAndLocation,
status,
colors: course.colors,
},
course,
async: false,
}));
}
/**
* Utility function to sort courses for the calendar grid
*/
function sortCourses(a: CalendarGridCourse, b: CalendarGridCourse): number {
const { dayIndex: dayIndexA, startIndex: startIndexA, endIndex: endIndexA } = a.calendarGridPoint;
const { dayIndex: dayIndexB, startIndex: startIndexB, endIndex: endIndexB } = b.calendarGridPoint;
if (dayIndexA !== dayIndexB) {
return dayIndexA - dayIndexB;
}
if (startIndexA !== startIndexB) {
return startIndexA - startIndexB;
}
return endIndexA - endIndexB;
}