Skip to content

Commit 91155c5

Browse files
authored
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 <[email protected]> Co-authored-by: Jim Hlad <[email protected]> #2405 #1649
1 parent c7cd908 commit 91155c5

File tree

10 files changed

+121
-12
lines changed

10 files changed

+121
-12
lines changed

src/Calendar.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ class Calendar extends React.Component {
246246
resources: PropTypes.arrayOf(PropTypes.object),
247247

248248
/**
249-
* Provides a unique identifier for each resource in the `resources` array
249+
* Provides a unique identifier, or an array of unique identifiers, for each resource in the `resources` array
250250
*
251251
* ```js
252252
* string | (resource: Object) => any

src/DayColumn.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,11 @@ class DayColumn extends React.Component {
238238
continuesPrior={continuesPrior}
239239
continuesAfter={continuesAfter}
240240
accessors={accessors}
241+
resource={this.props.resource}
241242
selected={isSelected(event, selected)}
242-
onClick={(e) => this._select(event, e)}
243+
onClick={(e) =>
244+
this._select({ ...event, sourceResource: this.props.resource }, e)
245+
}
243246
onDoubleClick={(e) => this._doubleClick(event, e)}
244247
isBackgroundEvent={isBackgroundEvent}
245248
onKeyPress={(e) => this._keyPress(event, e)}

src/addons/dragAndDrop/EventWrapper.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class EventWrapper extends React.Component {
1818
continuesAfter: PropTypes.bool,
1919
isDragging: PropTypes.bool,
2020
isResizing: PropTypes.bool,
21+
resource: PropTypes.number,
2122
resizable: PropTypes.bool,
2223
}
2324

@@ -45,8 +46,11 @@ class EventWrapper extends React.Component {
4546
const isResizeHandle = e.target
4647
.getAttribute('class')
4748
?.includes('rbc-addons-dnd-resize')
48-
if (!isResizeHandle)
49+
if (!isResizeHandle) {
50+
let extendedEvent = this.props.event
51+
extendedEvent.sourceResource = this.props.resource
4952
this.context.draggable.onBeginAction(this.props.event, 'move')
53+
}
5054
}
5155

5256
renderAnchor(direction) {

src/utils/Resources.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,17 @@ export default function Resources(resources, accessors) {
2020

2121
events.forEach((event) => {
2222
const id = accessors.resource(event) || NONE
23-
let resourceEvents = eventsByResource.get(id) || []
24-
resourceEvents.push(event)
25-
eventsByResource.set(id, resourceEvents)
23+
if (Array.isArray(id)) {
24+
id.forEach((item) => {
25+
let resourceEvents = eventsByResource.get(item) || []
26+
resourceEvents.push(event)
27+
eventsByResource.set(item, resourceEvents)
28+
})
29+
} else {
30+
let resourceEvents = eventsByResource.get(id) || []
31+
resourceEvents.push(event)
32+
eventsByResource.set(id, resourceEvents)
33+
}
2634
})
2735
return eventsByResource
2836
},

stories/DragAndDrop.stories.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import React from 'react'
22
import { action } from '@storybook/addon-actions'
33

4-
import { events, Calendar, Views, DragAndDropCalendar } from './helpers'
4+
import {
5+
events,
6+
resourceEvents,
7+
resources,
8+
Calendar,
9+
Views,
10+
DragAndDropCalendar,
11+
} from './helpers'
512
import customComponents from './resources/customComponents'
613

714
export default {
@@ -106,3 +113,20 @@ WithCustomEventWrapper.args = {
106113
eventWrapper: customComponents.eventWrapper,
107114
},
108115
}
116+
117+
export const DraggableMultipleResources = Template.bind({})
118+
DraggableMultipleResources.storyName =
119+
'draggable and resizable with multiple resource lanes'
120+
DraggableMultipleResources.args = {
121+
defaultDate: new Date(),
122+
defaultView: Views.DAY,
123+
views: [Views.DAY, Views.WEEK, Views.AGENDA],
124+
events: resourceEvents,
125+
resources: resources,
126+
resourceAccessor: 'resourceId',
127+
resourceIdAccessor: 'id',
128+
resourceTitleAccessor: 'name',
129+
resizable: true,
130+
onEventDrop: action('event dropped'),
131+
onEventResize: action('event resized'),
132+
}

stories/demos/exampleCode/dndresource.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const events = [
1515
title: 'Board meeting',
1616
start: new Date(2018, 0, 29, 9, 0, 0),
1717
end: new Date(2018, 0, 29, 13, 0, 0),
18-
resourceId: 1,
18+
resourceId: [1, 2],
1919
},
2020
{
2121
id: 1,
@@ -77,6 +77,9 @@ const resourceMap = [
7777

7878
export default function DnDResource({ localizer }) {
7979
const [myEvents, setMyEvents] = useState(events)
80+
const [copyEvent, setCopyEvent] = useState(true)
81+
82+
const toggleCopyEvent = useCallback(() => setCopyEvent((val) => !val), [])
8083

8184
const moveEvent = useCallback(
8285
({
@@ -90,14 +93,26 @@ export default function DnDResource({ localizer }) {
9093
if (!allDay && droppedOnAllDaySlot) {
9194
event.allDay = true
9295
}
96+
if (Array.isArray(event.resourceId)) {
97+
if (copyEvent) {
98+
resourceId = [...new Set([...event.resourceId, resourceId])]
99+
} else {
100+
const filtered = event.resourceId.filter(
101+
(ev) => ev !== event.sourceResource
102+
)
103+
resourceId = [...new Set([...filtered, resourceId])]
104+
}
105+
} else if (copyEvent) {
106+
resourceId = [...new Set([event.resourceId, resourceId])]
107+
}
93108

94109
setMyEvents((prev) => {
95110
const existing = prev.find((ev) => ev.id === event.id) ?? {}
96111
const filtered = prev.filter((ev) => ev.id !== event.id)
97112
return [...filtered, { ...existing, start, end, resourceId, allDay }]
98113
})
99114
},
100-
[setMyEvents]
115+
[setMyEvents, copyEvent]
101116
)
102117

103118
const resizeEvent = useCallback(
@@ -125,6 +140,16 @@ export default function DnDResource({ localizer }) {
125140
<strong>
126141
Drag and Drop an "event" from one resource slot to another.
127142
</strong>
143+
<div style={{ margin: '10px 0 20px 0' }}>
144+
<label>
145+
<input
146+
type="checkbox"
147+
checked={copyEvent}
148+
onChange={toggleCopyEvent}
149+
/>
150+
Keep copy of dragged "source" event in its original resource slot.
151+
</label>
152+
</div>
128153
</DemoLink>
129154
<div className="height600">
130155
<DragAndDropCalendar

stories/demos/exampleCode/resource.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { Fragment, useMemo } from 'react'
22
import PropTypes from 'prop-types'
33
import { Calendar, Views, DateLocalizer } from 'react-big-calendar'
44
import DemoLink from '../../DemoLink.component'
5+
import LinkTo from '@storybook/addon-links/react'
56

67
const events = [
78
{
@@ -24,7 +25,7 @@ const events = [
2425
title: 'Team lead meeting',
2526
start: new Date(2018, 0, 29, 8, 30, 0),
2627
end: new Date(2018, 0, 29, 12, 30, 0),
27-
resourceId: 3,
28+
resourceId: [2, 3],
2829
},
2930
{
3031
id: 11,
@@ -54,6 +55,11 @@ export default function Resource({ localizer }) {
5455
return (
5556
<Fragment>
5657
<DemoLink fileName="resource" />
58+
<strong>
59+
The calendar below uses the <LinkTo kind="props" story="resource-id-accessor">resourceIdAccessor</LinkTo>, <LinkTo kind="props" story="resource-title-accessor">resourceTitleAccessor</LinkTo> and <LinkTo kind="props" story="resources">resources</LinkTo> props to show events scheduled for different resources.
60+
<br/>
61+
Events can be mapped to a single resource, or multiple resources.
62+
</strong>
5763
<div className="height600">
5864
<Calendar
5965
defaultDate={defaultDate}

stories/helpers/index.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,42 @@ export const backgroundEvents = [
9191
allDay: false,
9292
},
9393
]
94+
95+
export const resourceEvents = [
96+
{
97+
title: 'event 1',
98+
start: moment().startOf('day').add(1, 'hours').toDate(),
99+
end: moment().startOf('day').add(2, 'hours').toDate(),
100+
allDay: false,
101+
resourceId: 1,
102+
},
103+
{
104+
title: 'event 2',
105+
start: moment().startOf('day').add(3, 'hours').toDate(),
106+
end: moment().startOf('day').add(4, 'hours').toDate(),
107+
allDay: false,
108+
resourceId: [1, 2],
109+
},
110+
{
111+
title: 'event 3',
112+
start: moment().startOf('day').add(1, 'hours').toDate(),
113+
end: moment().startOf('day').add(3, 'hours').toDate(),
114+
allDay: false,
115+
resourceId: 3,
116+
},
117+
]
118+
119+
export const resources = [
120+
{
121+
id: 1,
122+
name: 'Resource One',
123+
},
124+
{
125+
id: 2,
126+
name: 'Resource Two',
127+
},
128+
{
129+
id: 3,
130+
name: 'Resource Three',
131+
},
132+
]

stories/props/API.stories.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ Resource {
280280
Example
281281
</LinkTo>
282282

283-
Provides a unique identifier for each resource in the <LinkTo kind="props" story="resources">resources</LinkTo> array
283+
Provides a unique identifier, or an array of unique identifiers, for each resource in the <LinkTo kind="props" story="resources">resources</LinkTo> array
284284

285285
### resourceTitleAccessor
286286

stories/props/resourceIdAccessor.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ import LinkTo from '@storybook/addon-links/react'
55

66
- type: `string | function (resource: Object) => string | number // must be unique`
77

8-
Provides a unique identifier for each resource in the <LinkTo kind="props" story="resources">resources</LinkTo> array
8+
Provides a unique identifier, or an array of unique identifiers, for each resource in the <LinkTo kind="props" story="resources">resources</LinkTo> array
99

1010
<Story id="props--resource-id-accessor" />

0 commit comments

Comments
 (0)