Overview
The React Scheduler component from DayPilot Pro for JavaScript displays a timeline for the specified resources. The resources are displayed as rows and the time slots are displayed as columns.
In this tutorial, we will focus on add UI controls to the rows that represent resources (buildings, people, tools, cars, machines) that will let users of your React application manage the resources - add, move, delete and edit them.
License
Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript.
How to load Scheduler resource data in React?
As soon as you have the React Scheduler installed and configured (see React Scheduler Component Tutorial for an introduction), you can load the Scheduler data.
The following example loads the resource data:
const [resources, setResources] = useState([]);
useEffect(() => {
setResources([
{name: "Group A", id: "GroupA", expanded: true, children: [
{name: "Resource A", id: "A"},
{name: "Resource B", id: "B"},
{name: "Resource C", id: "C"},
{name: "Resource D", id: "D"},
{name: "Resource E", id: "E"},
{name: "Resource F", id: "F"},
{name: "Resource G", id: "G"}
]
},
{name: "Group B", id: "GroupB", expanded: true, children: [
{name: "Resource H", id: "H"},
{name: "Resource I", id: "I"},
]}
]);
}, []);
return (
<div>
<DayPilotScheduler
resources={resources}
/>
</div>
);
How to add resource groups?
The Scheduler includes a built-in UI feature for adding new rows. We will use it to provide an interface for adding new groups (top-level rows).
The first step is to enable the feature using rowCreateHandling property. This will display a special row at the bottom of the Scheduler. By default, it displays “New row…” text. We will change this to “New group…” using the rowCreateText property.
As soon as the user enters a new group name in the field and confirms using <enter>, the Scheduler will fire onRowCreate event. We will use this event to add a new group to the Scheduler using rows.add() method.
This new row will be displayed as the last item at the top level.
const [config, setConfig] = useState({
rowCreateHandling: "Enabled",
rowCreateText: "New group...",
onRowCreate: args => {
const scheduler = schedulerRef.current.control;
const row = {
name: args.text,
id: DayPilot.guid()
}
scheduler.rows.add(row);
},
// ...
});
How to add child resources?
In order to let users add child resources to the groups, we will enable a row header context menu using contextMenuResource property.
The “Add resource…” item will open a modal dialog where the user can enter the new resource name.
When the modal dialog closes, we add the new resource as the last child of the selected group using rows.addChild() method of the Scheduler.
const [config, setConfig] = useState({
contextMenuResource: new DayPilot.Menu({
onShow: args => {
const row = args.source;
const isParent = row.level === 0;
args.menu.items[3].disabled = !isParent;
},
items: [
{text: "Add resource....", onClick: args => add(args)}
]
}),
// ...
});
const add = async (args) => {
const scheduler = schedulerRef.current.control;
const parent = args.source;
const form = [
{name: "Name", id: "name"}
];
const data = {
name: "Resource"
};
const modal = await DayPilot.Modal.form(form, data);
if (modal.canceled) {
return;
}
const row = modal.result;
row.id = DayPilot.guid();
scheduler.rows.addChild(parent, row);
};
How to edit Scheduler resources?
Now we can extend the context menu with additional items. The “Edit…” item will let users modify the resource name.
It opens a modal dialog that lets users edit the resource name. The Scheduler updates the resource data using rows.update() method.
const [config, setConfig] = useState({
contextMenuResource: new DayPilot.Menu({
onShow: args => {
const row = args.source;
const isParent = row.level === 0;
args.menu.items[3].disabled = !isParent;
},
items: [
{text: "Edit...", onClick: args => edit(args) },
]
}),
// ...
});
const edit = async (args) => {
const scheduler = schedulerRef.current.control;
const form = [
{name: "Name", id: "name"}
];
const row = args.source;
const data = row.data;
const modal = await DayPilot.Modal.form(form, data);
if (modal.canceled) {
return;
}
scheduler.rows.update(modal.result);
};
How to delete resources?
The “Delete…” menu item will let users delete a group or resource. Before the resource is actually deleted, we display a modal dialog with a confirmation dialog created using DayPilot.Modal.confirm() method.
const [config, setConfig] = useState({
contextMenuResource: new DayPilot.Menu({
onShow: args => {
const row = args.source;
const isParent = row.level === 0;
args.menu.items[3].disabled = !isParent;
},
items: [
{text: "Delete...", onClick: args => remove(args) },
]
}),
// ...
});
const remove = async (args) => {
const scheduler = schedulerRef.current.control;
const row = args.source;
const modal = await DayPilot.Modal.confirm(`Do you really want to delete '${row.name}'?`);
if (modal.canceled) {
return;
}
scheduler.rows.remove(row);
};
How to add an icon to the row header?
The resource context menu opens when the user right-clicks the row header. In order to make that option more prominent, we will add a special icon to the row header which will display on hover.
The icon can be added using an active area.
You can specify the active area position and dimensions (right
, top
, width
, height
properties), the SVG icon (symbol
property), hover visibility (visibility
property).
The action is set to "ContextMenu"
. This command will open the default context menu associated with the row header.
const [config, setConfig] = useState({
onBeforeRowHeaderRender: args => {
args.row.areas = [
{
right: 6,
top: 6,
width: 18,
height: 18,
action: "ContextMenu",
backColor: "#ffffff",
symbol: "icons/daypilot.svg#minichevron-down-4",
fontColor: "#cccccc",
style: "border: 1px solid #ccc; cursor: pointer;",
visibility: "Hover"
}
]
},
// ...
});
How to move resources using drag an drop?
In order to enable drag and drop row moving, you need to set rowMoveHandling property to "Update"
.
When row moving is enabled, the React Scheduler will display a drag handle on hover. The users can drag the resource to a new location using this drag handle.
By default, the Scheduler lets users drag the rows to an arbitrary position in the tree. As we want to the resources and groups stay at the same level (only groups are allowed at the top level and groups can only contain resources, not other groups) we will enforce the same level using rowMoveSaveLevelOnly property.
const [config, setConfig] = useState({
rowMoveHandling: "Update",
onRowMove: args => {
console.log("onRowMove", args);
},
rowMoveSameLevelOnly: true,
// ...
});
Full Source Code
Here is the complete source code for the React Scheduler component with a UI for managing resources. You can also download the entire React project using the link at the top of this article.
import React, { useEffect, useRef, useState } from 'react';
import { DayPilot, DayPilotScheduler } from "daypilot-pro-react";
const Scheduler = () => {
const schedulerRef = useRef();
const [events, setEvents] = useState([]);
const [resources, setResources] = useState([]);
const [config, setConfig] = useState({
timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
scale: "Day",
days: 31,
startDate: "2025-07-01",
onTimeRangeSelected: async (args) => {
const scheduler = args.control;
const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
scheduler.clearSelection();
if (modal.canceled) { return; }
scheduler.events.add({
start: args.start,
end: args.end,
id: DayPilot.guid(),
resource: args.resource,
text: modal.result
});
},
treeEnabled: true,
rowCreateHandling: "Enabled",
rowCreateText: "New group...",
onRowCreate: args => {
const scheduler = schedulerRef.current.control;
const row = {
name: args.text,
id: DayPilot.guid()
}
scheduler.rows.add(row);
},
rowMoveSameLevelOnly: true,
rowMoveHandling: "Update",
onRowMove: args => {
console.log("onRowMove", args);
},
onBeforeRowHeaderRender: args => {
args.row.areas = [
{
right: 6,
top: 6,
width: 18,
height: 18,
action: "ContextMenu",
backColor: "#ffffff",
symbol: "icons/daypilot.svg#minichevron-down-4",
fontColor: "#cccccc",
style: "border: 1px solid #ccc; cursor: pointer;",
visibility: "Hover"
}
]
},
contextMenuResource: new DayPilot.Menu({
onShow: args => {
const row = args.source;
const isParent = row.level === 0;
args.menu.items[3].disabled = !isParent;
},
items: [
{text: "Edit...", onClick: args => edit(args) },
{text: "Delete...", onClick: args => remove(args) },
{text: "-"},
{text: "Add resource....", onClick: args => add(args)}
]
})
});
const edit = async (args) => {
const scheduler = schedulerRef.current.control;
const form = [
{name: "Name", id: "name"}
];
const row = args.source;
const data = row.data;
const modal = await DayPilot.Modal.form(form, data);
if (modal.canceled) {
return;
}
scheduler.rows.update(modal.result);
};
const remove = async (args) => {
const scheduler = schedulerRef.current.control;
const row = args.source;
const modal = await DayPilot.Modal.confirm(`Do you really want to delete '${row.name}'?`);
if (modal.canceled) {
return;
}
scheduler.rows.remove(row);
};
const add = async (args) => {
const scheduler = schedulerRef.current.control;
const parent = args.source;
const form = [
{name: "Name", id: "name"}
];
const data = {
name: "Resource"
};
const modal = await DayPilot.Modal.form(form, data);
if (modal.canceled) {
return;
}
const row = modal.result;
row.id = DayPilot.guid();
scheduler.rows.addChild(parent, row);
};
useEffect(() => {
setResources([
{name: "Group A", id: "GroupA", expanded: true, children: [
{name: "Resource A", id: "A"},
{name: "Resource B", id: "B"},
{name: "Resource C", id: "C"},
{name: "Resource D", id: "D"},
{name: "Resource E", id: "E"},
{name: "Resource F", id: "F"},
{name: "Resource G", id: "G"}
]
},
{name: "Group B", id: "GroupB", expanded: true, children: [
{name: "Resource H", id: "H"},
{name: "Resource I", id: "I"},
]}
]);
setEvents([
{
id: 1,
text: "Event 1",
start: "2025-07-02T00:00:00",
end: "2025-07-05T00:00:00",
resource: "A"
},
{
id: 2,
text: "Event 2",
start: "2025-07-03T00:00:00",
end: "2025-07-10T00:00:00",
resource: "C",
barColor: "#38761d",
barBackColor: "#93c47d"
},
{
id: 3,
text: "Event 3",
start: "2025-07-02T00:00:00",
end: "2025-07-08T00:00:00",
resource: "E",
barColor: "#f1c232",
barBackColor: "#f1c232"
},
{
id: 4,
text: "Event 3",
start: "2025-07-02T00:00:00",
end: "2025-07-08T00:00:00",
resource: "G",
barColor: "#cc0000",
barBackColor: "#ea9999"
}
]);
}, []);
return (
<div>
<DayPilotScheduler
{...config}
events={events}
resources={resources}
ref={schedulerRef}
/>
</div>
);
}
export default Scheduler;