Skip to content

Commit

Permalink
perf: do not re-render entire time slot page when slot changes
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbenincasa committed Nov 8, 2024
1 parent b4fb2be commit d3d1242
Show file tree
Hide file tree
Showing 10 changed files with 360 additions and 223 deletions.
5 changes: 3 additions & 2 deletions shared/src/util/seq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function intersperse<T>(arr: T[], v: T, makeLast: boolean = false): T[] {
*/
export function collect<T, U>(
arr: T[] | null | undefined,
f: (t: T) => U | null | undefined,
f: (t: T, index: number, arr: T[]) => U | null | undefined,
): U[] {
if (isNil(arr)) {
return [];
Expand All @@ -18,8 +18,9 @@ export function collect<T, U>(
const func = isFunction(f) ? f : (t: T) => t[f];

const results: U[] = [];
let i = 0;
for (const el of arr) {
const res = func(el);
const res = func(el, i++, arr);
if (!isNil(res)) {
results.push(res);
}
Expand Down
1 change: 0 additions & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
"@tanstack/router-devtools": "^1.36.0",
"@tanstack/router-vite-plugin": "^1.35.4",
"@types/lodash-es": "4.17.9",
"@types/lodash": "4.17.10",
"@types/pluralize": "^0.0.33",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
Expand Down
31 changes: 13 additions & 18 deletions web/src/components/slot_scheduler/AddTimeSlotButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,27 @@ import { TimeSlot } from '@tunarr/types/api';
import dayjs from 'dayjs';
import { maxBy } from 'lodash-es';
import { useCallback } from 'react';
import { Control, UseFormSetValue, useWatch } from 'react-hook-form';
import { UseFieldArrayAppend } from 'react-hook-form';
import { TimeSlotForm } from '../../pages/channels/TimeSlotEditorPage.tsx';

export const AddTimeSlotButton = ({
control,
setValue,
slots,
append,
}: AddTimeSlotButtonProps) => {
const currentSlots = useWatch({ control, name: 'slots' });
// const currentSlots = useWatch({ control, name: 'slots' });

const addSlot = useCallback(() => {
const maxSlot = maxBy(currentSlots, (p) => p.startTime);
const maxSlot = maxBy(slots, (p) => p.startTime);
const newStartTime = maxSlot
? dayjs.duration(maxSlot.startTime).add(1, 'hour')
: dayjs.duration(0);

const newSlots: TimeSlot[] = [
...currentSlots,
{
programming: { type: 'flex' },
startTime: newStartTime.asMilliseconds(),
order: 'next',
},
];

setValue('slots', newSlots, { shouldDirty: true });
}, [currentSlots, setValue]);
append({
programming: { type: 'flex' },
startTime: newStartTime.asMilliseconds(),
order: 'next',
});
}, [append, slots]);

return (
<Button
Expand All @@ -43,6 +38,6 @@ export const AddTimeSlotButton = ({
};

type AddTimeSlotButtonProps = {
control: Control<TimeSlotForm>;
setValue: UseFormSetValue<TimeSlotForm>;
slots: TimeSlot[];
append: UseFieldArrayAppend<TimeSlotForm, 'slots'>;
};
17 changes: 17 additions & 0 deletions web/src/components/slot_scheduler/ClearSlotsButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ClearAll } from '@mui/icons-material';
import { Button } from '@mui/material';

type Props = {
fields: { id: string }[];
remove: () => void;
};

export const ClearSlotsButton = ({ fields, remove }: Props) => {
return (
fields.length > 0 && (
<Button onClick={() => remove()} sx={{ mr: 1 }} startIcon={<ClearAll />}>
Clear All
</Button>
)
);
};
65 changes: 65 additions & 0 deletions web/src/components/slot_scheduler/MissingProgramsAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { slotOptionIsScheduled } from '@/helpers/slotSchedulerUtil';
import { useSlotProgramOptions } from '@/hooks/programming_controls/useSlotProgramOptions';
import { TimeSlotForm } from '@/pages/channels/TimeSlotEditorPage';
import { ExpandLess, ExpandMore } from '@mui/icons-material';
import { Alert, Collapse, IconButton, ListItem } from '@mui/material';
import { isEmpty, map, reject } from 'lodash-es';
import pluralize from 'pluralize';
import { useMemo } from 'react';
import { Control, useWatch } from 'react-hook-form';
import { useToggle } from 'usehooks-ts';

type Props = {
control: Control<TimeSlotForm>;
};

export const MissingProgramsAlert = ({ control }: Props) => {
const programOptions = useSlotProgramOptions();
const currentSlots = useWatch({ control, name: 'slots' });
const [unscheduledOpen, toggleUnscheduledOpen] = useToggle(false);

const unscheduledOptions = useMemo(
() =>
reject(programOptions, (item) =>
slotOptionIsScheduled(currentSlots, item),
),
[currentSlots, programOptions],
);

return (
!isEmpty(unscheduledOptions) && (
<Alert
severity="warning"
action={
<IconButton
aria-label="close"
color="inherit"
size="small"
onClick={() => {
toggleUnscheduledOpen();
}}
>
{!unscheduledOpen ? (
<ExpandMore fontSize="inherit" />
) : (
<ExpandLess fontSize="inherit" />
)}
</IconButton>
}
>
There are {unscheduledOptions.length} unscheduled{' '}
{pluralize('program', unscheduledOptions.length)}. Unscheduled items
will be removed from the channel when saving.
<Collapse in={unscheduledOpen}>
<>
{map(unscheduledOptions, (option) => (
<ListItem key={option.value}>
{option.description} ({option.type})
</ListItem>
))}
</>
</Collapse>
</Alert>
)
);
};
Loading

0 comments on commit d3d1242

Please sign in to comment.