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:
@@ -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)}
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -83,7 +83,6 @@ export default function Calendar(): JSX.Element {
|
||||
|
||||
<CourseCatalogInjectedPopup
|
||||
course={course}
|
||||
activeSchedule={activeSchedule}
|
||||
onClose={() => setShowPopup(false)}
|
||||
open={showPopup}
|
||||
afterLeave={() => setCourse(null)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user