Skip to content

Commit

Permalink
feat: Support multiple resources on an event
Browse files Browse the repository at this point in the history
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]>

jquense#2405 jquense#1649
  • Loading branch information
basstager authored Jun 2, 2023
1 parent c7cd908 commit 91155c5
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/Calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion src/DayColumn.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
Expand Down
6 changes: 5 additions & 1 deletion src/addons/dragAndDrop/EventWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class EventWrapper extends React.Component {
continuesAfter: PropTypes.bool,
isDragging: PropTypes.bool,
isResizing: PropTypes.bool,
resource: PropTypes.number,
resizable: PropTypes.bool,
}

Expand Down Expand Up @@ -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) {
Expand Down
14 changes: 11 additions & 3 deletions src/utils/Resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down
26 changes: 25 additions & 1 deletion stories/DragAndDrop.stories.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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'),
}
29 changes: 27 additions & 2 deletions stories/demos/exampleCode/dndresource.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
({
Expand All @@ -90,14 +93,26 @@ 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) ?? {}
const filtered = prev.filter((ev) => ev.id !== event.id)
return [...filtered, { ...existing, start, end, resourceId, allDay }]
})
},
[setMyEvents]
[setMyEvents, copyEvent]
)

const resizeEvent = useCallback(
Expand Down Expand Up @@ -125,6 +140,16 @@ export default function DnDResource({ localizer }) {
<strong>
Drag and Drop an "event" from one resource slot to another.
</strong>
<div style={{ margin: '10px 0 20px 0' }}>
<label>
<input
type="checkbox"
checked={copyEvent}
onChange={toggleCopyEvent}
/>
Keep copy of dragged "source" event in its original resource slot.
</label>
</div>
</DemoLink>
<div className="height600">
<DragAndDropCalendar
Expand Down
8 changes: 7 additions & 1 deletion stories/demos/exampleCode/resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { Fragment, useMemo } from 'react'
import PropTypes from 'prop-types'
import { Calendar, Views, DateLocalizer } from 'react-big-calendar'
import DemoLink from '../../DemoLink.component'
import LinkTo from '@storybook/addon-links/react'

const events = [
{
Expand All @@ -24,7 +25,7 @@ const events = [
title: 'Team lead meeting',
start: new Date(2018, 0, 29, 8, 30, 0),
end: new Date(2018, 0, 29, 12, 30, 0),
resourceId: 3,
resourceId: [2, 3],
},
{
id: 11,
Expand Down Expand Up @@ -54,6 +55,11 @@ export default function Resource({ localizer }) {
return (
<Fragment>
<DemoLink fileName="resource" />
<strong>
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.
<br/>
Events can be mapped to a single resource, or multiple resources.
</strong>
<div className="height600">
<Calendar
defaultDate={defaultDate}
Expand Down
39 changes: 39 additions & 0 deletions stories/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,42 @@ export const backgroundEvents = [
allDay: false,
},
]

export const resourceEvents = [
{
title: 'event 1',
start: moment().startOf('day').add(1, 'hours').toDate(),
end: moment().startOf('day').add(2, 'hours').toDate(),
allDay: false,
resourceId: 1,
},
{
title: 'event 2',
start: moment().startOf('day').add(3, 'hours').toDate(),
end: moment().startOf('day').add(4, 'hours').toDate(),
allDay: false,
resourceId: [1, 2],
},
{
title: 'event 3',
start: moment().startOf('day').add(1, 'hours').toDate(),
end: moment().startOf('day').add(3, 'hours').toDate(),
allDay: false,
resourceId: 3,
},
]

export const resources = [
{
id: 1,
name: 'Resource One',
},
{
id: 2,
name: 'Resource Two',
},
{
id: 3,
name: 'Resource Three',
},
]
2 changes: 1 addition & 1 deletion stories/props/API.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ Resource {
Example
</LinkTo>

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

### resourceTitleAccessor

Expand Down
2 changes: 1 addition & 1 deletion stories/props/resourceIdAccessor.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <LinkTo kind="props" story="resources">resources</LinkTo> array
Provides a unique identifier, or an array of unique identifiers, for each resource in the <LinkTo kind="props" story="resources">resources</LinkTo> array

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

0 comments on commit 91155c5

Please sign in to comment.