diff --git a/docs/data/scheduler/datasets/personal-agenda.js b/docs/data/scheduler/datasets/personal-agenda.js index 6a2bc68160c85..7d32e4d3f0c46 100644 --- a/docs/data/scheduler/datasets/personal-agenda.js +++ b/docs/data/scheduler/datasets/personal-agenda.js @@ -45,7 +45,7 @@ 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'] }, }, { @@ -53,9 +53,25 @@ export const initialEvents = [ 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', @@ -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' }, diff --git a/docs/data/scheduler/datasets/personal-agenda.ts b/docs/data/scheduler/datasets/personal-agenda.ts index 682d175d9afad..557f594db781b 100644 --- a/docs/data/scheduler/datasets/personal-agenda.ts +++ b/docs/data/scheduler/datasets/personal-agenda.ts @@ -46,7 +46,7 @@ export const initialEvents: SchedulerEvent[] = [ 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'] }, }, { @@ -54,9 +54,25 @@ export const initialEvents: SchedulerEvent[] = [ 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 { @@ -252,7 +268,20 @@ export const initialEvents: SchedulerEvent[] = [ ]; 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' }, diff --git a/packages/x-scheduler-headless/src/models/resource.ts b/packages/x-scheduler-headless/src/models/resource.ts index 91cfd86e48e03..8dbb59cee1633 100644 --- a/packages/x-scheduler-headless/src/models/resource.ts +++ b/packages/x-scheduler-headless/src/models/resource.ts @@ -20,6 +20,10 @@ export interface CalendarResource { * @default "jade" */ eventColor?: CalendarEventColor; + /** + * The child resources of this resource. + */ + children?: CalendarResource[]; } export type SchedulerResourceModelStructure = { diff --git a/packages/x-scheduler-headless/src/scheduler-selectors/schedulerResourceSelectors.ts b/packages/x-scheduler-headless/src/scheduler-selectors/schedulerResourceSelectors.ts index 2bfed85f484dc..b51782fedb00b 100644 --- a/packages/x-scheduler-headless/src/scheduler-selectors/schedulerResourceSelectors.ts +++ b/packages/x-scheduler-headless/src/scheduler-selectors/schedulerResourceSelectors.ts @@ -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 = 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 = 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( diff --git a/packages/x-scheduler-headless/src/use-agenda-event-occurrences-grouped-by-day/useAgendaEventOccurrencesGroupedByDay.ts b/packages/x-scheduler-headless/src/use-agenda-event-occurrences-grouped-by-day/useAgendaEventOccurrencesGroupedByDay.ts index d2dd54e227170..6dbbdafc66c12 100644 --- a/packages/x-scheduler-headless/src/use-agenda-event-occurrences-grouped-by-day/useAgendaEventOccurrencesGroupedByDay.ts +++ b/packages/x-scheduler-headless/src/use-agenda-event-occurrences-grouped-by-day/useAgendaEventOccurrencesGroupedByDay.ts @@ -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; @@ -57,6 +58,7 @@ export function useAgendaEventOccurrencesGroupedByDay(): useAgendaEventOccurrenc accumulatedDays, events, visibleResources, + resourceParentIds, ); const hasEvents = (day: CalendarProcessedDate) => (occurrenceMap.get(day.key)?.length ?? 0) > 0; @@ -106,6 +108,7 @@ export function useAgendaEventOccurrencesGroupedByDay(): useAgendaEventOccurrenc accumulatedDays, events, visibleResources, + resourceParentIds, ); daysWithEvents = accumulatedDays.filter(hasEvents).slice(0, amount); @@ -125,6 +128,7 @@ export function useAgendaEventOccurrencesGroupedByDay(): useAgendaEventOccurrenc events, visibleResources, showEmptyDays, + resourceParentIds, ]); } diff --git a/packages/x-scheduler-headless/src/use-event-calendar/tests/core.EventCalendarStore.test.ts b/packages/x-scheduler-headless/src/use-event-calendar/tests/core.EventCalendarStore.test.ts index 9ed05399e7e1d..9cb741b5aced7 100644 --- a/packages/x-scheduler-headless/src/use-event-calendar/tests/core.EventCalendarStore.test.ts +++ b/packages/x-scheduler-headless/src/use-event-calendar/tests/core.EventCalendarStore.test.ts @@ -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, diff --git a/packages/x-scheduler-headless/src/use-event-occurrences-grouped-by-day/useEventOccurrencesGroupedByDay.ts b/packages/x-scheduler-headless/src/use-event-occurrences-grouped-by-day/useEventOccurrencesGroupedByDay.ts index 9ad6b6d6e73b2..aad2b90f8ffee 100644 --- a/packages/x-scheduler-headless/src/use-event-occurrences-grouped-by-day/useEventOccurrencesGroupedByDay.ts +++ b/packages/x-scheduler-headless/src/use-event-occurrences-grouped-by-day/useEventOccurrencesGroupedByDay.ts @@ -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], ); } @@ -48,6 +56,7 @@ export function innerGetEventOccurrencesGroupedByDay( days: CalendarProcessedDate[], events: SchedulerProcessedEvent[], visibleResources: Map, + resourceParentIds: Map, ): Map { // STEP 4: Create a Map of the occurrences grouped by day const occurrencesGroupedByDay = new Map< @@ -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); diff --git a/packages/x-scheduler-headless/src/use-event-occurrences-grouped-by-resource/useEventOccurencesGroupedByResource.tsx b/packages/x-scheduler-headless/src/use-event-occurrences-grouped-by-resource/useEventOccurencesGroupedByResource.tsx index 7fd376b002281..8087a3da26b71 100644 --- a/packages/x-scheduler-headless/src/use-event-occurrences-grouped-by-resource/useEventOccurencesGroupedByResource.tsx +++ b/packages/x-scheduler-headless/src/use-event-occurrences-grouped-by-resource/useEventOccurencesGroupedByResource.tsx @@ -20,6 +20,11 @@ export function useEventOccurrencesGroupedByResource( const events = useStore(store, schedulerEventSelectors.processedEventList); const visibleResources = useStore(store, schedulerResourceSelectors.visibleMap); const resources = useStore(store, schedulerResourceSelectors.processedResourceList); + const resourcesChildrenMap = useStore( + store, + schedulerResourceSelectors.processedResourceChildrenLookup, + ); + const resourceParentIds = useStore(store, schedulerResourceSelectors.resourceParentIdLookup); return React.useMemo( () => @@ -28,10 +33,21 @@ export function useEventOccurrencesGroupedByResource( events, visibleResources, resources, + resourcesChildrenMap, + resourceParentIds, start, end, ), - [adapter, events, visibleResources, resources, start, end], + [ + adapter, + events, + visibleResources, + resources, + resourcesChildrenMap, + resourceParentIds, + start, + end, + ], ); } @@ -47,6 +63,11 @@ export namespace useEventOccurrencesGroupedByResource { }[]; } +interface InnerGetEventOccurrencesGroupedByResourceReturnValue { + resource: CalendarResource; + occurrences: CalendarEventOccurrence[]; +} + /** * Do not use directly, use the `useEventOccurrencesGroupedByResource` hook instead. * This is only exported for testing purposes. @@ -56,12 +77,21 @@ export function innerGetEventOccurrencesGroupedByResource( events: SchedulerProcessedEvent[], visibleResources: Map, resources: readonly CalendarResource[], + resourcesChildrenMap: Map, + resourceParentIds: Map, start: SchedulerValidDate, end: SchedulerValidDate, -): { resource: CalendarResource; occurrences: CalendarEventOccurrence[] }[] { +): InnerGetEventOccurrencesGroupedByResourceReturnValue[] { const occurrencesGroupedByResource = new Map(); - const occurrences = getOccurrencesFromEvents({ adapter, start, end, events, visibleResources }); + const occurrences = getOccurrencesFromEvents({ + adapter, + start, + end, + events, + visibleResources, + resourceParentIds, + }); for (const occurrence of occurrences) { const resourceId = occurrence.resource; @@ -74,13 +104,24 @@ export function innerGetEventOccurrencesGroupedByResource( } } - return ( - resources - // Sort by resource.title (localeCompare for stable alphabetical ordering). - .toSorted((a, b) => a.title.localeCompare(b.title)) - .map((resource) => ({ + const processResources = (innerResources: readonly CalendarResource[]) => { + const sortedResources = innerResources.toSorted((a, b) => a.title.localeCompare(b.title)); + const result: InnerGetEventOccurrencesGroupedByResourceReturnValue[] = []; + + for (const resource of sortedResources) { + result.push({ resource, occurrences: occurrencesGroupedByResource.get(resource.id) ?? [], - })) - ); + }); + + const children = resourcesChildrenMap.get(resource.id) ?? []; + if (children.length > 0) { + result.push(...processResources(children)); + } + } + + return result; + }; + + return processResources(resources); } diff --git a/packages/x-scheduler-headless/src/use-event-occurrences-with-day-grid-position/useEventOccurrencesWithDayGridPosition.test.ts b/packages/x-scheduler-headless/src/use-event-occurrences-with-day-grid-position/useEventOccurrencesWithDayGridPosition.test.ts index 810470b260913..7835654b84b6b 100644 --- a/packages/x-scheduler-headless/src/use-event-occurrences-with-day-grid-position/useEventOccurrencesWithDayGridPosition.test.ts +++ b/packages/x-scheduler-headless/src/use-event-occurrences-with-day-grid-position/useEventOccurrencesWithDayGridPosition.test.ts @@ -14,7 +14,13 @@ describe('useDayListEventOccurrencesWithPosition', () => { function testHook(events: SchedulerProcessedEvent[]) { const { result } = renderHook(() => { - const occurrencesMap = innerGetEventOccurrencesGroupedByDay(adapter, days, events, new Map()); + const occurrencesMap = innerGetEventOccurrencesGroupedByDay( + adapter, + days, + events, + new Map(), + new Map(), + ); return useEventOccurrencesWithDayGridPosition({ days, occurrencesMap }); }); diff --git a/packages/x-scheduler-headless/src/use-event-occurrences-with-timeline-position/useEventOccurrencesWithTimelinePosition.test.ts b/packages/x-scheduler-headless/src/use-event-occurrences-with-timeline-position/useEventOccurrencesWithTimelinePosition.test.ts index 03a7cdbb9ca32..e0fb54c8eea10 100644 --- a/packages/x-scheduler-headless/src/use-event-occurrences-with-timeline-position/useEventOccurrencesWithTimelinePosition.test.ts +++ b/packages/x-scheduler-headless/src/use-event-occurrences-with-timeline-position/useEventOccurrencesWithTimelinePosition.test.ts @@ -16,6 +16,7 @@ describe('useDayListEventOccurrencesWithPosition', () => { end: collectionEnd, events, visibleResources: new Map(), + resourceParentIds: new Map(), }); return useEventOccurrencesWithTimelinePosition({ occurrences, maxSpan }); }); diff --git a/packages/x-scheduler-headless/src/use-timeline/tests/core.TimelineStore.test.ts b/packages/x-scheduler-headless/src/use-timeline/tests/core.TimelineStore.test.ts index 8d996e9df15a7..033bb96b67754 100644 --- a/packages/x-scheduler-headless/src/use-timeline/tests/core.TimelineStore.test.ts +++ b/packages/x-scheduler-headless/src/use-timeline/tests/core.TimelineStore.test.ts @@ -23,6 +23,7 @@ describe('Core - TimelineStore', () => { resourceIdList: [], processedResourceLookup: new Map(), resourceModelStructure: undefined, + resourceChildrenIdLookup: new Map(), nowUpdatedEveryMinute: adapter.date(), isMultiDayEvent: DEFAULT_IS_MULTI_DAY_EVENT, areEventsDraggable: false, diff --git a/packages/x-scheduler-headless/src/utils/SchedulerStore/SchedulerStore.types.ts b/packages/x-scheduler-headless/src/utils/SchedulerStore/SchedulerStore.types.ts index 3877c3ec98cd2..482d696d7b800 100644 --- a/packages/x-scheduler-headless/src/utils/SchedulerStore/SchedulerStore.types.ts +++ b/packages/x-scheduler-headless/src/utils/SchedulerStore/SchedulerStore.types.ts @@ -51,6 +51,10 @@ export interface SchedulerState { * The IDs of the resources the events can be assigned to. */ resourceIdList: readonly CalendarResourceId[]; + /** + * A lookup to get the children of a resource from its ID. + */ + resourceChildrenIdLookup: Map; /** * A lookup to get the processed resource from its ID. */ diff --git a/packages/x-scheduler-headless/src/utils/SchedulerStore/SchedulerStore.utils.ts b/packages/x-scheduler-headless/src/utils/SchedulerStore/SchedulerStore.utils.ts index c1a3b270bc5db..80753869cd045 100644 --- a/packages/x-scheduler-headless/src/utils/SchedulerStore/SchedulerStore.utils.ts +++ b/packages/x-scheduler-headless/src/utils/SchedulerStore/SchedulerStore.utils.ts @@ -63,6 +63,7 @@ const RESOURCE_PROPERTIES_LOOKUP: { [P in keyof CalendarResource]-?: true } = { id: true, title: true, eventColor: true, + children: true, }; const RESOURCE_PROPERTIES = Object.keys(RESOURCE_PROPERTIES_LOOKUP) as (keyof CalendarResource)[]; @@ -164,7 +165,20 @@ export function getProcessedResourceFromModel( const getter = resourceModelStructure?.[key]?.getter; // @ts-ignore - processedResource[key] = getter ? getter(resource) : resource[key]; + const resourceProperty = getter ? getter(resource) : resource[key]; + + if (key === 'children' && Array.isArray(resourceProperty)) { + // Process children recursively + const children = resourceProperty.map((child) => + getProcessedResourceFromModel(child, resourceModelStructure), + ); + // @ts-ignore + processedResource[key] = children; + continue; + } + + // @ts-ignore + processedResource[key] = resourceProperty; } return processedResource; @@ -211,21 +225,41 @@ export function buildResourcesState, 'resources' | 'resourceModelStructure'>, ): Pick< SchedulerState, - 'resourceIdList' | 'processedResourceLookup' | 'resourceModelStructure' + | 'resourceIdList' + | 'processedResourceLookup' + | 'resourceModelStructure' + | 'resourceChildrenIdLookup' > { const { resources = EMPTY_ARRAY, resourceModelStructure } = parameters; const resourceIdList: string[] = []; const processedResourceLookup = new Map(); + const resourceChildrenIdLookup = new Map(); + + const addResourceToState = (processedResource: CalendarResource) => { + const { children, ...resourceWithoutChildren } = processedResource; + processedResourceLookup.set(processedResource.id, resourceWithoutChildren); + if (children) { + for (const child of children) { + if (!resourceChildrenIdLookup.get(processedResource.id)) { + resourceChildrenIdLookup.set(processedResource.id, []); + } + resourceChildrenIdLookup.get(processedResource.id)?.push(child.id); + addResourceToState(child); + } + } + }; + for (const resource of resources) { const processedResource = getProcessedResourceFromModel(resource, resourceModelStructure); resourceIdList.push(processedResource.id); - processedResourceLookup.set(processedResource.id, processedResource); + addResourceToState(processedResource); } return { resourceIdList, processedResourceLookup, resourceModelStructure, + resourceChildrenIdLookup, }; } diff --git a/packages/x-scheduler-headless/src/utils/event-utils.ts b/packages/x-scheduler-headless/src/utils/event-utils.ts index 56e2f434c17b3..209056144cf4a 100644 --- a/packages/x-scheduler-headless/src/utils/event-utils.ts +++ b/packages/x-scheduler-headless/src/utils/event-utils.ts @@ -31,16 +31,41 @@ export function getDaysTheOccurrenceIsVisibleOn( return dayKeys; } +const checkResourceVisibility = ( + resourceId: string, + visibleResources: Map, + resourceParentIds: Map, +): boolean => { + if (!resourceId) { + return true; + } + + const isResourceVisible = visibleResources.get(resourceId) !== false; + + if (isResourceVisible) { + const parentId = resourceParentIds.get(resourceId); + if (!parentId) { + return isResourceVisible; + } + return checkResourceVisibility(parentId, visibleResources, resourceParentIds); + } + + return isResourceVisible; +}; + /** * Returns the occurrences to render in the given date range, expanding recurring events. */ export function getOccurrencesFromEvents(parameters: GetOccurrencesFromEventsParameters) { - const { adapter, start, end, events, visibleResources } = parameters; + const { adapter, start, end, events, visibleResources, resourceParentIds } = parameters; const occurrences: CalendarEventOccurrence[] = []; for (const event of events) { // STEP 1: Skip events from resources that are not visible - if (event.resource && visibleResources.get(event.resource) === false) { + if ( + event.resource && + checkResourceVisibility(event.resource, visibleResources, resourceParentIds) === false + ) { continue; } @@ -81,4 +106,5 @@ interface GetOccurrencesFromEventsParameters { end: SchedulerValidDate; events: SchedulerProcessedEvent[]; visibleResources: Map; + resourceParentIds: Map; } diff --git a/packages/x-scheduler/src/internals/components/event-popover/FormContent.tsx b/packages/x-scheduler/src/internals/components/event-popover/FormContent.tsx index 84a3f71f453cb..554c0aea8b8aa 100644 --- a/packages/x-scheduler/src/internals/components/event-popover/FormContent.tsx +++ b/packages/x-scheduler/src/internals/components/event-popover/FormContent.tsx @@ -50,7 +50,7 @@ export default function FormContent(props: FormContentProps) { occurrence.id, ); const rawPlaceholder = useStore(store, schedulerOccurrencePlaceholderSelectors.value); - const resources = useStore(store, schedulerResourceSelectors.processedResourceList); + const resources = useStore(store, schedulerResourceSelectors.processedResourceFlatList); const recurrencePresets = useStore( store, schedulerRecurringEventSelectors.presets, diff --git a/packages/x-scheduler/src/internals/components/resource-legend/ResourceLegend.css b/packages/x-scheduler/src/internals/components/resource-legend/ResourceLegend.css index 6b65d0e8891e0..b6eca5ba7fe4d 100644 --- a/packages/x-scheduler/src/internals/components/resource-legend/ResourceLegend.css +++ b/packages/x-scheduler/src/internals/components/resource-legend/ResourceLegend.css @@ -29,6 +29,10 @@ color: var(--text-secondary); } +.ResourceLegendChildren { + margin-left: var(--space-2); +} + .mui-x-scheduler .Button.ResourceLegendButton { padding: var(--space-1); margin-inline-start: auto; diff --git a/packages/x-scheduler/src/timeline/content/TimelineContent.tsx b/packages/x-scheduler/src/timeline/content/TimelineContent.tsx index 99a319a14cd1f..46681b2b9d1e2 100644 --- a/packages/x-scheduler/src/timeline/content/TimelineContent.tsx +++ b/packages/x-scheduler/src/timeline/content/TimelineContent.tsx @@ -51,7 +51,7 @@ export const TimelineContent = React.forwardRef(function TimelineContent( const adapter = useAdapter(); const store = useTimelineStoreContext(); - const resources = useStore(store, schedulerResourceSelectors.processedResourceList); + const resources = useStore(store, schedulerResourceSelectors.processedResourceFlatList); const visibleDate = useStore(store, schedulerOtherSelectors.visibleDate); const view = useStore(store, timelineViewSelectors.view); diff --git a/tsconfig.dev.json b/tsconfig.dev.json index 07a1df53ae975..bc9114f164a3d 100644 --- a/tsconfig.dev.json +++ b/tsconfig.dev.json @@ -4,7 +4,6 @@ "skipLibCheck": true, "incremental": true, "esModuleInterop": true, - "resolveJsonModule": true, "jsx": "preserve" }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]