fix: course columns on calendar (#587)

* fix: course columns on calendar

* test: new tests for course cell columns, extract calls to help function

* fix: gracefully handle async courses

* refactor: split course cell column logic into multiple functions, add comments, and derive fields

* chore: comments, for-index to for-of

* chore: fix typo

* fix: don't use sentry in storybook contexts

* fix: silence, brand

* refactor: don't rely on calculating columns, find as you go

---------

Co-authored-by: doprz <52579214+doprz@users.noreply.github.com>
This commit is contained in:
Samuel Gunter
2025-05-06 16:07:27 -05:00
committed by GitHub
parent 37471efb74
commit cfb5faa09b
4 changed files with 357 additions and 26 deletions

View File

@@ -2,14 +2,18 @@ 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;
@@ -106,6 +110,12 @@ interface AccountForCourseConflictsProps {
// 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) => {
@@ -120,31 +130,15 @@ function AccountForCourseConflicts({ courseCells, setCourse }: AccountForCourseC
);
// Check for overlaps within each day and adjust gridColumnIndex and totalColumns
Object.values(days).forEach((dayCells: CalendarGridCourse[]) => {
// Sort by start time to ensure proper columnIndex assignment
dayCells.sort((a, b) => a.calendarGridPoint.startIndex - b.calendarGridPoint.startIndex);
dayCells.forEach((cell, _, arr) => {
let columnIndex = 1;
cell.totalColumns = 1;
// Check for overlaps and adjust columnIndex as needed
for (let otherCell of arr) {
if (otherCell !== cell) {
const isOverlapping =
otherCell.calendarGridPoint.startIndex < cell.calendarGridPoint.endIndex &&
otherCell.calendarGridPoint.endIndex > cell.calendarGridPoint.startIndex;
if (isOverlapping) {
// Adjust columnIndex to not overlap with the otherCell
if (otherCell.gridColumnStart && otherCell.gridColumnStart >= columnIndex) {
columnIndex = otherCell.gridColumnStart + 1;
}
cell.totalColumns += 1;
}
}
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);
}
cell.gridColumnStart = columnIndex;
cell.gridColumnEnd = columnIndex + 1;
});
}
});
return courseCells