From 91155c5193f0a0b3899cb84c1dbfc6480fca4c0c Mon Sep 17 00:00:00 2001 From: basstager <81801747+basstager@users.noreply.github.com> Date: Fri, 2 Jun 2023 15:55:53 +0200 Subject: [PATCH] feat: Support multiple resources on an event Allows for defining multiple resources on a single event, so that the event can display in multiple resource columns simultaneously Co-authored-by: Jim Hlad Co-authored-by: Jim Hlad #2405 #1649 --- src/Calendar.js | 2 +- src/DayColumn.js | 5 ++- src/addons/dragAndDrop/EventWrapper.js | 6 +++- src/utils/Resources.js | 14 +++++++-- stories/DragAndDrop.stories.js | 26 +++++++++++++++- stories/demos/exampleCode/dndresource.js | 29 ++++++++++++++++-- stories/demos/exampleCode/resource.js | 8 ++++- stories/helpers/index.js | 39 ++++++++++++++++++++++++ stories/props/API.stories.mdx | 2 +- stories/props/resourceIdAccessor.mdx | 2 +- 10 files changed, 121 insertions(+), 12 deletions(-) diff --git a/src/Calendar.js b/src/Calendar.js index cf70d2391..f008227c4 100644 --- a/src/Calendar.js +++ b/src/Calendar.js @@ -246,7 +246,7 @@ class Calendar extends React.Component { resources: PropTypes.arrayOf(PropTypes.object), /** - * Provides a unique identifier for each resource in the `resources` array + * Provides a unique identifier, or an array of unique identifiers, for each resource in the `resources` array * * ```js * string | (resource: Object) => any diff --git a/src/DayColumn.js b/src/DayColumn.js index f02dde7af..173662037 100644 --- a/src/DayColumn.js +++ b/src/DayColumn.js @@ -238,8 +238,11 @@ class DayColumn extends React.Component { continuesPrior={continuesPrior} continuesAfter={continuesAfter} accessors={accessors} + resource={this.props.resource} selected={isSelected(event, selected)} - onClick={(e) => this._select(event, e)} + onClick={(e) => + this._select({ ...event, sourceResource: this.props.resource }, e) + } onDoubleClick={(e) => this._doubleClick(event, e)} isBackgroundEvent={isBackgroundEvent} onKeyPress={(e) => this._keyPress(event, e)} diff --git a/src/addons/dragAndDrop/EventWrapper.js b/src/addons/dragAndDrop/EventWrapper.js index 34ac2e66d..3ed575498 100644 --- a/src/addons/dragAndDrop/EventWrapper.js +++ b/src/addons/dragAndDrop/EventWrapper.js @@ -18,6 +18,7 @@ class EventWrapper extends React.Component { continuesAfter: PropTypes.bool, isDragging: PropTypes.bool, isResizing: PropTypes.bool, + resource: PropTypes.number, resizable: PropTypes.bool, } @@ -45,8 +46,11 @@ class EventWrapper extends React.Component { const isResizeHandle = e.target .getAttribute('class') ?.includes('rbc-addons-dnd-resize') - if (!isResizeHandle) + if (!isResizeHandle) { + let extendedEvent = this.props.event + extendedEvent.sourceResource = this.props.resource this.context.draggable.onBeginAction(this.props.event, 'move') + } } renderAnchor(direction) { diff --git a/src/utils/Resources.js b/src/utils/Resources.js index a99a3d4ac..0c3d1ab10 100644 --- a/src/utils/Resources.js +++ b/src/utils/Resources.js @@ -20,9 +20,17 @@ export default function Resources(resources, accessors) { events.forEach((event) => { const id = accessors.resource(event) || NONE - let resourceEvents = eventsByResource.get(id) || [] - resourceEvents.push(event) - eventsByResource.set(id, resourceEvents) + if (Array.isArray(id)) { + id.forEach((item) => { + let resourceEvents = eventsByResource.get(item) || [] + resourceEvents.push(event) + eventsByResource.set(item, resourceEvents) + }) + } else { + let resourceEvents = eventsByResource.get(id) || [] + resourceEvents.push(event) + eventsByResource.set(id, resourceEvents) + } }) return eventsByResource }, diff --git a/stories/DragAndDrop.stories.js b/stories/DragAndDrop.stories.js index 333033917..4b15e4dc9 100644 --- a/stories/DragAndDrop.stories.js +++ b/stories/DragAndDrop.stories.js @@ -1,7 +1,14 @@ import React from 'react' import { action } from '@storybook/addon-actions' -import { events, Calendar, Views, DragAndDropCalendar } from './helpers' +import { + events, + resourceEvents, + resources, + Calendar, + Views, + DragAndDropCalendar, +} from './helpers' import customComponents from './resources/customComponents' export default { @@ -106,3 +113,20 @@ WithCustomEventWrapper.args = { eventWrapper: customComponents.eventWrapper, }, } + +export const DraggableMultipleResources = Template.bind({}) +DraggableMultipleResources.storyName = + 'draggable and resizable with multiple resource lanes' +DraggableMultipleResources.args = { + defaultDate: new Date(), + defaultView: Views.DAY, + views: [Views.DAY, Views.WEEK, Views.AGENDA], + events: resourceEvents, + resources: resources, + resourceAccessor: 'resourceId', + resourceIdAccessor: 'id', + resourceTitleAccessor: 'name', + resizable: true, + onEventDrop: action('event dropped'), + onEventResize: action('event resized'), +} diff --git a/stories/demos/exampleCode/dndresource.js b/stories/demos/exampleCode/dndresource.js index 4d3c1035e..c9c41794f 100644 --- a/stories/demos/exampleCode/dndresource.js +++ b/stories/demos/exampleCode/dndresource.js @@ -15,7 +15,7 @@ const events = [ title: 'Board meeting', start: new Date(2018, 0, 29, 9, 0, 0), end: new Date(2018, 0, 29, 13, 0, 0), - resourceId: 1, + resourceId: [1, 2], }, { id: 1, @@ -77,6 +77,9 @@ const resourceMap = [ export default function DnDResource({ localizer }) { const [myEvents, setMyEvents] = useState(events) + const [copyEvent, setCopyEvent] = useState(true) + + const toggleCopyEvent = useCallback(() => setCopyEvent((val) => !val), []) const moveEvent = useCallback( ({ @@ -90,6 +93,18 @@ export default function DnDResource({ localizer }) { if (!allDay && droppedOnAllDaySlot) { event.allDay = true } + if (Array.isArray(event.resourceId)) { + if (copyEvent) { + resourceId = [...new Set([...event.resourceId, resourceId])] + } else { + const filtered = event.resourceId.filter( + (ev) => ev !== event.sourceResource + ) + resourceId = [...new Set([...filtered, resourceId])] + } + } else if (copyEvent) { + resourceId = [...new Set([event.resourceId, resourceId])] + } setMyEvents((prev) => { const existing = prev.find((ev) => ev.id === event.id) ?? {} @@ -97,7 +112,7 @@ export default function DnDResource({ localizer }) { return [...filtered, { ...existing, start, end, resourceId, allDay }] }) }, - [setMyEvents] + [setMyEvents, copyEvent] ) const resizeEvent = useCallback( @@ -125,6 +140,16 @@ export default function DnDResource({ localizer }) { Drag and Drop an "event" from one resource slot to another. +
+ +
+ + The calendar below uses the resourceIdAccessor, resourceTitleAccessor and resources props to show events scheduled for different resources. +
+ Events can be mapped to a single resource, or multiple resources. +
-Provides a unique identifier for each resource in the resources array +Provides a unique identifier, or an array of unique identifiers, for each resource in the resources array ### resourceTitleAccessor diff --git a/stories/props/resourceIdAccessor.mdx b/stories/props/resourceIdAccessor.mdx index 8a591a7dd..e02e3725f 100644 --- a/stories/props/resourceIdAccessor.mdx +++ b/stories/props/resourceIdAccessor.mdx @@ -5,6 +5,6 @@ import LinkTo from '@storybook/addon-links/react' - type: `string | function (resource: Object) => string | number // must be unique` -Provides a unique identifier for each resource in the resources array +Provides a unique identifier, or an array of unique identifiers, for each resource in the resources array