refactor(UserSchedule): index by a unique id rather than name (#166)

* refactor(UserSchedule): index by a unique id rather than name

* refactor: Update parameter names in schedule function jsdocs

* refactor: change more instances of .name to .id

* refactor: Fix typo in variable name and update references

* refactor: Remove console.log statement

* fix(chromatic): Update ScheduleListItem story

* refactor: remove unused eslint rule
This commit is contained in:
Razboy20
2024-03-14 23:09:04 -05:00
committed by GitHub
parent 5714ed16d7
commit 85769e9d2c
31 changed files with 182 additions and 304 deletions

View File

@@ -79,7 +79,6 @@ export default function CourseCatalogMain({ support }: Props): JSX.Element {
)}
<CourseCatalogInjectedPopup
course={selectedCourse}
activeSchedule={activeSchedule}
show={showPopup}
onClose={() => setShowPopup(false)}
afterLeave={() => setSelectedCourse(null)}

View File

@@ -64,10 +64,10 @@ export default function PopupMain(): JSX.Element {
<ScheduleDropdown>
<List
draggables={schedules}
equalityCheck={(a, b) => a.name === b.name}
itemKey={schedule => schedule.id}
onReordered={reordered => {
const activeSchedule = getActiveSchedule();
const activeIndex = reordered.findIndex(s => s.name === activeSchedule.name);
const activeIndex = reordered.findIndex(s => s.id === activeSchedule.id);
// don't care about the promise
UserScheduleStore.set('schedules', reordered);
@@ -77,9 +77,9 @@ export default function PopupMain(): JSX.Element {
>
{(schedule, handleProps) => (
<ScheduleListItem
name={schedule.name}
schedule={schedule}
onClick={() => {
switchSchedule(schedule.name);
switchSchedule(schedule.id);
}}
dragHandleProps={handleProps}
/>
@@ -98,7 +98,7 @@ export default function PopupMain(): JSX.Element {
activeSchedule.courses = reordered.map(c => c.course);
replaceSchedule(getActiveSchedule(), activeSchedule);
}}
equalityCheck={(a, b) => a.course.uniqueId === b.course.uniqueId}
itemKey={e => e.course.uniqueId}
gap={10}
>
{({ course, colors }, handleProps) => (

View File

@@ -83,7 +83,6 @@ export default function Calendar(): JSX.Element {
<CourseCatalogInjectedPopup
course={course}
activeSchedule={activeSchedule}
onClose={() => setShowPopup(false)}
open={showPopup}
afterLeave={() => setCourse(null)}

View File

@@ -40,8 +40,8 @@ export default function CalendarHeader(): JSX.Element {
<div className='flex items-center gap-2'>
<LogoIcon />
<div className='flex flex-col whitespace-nowrap'>
<Text className='text-lg! text-ut-burntorange font-medium!'>UT Registration</Text>
<Text className='text-lg! text-ut-orange font-medium!'>Plus</Text>
<Text className='text-ut-burntorange text-lg! font-medium!'>UT Registration</Text>
<Text className='text-ut-orange text-lg! font-medium!'>Plus</Text>
</div>
</div>
</div>
@@ -52,7 +52,7 @@ export default function CalendarHeader(): JSX.Element {
totalHours={activeSchedule.hours}
totalCourses={activeSchedule.courses.length}
/>
<Text variant='h4' className='text-xs! text-gray font-medium! leading-normal!'>
<Text variant='h4' className='text-gray text-xs! font-medium! leading-normal!'>
LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)}
</Text>
</div>

View File

@@ -30,7 +30,7 @@ export function CalendarSchedules({ style, dummySchedules, dummyActiveIndex }: P
const [activeSchedule, schedules] = useSchedules();
useEffect(() => {
const index = schedules.findIndex(schedule => schedule.name === activeSchedule.name);
const index = schedules.findIndex(schedule => schedule.id === activeSchedule.id);
if (index !== -1) {
setActiveScheduleIndex(index);
}
@@ -68,10 +68,10 @@ export function CalendarSchedules({ style, dummySchedules, dummyActiveIndex }: P
<List
gap={10}
draggables={schedules}
equalityCheck={(a, b) => a.name === b.name}
itemKey={s => s.id}
onReordered={reordered => {
const activeSchedule = getActiveSchedule();
const activeIndex = reordered.findIndex(s => s.name === activeSchedule.name);
const activeIndex = reordered.findIndex(s => s.id === activeSchedule.id);
// don't care about the promise
UserScheduleStore.set('schedules', reordered);
@@ -80,9 +80,9 @@ export function CalendarSchedules({ style, dummySchedules, dummyActiveIndex }: P
>
{(schedule, handleProps) => (
<ScheduleListItem
name={schedule.name}
schedule={schedule}
onClick={() => {
switchSchedule(schedule.name);
switchSchedule(schedule.id);
}}
dragHandleProps={handleProps}
/>

View File

@@ -3,6 +3,8 @@ import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import type { ReactElement } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import ExtensionRoot from '../ExtensionRoot/ExtensionRoot';
/*
* Ctrl + f dragHandleProps on PopupCourseBlock.tsx for example implementation of drag handle (two lines of code)
*/
@@ -14,13 +16,13 @@ export interface ListProps<T> {
draggables: T[];
children: (draggable: T, handleProps: DraggableProvidedDragHandleProps) => ReactElement;
onReordered: (elements: T[]) => void;
equalityCheck?: (a: T, b: T) => boolean;
itemKey: (item: T) => React.Key;
gap?: number; // Impacts the spacing between items in the list
}
function wrap<T>(draggableElements: T[]) {
return draggableElements.map((element, index) => ({
id: `id:${index}`,
function wrap<T>(draggableElements: T[], keyTransform: ListProps<T>['itemKey']) {
return draggableElements.map(element => ({
id: keyTransform(element),
content: element,
}));
}
@@ -68,20 +70,19 @@ function Item<T>(props: {
* <List draggableElements={elements} />
*/
function List<T>(props: ListProps<T>): JSX.Element {
const [items, setItems] = useState(wrap(props.draggables));
const [items, setItems] = useState(wrap(props.draggables, props.itemKey));
const equalityCheck = props.equalityCheck || ((a, b) => a === b);
const transformFunction = props.children;
useEffect(() => {
// check if the draggables content has *actually* changed
if (
props.draggables.length === items.length &&
props.draggables.every((element, index) => equalityCheck(element, items[index].content))
props.draggables.every((element, index) => props.itemKey(element) === items[index].id)
) {
return;
}
setItems(wrap(props.draggables));
setItems(wrap(props.draggables, props.itemKey));
}, [props.draggables]);
const onDragEnd: OnDragEndResponder = useCallback(
@@ -123,7 +124,9 @@ function List<T>(props: ListProps<T>): JSX.Element {
...style,
}}
>
{transformFunction(items[rubric.source.index].content, provided.dragHandleProps)}
<ExtensionRoot>
{transformFunction(items[rubric.source.index].content, provided.dragHandleProps)}
</ExtensionRoot>
</Item>
);
}}
@@ -135,7 +138,7 @@ function List<T>(props: ListProps<T>): JSX.Element {
style={{ marginBottom: `-${props.gap}px` }}
>
{items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
<Draggable key={item.id} draggableId={item.id.toString()} index={index}>
{draggableProvided => (
<div
ref={draggableProvided.innerRef}

View File

@@ -1,3 +1,4 @@
import type { UserSchedule } from '@shared/types/UserSchedule';
import Text from '@views/components/common/Text/Text';
import useSchedules from '@views/hooks/useSchedules';
import clsx from 'clsx';
@@ -10,7 +11,7 @@ import DragIndicatorIcon from '~icons/material-symbols/drag-indicator';
*/
export type Props = {
style?: React.CSSProperties;
name: string;
schedule: UserSchedule;
dragHandleProps?: Omit<React.HTMLAttributes<HTMLDivElement>, 'className'>;
onClick?: React.DOMAttributes<HTMLDivElement>['onClick'];
};
@@ -18,10 +19,10 @@ export type Props = {
/**
* This is a reusable dropdown component that can be used to toggle the visiblity of information
*/
export default function ScheduleListItem({ style, name, dragHandleProps, onClick }: Props): JSX.Element {
export default function ScheduleListItem({ style, schedule, dragHandleProps, onClick }: Props): JSX.Element {
const [activeSchedule] = useSchedules();
const isActive = useMemo(() => activeSchedule.name === name, [activeSchedule, name]);
const isActive = useMemo(() => activeSchedule.id === schedule.id, [activeSchedule, schedule]);
return (
<div style={{ ...style }} className='items-center rounded bg-white'>
@@ -42,7 +43,7 @@ export default function ScheduleListItem({ style, name, dragHandleProps, onClick
}
)}
/>
<Text variant='p'>{name}</Text>
<Text variant='p'>{schedule.name}</Text>
</div>
</div>
</li>

View File

@@ -1,7 +1,7 @@
import type { Course } from '@shared/types/Course';
import type { UserSchedule } from '@shared/types/UserSchedule';
import type { DialogProps } from '@views/components/common/Dialog/Dialog';
import Dialog from '@views/components/common/Dialog/Dialog';
import useSchedules from '@views/hooks/useSchedules';
import React from 'react';
import Description from './Description';
@@ -10,7 +10,6 @@ import HeadingAndActions from './HeadingAndActions';
export type CourseCatalogInjectedPopupProps = DialogProps & {
course: Course;
activeSchedule: UserSchedule;
};
/**
@@ -23,8 +22,9 @@ export type CourseCatalogInjectedPopupProps = DialogProps & {
* @param {Function} props.onClose - The function to close the popup.
* @returns {JSX.Element} The CourseCatalogInjectedPopup component.
*/
function CourseCatalogInjectedPopup({ course, activeSchedule, ...rest }: CourseCatalogInjectedPopupProps): JSX.Element {
function CourseCatalogInjectedPopup({ course, ...rest }: CourseCatalogInjectedPopupProps): JSX.Element {
const emptyRef = React.useRef<HTMLDivElement>(null);
const [activeSchedule] = useSchedules();
return (
<Dialog className='max-w-[780px] px-6' {...rest} initialFocus={emptyRef}>

View File

@@ -100,9 +100,9 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
const handleAddOrRemoveCourse = async () => {
if (!activeSchedule) return;
if (!courseAdded) {
addCourse({ course, scheduleName: activeSchedule.name });
addCourse({ course, scheduleId: activeSchedule.id });
} else {
removeCourse({ course, scheduleName: activeSchedule.name });
removeCourse({ course, scheduleId: activeSchedule.id });
}
};