import type { Course } from '@shared/types/Course'; import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell'; import Text from '@views/components/common/Text/Text'; import { ColorPickerProvider } from '@views/contexts/ColorPickerContext'; import { useSentryScope } from '@views/contexts/SentryContext'; import type { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule'; import React, { Fragment } from 'react'; import CalendarCell from './CalendarGridCell'; import { calculateCourseCellColumns } from './utils'; const daysOfWeek = ['MON', 'TUE', 'WED', 'THU', 'FRI']; const hoursOfDay = Array.from({ length: 14 }, (_, index) => index + 8); const IS_STORYBOOK = import.meta.env.STORYBOOK; interface Props { courseCells?: CalendarGridCourse[]; saturdayClass?: boolean; setCourse: React.Dispatch>; } function CalendarHour({ hour }: { hour: number }) { return (
{(hour % 12 === 0 ? 12 : hour % 12) + (hour < 12 ? ' AM' : ' PM')}
); } function makeGridRow(row: number, cols: number): JSX.Element { const hour = hoursOfDay[row]!; return (
{[...Array(cols).keys()].map(col => ( ))} ); } // TODO: add Saturday class support /** * Grid of CalendarGridCell components forming the user's course schedule calendar view * * @param courseCells - The courses to display on the calendar * @param saturdayClass - Whether the user has a Saturday class * @param setCourse - Function to set the course to display in the course details panel * @returns The CalendarGrid component */ export default function CalendarGrid({ courseCells, saturdayClass: _saturdayClass, // TODO: implement/move away from props setCourse, }: React.PropsWithChildren): JSX.Element { return (
{/* Cover top left corner of grid, so time gets cut off at the top of the partial border */}
{/* Displaying day labels */} {daysOfWeek.map(day => (
{/* Partial border height because that's what Isaiah wants */}
{/* Alignment for text */}
{day}
))} {/* empty slot, for alignment */}
{/* time tick for the first hour */}
{[...Array(13).keys()].map(i => makeGridRow(i, 5))} {Array(6) .fill(1) .map((_, i) => ( // Key suppresses warning about duplicate keys, // and index is fine because it doesn't change between renders // eslint-disable-next-line react/no-array-index-key
))} {courseCells && }
); } interface AccountForCourseConflictsProps { courseCells: CalendarGridCourse[]; setCourse: React.Dispatch>; } // TODO: Possibly refactor to be more concise // TODO: Deal with react strict mode (wacky movements) function AccountForCourseConflicts({ courseCells, setCourse }: AccountForCourseConflictsProps): JSX.Element[] { // Sentry is not defined in storybook. // This is a valid use case for a condition hook, since IS_STORYBOOK is determined at build time, // it doesn't change between renders. // eslint-disable-next-line react-hooks/rules-of-hooks const [sentryScope] = IS_STORYBOOK ? [undefined] : useSentryScope(); // Groups by dayIndex to identify overlaps const days = courseCells.reduce( (acc, cell: CalendarGridCourse) => { const { dayIndex } = cell.calendarGridPoint; if (acc[dayIndex] === undefined) { acc[dayIndex] = []; } acc[dayIndex]!.push(cell); return acc; }, {} as Record ); // Check for overlaps within each day and adjust gridColumnIndex and totalColumns Object.values(days).forEach((dayCells: CalendarGridCourse[], idx) => { try { calculateCourseCellColumns(dayCells); } catch (error) { console.error(`Error calculating course cell columns ${idx}`, error); if (sentryScope) { sentryScope.captureException(error); } } }); return courseCells .filter(block => !block.async) .map(block => { const { courseDeptAndInstr, timeAndLocation, status } = block.componentProps; return (
setCourse(block.course)} blockData={block} />
); }); }