Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions docs/data/scheduler/datasets/personal-agenda.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,33 @@ export const initialEvents = [
start: START_OF_FIRST_WEEK.set({ weekday: 4, hour: 10 }),
end: START_OF_FIRST_WEEK.set({ weekday: 4, hour: 11 }),
title: '1-on-1 with Abigail',
resource: 'work',
resource: 'explore',
rrule: { freq: 'WEEKLY', interval: 3, byDay: ['TH'] },
},
{
id: 'work-1on1-hailey',
start: START_OF_FIRST_WEEK.plus({ weeks: 1 }).set({ weekday: 1, hour: 10 }),
end: START_OF_FIRST_WEEK.plus({ weeks: 1 }).set({ weekday: 1, hour: 11 }),
title: '1-on-1 with Hailey',
resource: 'work',
resource: 'data-grid',
rrule: { freq: 'WEEKLY', interval: 3, byDay: ['MO'] },
},
{
id: 'weekly-planning-explore',
start: START_OF_FIRST_WEEK.plus({ weeks: 1 }).set({ weekday: 2, hour: 10 }),
end: START_OF_FIRST_WEEK.plus({ weeks: 1 }).set({ weekday: 2, hour: 11 }),
title: 'Weekly planning',
resource: 'explore',
rrule: { freq: 'WEEKLY', interval: 2, byDay: ['TU'] },
},
{
id: 'weekly-planning-data-grid',
start: START_OF_FIRST_WEEK.set({ weekday: 5, hour: 10 }),
end: START_OF_FIRST_WEEK.set({ weekday: 5, hour: 11 }),
title: 'Weekly planning',
resource: 'data-grid',
rrule: { freq: 'WEEKLY', interval: 2, byDay: ['FR'] },
},
// Non-recurring work events
{
id: 'client-call-1',
Expand Down Expand Up @@ -245,7 +261,22 @@ export const initialEvents = [
];

export const resources = [
{ title: 'Work', id: 'work', eventColor: 'violet' },
{
title: 'Work',
id: 'work',
eventColor: 'violet',
children: [
{
title: 'eXplore Team',
id: 'explore',
eventColor: 'pink',
children: [
{ title: 'Design meetings', id: 'design-meetings', eventColor: 'mauve' },
],
},
{ title: 'Data Grid Team', id: 'data-grid', eventColor: 'blue' },
],
},
{ title: 'Holidays', id: 'holidays', eventColor: 'red' },
{ title: 'Workout', id: 'workout', eventColor: 'jade' },
{ title: 'Birthdays', id: 'birthdays', eventColor: 'lime' },
Expand Down
35 changes: 32 additions & 3 deletions docs/data/scheduler/datasets/personal-agenda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,33 @@ export const initialEvents: CalendarEvent[] = [
start: START_OF_FIRST_WEEK.set({ weekday: 4, hour: 10 }),
end: START_OF_FIRST_WEEK.set({ weekday: 4, hour: 11 }),
title: '1-on-1 with Abigail',
resource: 'work',
resource: 'explore',
rrule: { freq: 'WEEKLY', interval: 3, byDay: ['TH'] },
},
{
id: 'work-1on1-hailey',
start: START_OF_FIRST_WEEK.plus({ weeks: 1 }).set({ weekday: 1, hour: 10 }),
end: START_OF_FIRST_WEEK.plus({ weeks: 1 }).set({ weekday: 1, hour: 11 }),
title: '1-on-1 with Hailey',
resource: 'work',
resource: 'data-grid',
rrule: { freq: 'WEEKLY', interval: 3, byDay: ['MO'] },
},
{
id: 'weekly-planning-explore',
start: START_OF_FIRST_WEEK.plus({ weeks: 1 }).set({ weekday: 2, hour: 10 }),
end: START_OF_FIRST_WEEK.plus({ weeks: 1 }).set({ weekday: 2, hour: 11 }),
title: 'Weekly planning',
resource: 'explore',
rrule: { freq: 'WEEKLY', interval: 2, byDay: ['TU'] },
},
{
id: 'weekly-planning-data-grid',
start: START_OF_FIRST_WEEK.set({ weekday: 5, hour: 10 }),
end: START_OF_FIRST_WEEK.set({ weekday: 5, hour: 11 }),
title: 'Weekly planning',
resource: 'data-grid',
rrule: { freq: 'WEEKLY', interval: 2, byDay: ['FR'] },
},

// Non-recurring work events
{
Expand Down Expand Up @@ -252,7 +268,20 @@ export const initialEvents: CalendarEvent[] = [
];

export const resources: CalendarResource[] = [
{ title: 'Work', id: 'work', eventColor: 'violet' },
{
title: 'Work',
id: 'work',
eventColor: 'violet',
children: [
{
title: 'eXplore Team',
id: 'explore',
eventColor: 'pink',
children: [{ title: 'Design meetings', id: 'design-meetings', eventColor: 'mauve' }],
},
{ title: 'Data Grid Team', id: 'data-grid', eventColor: 'blue' },
],
},
{ title: 'Holidays', id: 'holidays', eventColor: 'red' },
{ title: 'Workout', id: 'workout', eventColor: 'jade' },
{ title: 'Birthdays', id: 'birthdays', eventColor: 'lime' },
Expand Down
4 changes: 4 additions & 0 deletions packages/x-scheduler-headless/src/models/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export interface CalendarResource {
* @default "jade"
*/
eventColor?: CalendarEventColor;
/**
* The child resources of this resource.
*/
children?: CalendarResource[];
}

export type SchedulerResourceModelStructure<TResource extends object> = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,82 @@
import { createSelector, createSelectorMemoized } from '@base-ui-components/utils/store';
import { EMPTY_ARRAY } from '@base-ui-components/utils/empty';
import { SchedulerState as State } from '../utils/SchedulerStore/SchedulerStore.types';

const processedResourceSelector = createSelector(
(state: State) => state.processedResourceLookup,
(processedResourceLookup, resourceId: string | null | undefined) =>
resourceId == null ? null : processedResourceLookup.get(resourceId),
);
import { CalendarResource, CalendarResourceId } from '../models';

export const schedulerResourceSelectors = {
processedResource: processedResourceSelector,
processedResource: createSelector(
(state: State) => state.processedResourceLookup,
(processedResourceLookup, resourceId: string | null | undefined) =>
resourceId == null ? null : processedResourceLookup.get(resourceId),
),
processedResourceList: createSelectorMemoized(
(state: State) => state.resourceIdList,
(state: State) => state.processedResourceLookup,
(resourceIds, processedResourceLookup) =>
resourceIds.map((id) => processedResourceLookup.get(id)!),
),
processedResourceFlatList: createSelectorMemoized(
(state: State) => state.resourceIdList,
(state: State) => state.processedResourceLookup,
(state: State) => state.resourceChildrenIdLookup,
(resourceIds, processedResourceLookup, resourceChildrenIdLookup) => {
const flatList: CalendarResource[] = [];

const addResourceAndChildren = (resourceId: string) => {
const resource = processedResourceLookup.get(resourceId);
if (!resource) {
return;
}

flatList.push(resource);

const childrenIds = resourceChildrenIdLookup.get(resourceId);
if (childrenIds?.length) {
for (const childId of childrenIds) {
addResourceAndChildren(childId);
}
}
};

for (const resourceId of resourceIds) {
addResourceAndChildren(resourceId);
}
return flatList;
},
),
processedResourceChildrenLookup: createSelectorMemoized(
(state: State) => state.processedResourceLookup,
(state: State) => state.resourceChildrenIdLookup,
(processedResourceLookup, resourceChildrenIdLookup) => {
const result: Map<CalendarResourceId, CalendarResource[]> = new Map();

for (const [resourceId, childrenIds] of Array.from(resourceChildrenIdLookup.entries())) {
const children = childrenIds.map((id) => processedResourceLookup.get(id)!);
result.set(resourceId, children);
}

return result;
},
),
childrenIdLookup: (state: State) => state.resourceChildrenIdLookup,
resourceChildrenIds: createSelector(
(state: State, resourceId: CalendarResourceId) =>
state.resourceChildrenIdLookup.get(resourceId) ?? EMPTY_ARRAY,
),
resourceParentIdLookup: createSelectorMemoized(
(state: State) => state.resourceChildrenIdLookup,
(resourceChildrenIdLookup) => {
const result: Map<CalendarResourceId, CalendarResourceId | null> = new Map();

for (const [resourceId, childrenIds] of Array.from(resourceChildrenIdLookup.entries())) {
for (const childId of childrenIds) {
result.set(childId, resourceId);
}
}

return result;
},
),
idList: createSelector((state: State) => state.resourceIdList),
visibleMap: createSelector((state: State) => state.visibleResources),
visibleIdList: createSelectorMemoized(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function useAgendaEventOccurrencesGroupedByDay(): useAgendaEventOccurrenc
const showWeekends = useStore(store, eventCalendarPreferenceSelectors.showWeekends);
const showEmptyDays = useStore(store, eventCalendarPreferenceSelectors.showEmptyDaysInAgenda);
const visibleResources = useStore(store, schedulerResourceSelectors.visibleMap);
const resourceParentIds = useStore(store, schedulerResourceSelectors.resourceParentIdLookup);

const amount = AGENDA_VIEW_DAYS_AMOUNT;

Expand All @@ -57,6 +58,7 @@ export function useAgendaEventOccurrencesGroupedByDay(): useAgendaEventOccurrenc
accumulatedDays,
events,
visibleResources,
resourceParentIds,
);

const hasEvents = (day: CalendarProcessedDate) => (occurrenceMap.get(day.key)?.length ?? 0) > 0;
Expand Down Expand Up @@ -106,6 +108,7 @@ export function useAgendaEventOccurrencesGroupedByDay(): useAgendaEventOccurrenc
accumulatedDays,
events,
visibleResources,
resourceParentIds,
);

daysWithEvents = accumulatedDays.filter(hasEvents).slice(0, amount);
Expand All @@ -125,6 +128,7 @@ export function useAgendaEventOccurrencesGroupedByDay(): useAgendaEventOccurrenc
events,
visibleResources,
showEmptyDays,
resourceParentIds,
]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('Core - EventCalendarStore', () => {
resourceIdList: [],
processedResourceLookup: new Map(),
resourceModelStructure: undefined,
resourceChildrenIdLookup: new Map(),
visibleResources: new Map(),
nowUpdatedEveryMinute: adapter.date(),
isMultiDayEvent: DEFAULT_IS_MULTI_DAY_EVENT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,18 @@ export function useEventOccurrencesGroupedByDay(
const store = useEventCalendarStoreContext();
const events = useStore(store, schedulerEventSelectors.processedEventList);
const visibleResources = useStore(store, schedulerResourceSelectors.visibleMap);
const resourceParentIds = useStore(store, schedulerResourceSelectors.resourceParentIdLookup);

return React.useMemo(
() => innerGetEventOccurrencesGroupedByDay(adapter, days, events, visibleResources),
[adapter, days, events, visibleResources],
() =>
innerGetEventOccurrencesGroupedByDay(
adapter,
days,
events,
visibleResources,
resourceParentIds,
),
[adapter, days, events, visibleResources, resourceParentIds],
);
}

Expand All @@ -48,6 +56,7 @@ export function innerGetEventOccurrencesGroupedByDay(
days: CalendarProcessedDate[],
events: CalendarEvent[],
visibleResources: Map<string, boolean>,
resourceParentIds: Map<string, string | null>,
): Map<string, CalendarEventOccurrence[]> {
// STEP 4: Create a Map of the occurrences grouped by day
const occurrencesGroupedByDay = new Map<
Expand All @@ -57,7 +66,14 @@ export function innerGetEventOccurrencesGroupedByDay(

const start = adapter.startOfDay(days[0].value);
const end = adapter.endOfDay(days[days.length - 1].value);
const occurrences = getOccurrencesFromEvents({ adapter, start, end, events, visibleResources });
const occurrences = getOccurrencesFromEvents({
adapter,
start,
end,
events,
visibleResources,
resourceParentIds,
});

for (const occurrence of occurrences) {
const eventDays = getDaysTheOccurrenceIsVisibleOn(occurrence, days, adapter);
Expand Down
Loading
Loading