Work Order Scheduling
Usage
theme="ios" You can turn auto theming on and let it decide based on the platform.
Usage
themeVariant="light" You can turn auto dark mode on based on system settings.
Usage
clickToCreate={true}
// click to create is on dragToCreate={true}
// drag to create is on dragToMove={true}
// reordering is on dragToResize={true}
// resizing is on eventDelete={true}
// deleting is on Fine-tune drag & drop with these options, which are false by default.
Usage
locale={localeEn} Selecting the locale loads the localized settings and the translated UI text.
Work Order Scheduling
When scheduling work orders and synchronizing them across various assets, the timeline can be of enormous value. Resources of the same type can be grouped and arranged in multi-depth hierarchies. The list of various assets, employees, contractors can be added and organized under it.
For add/edit a custom form with the necessary fields (including resources) can be built. Resources linked to a work order can be of various categories, eg. drivers, trucks, contractors and will show up in their respective rows.
You can use the date header template and add a simple event template to show the $ value and print the total daily revenue below every day in the header.
Demo

Code
View code on GitHub
import {
Button,
Checkbox,
Datepicker,
Eventcalendar,
formatDate,
Input,
Popup,
setOptions,
Snackbar,
Textarea ,
} from '@mobiscroll/react';
import { useCallback, useMemo, useRef, useState } from 'react';
setOptions({
locale: ,
theme: ' ',
themeVariant: ' '
});
const defaultEvents = [
{
start: '2026-10-22T06:00',
end: '2026-10-22T14:00',
title: 'Farmhouse TPH',
location: '3339 Spruce Drive',
resource: ['d2', 'cm2', 'd4', 'cp1', 'cm2', 'ce2', 'b1'],
color: '#12ca6c',
cost: 48000,
},
{
start: '2026-10-23T08:00',
end: '2026-10-23T18:00',
title: 'Block of flats KXT',
location: '4698 Mercer Street',
resource: ['d1', 'cm1', 'd3', 'cp1', 'cm3', 'ce2', 'b2'],
color: '#c170c3',
cost: 36000,
},
{
start: '2026-10-24T12:00',
end: '2026-10-24T20:00',
title: 'Apartment house UGL',
location: '3647 Tavern Place',
resource: ['d3', 'cm2', 'd4', 'cp2', 'cm3', 'ce1', 'b2'],
color: '#03c9d2',
cost: 50000,
},
{
start: '2026-10-25T11:00',
end: '2026-10-25T19:00',
title: 'Detached house WKB',
location: '956 Dovetail Estates',
resource: ['d1', 'cm3', 'd4', 'cp3', 'cm4', 'c2', 'b1', 'ce2'],
color: '#ff1515',
cost: 55000,
},
{
start: '2026-10-26T10:00',
end: '2026-10-26T18:00',
title: 'Apartment house XAZ',
location: '4919 Jett Lane, Inglewood',
resource: ['d1', 'cm4', 'd4', 'cp1', 'cm2', 'c2', 'b2'],
color: '#12ca6c',
cost: 62000,
},
{
start: '2026-10-26T08:00',
end: '2026-10-26T16:00',
title: 'Block of flats DRG',
location: '486 Sycamore Fork Road',
resource: ['d2', 'cm1', 'd3', 'cp2', 'ce2', 'c1', 'b1'],
color: '#efd414',
cost: 39000,
},
{
start: '2026-10-27T09:00',
end: '2026-10-27T17:00',
title: 'Farmhouse YQK',
location: '1563 Retreat Avenue',
resource: ['d2', 'cm4', 'd4', 'cm2', 'cp1', 'c2', 'b2'],
color: '#cf49d8',
cost: 45000,
},
{
start: '2026-10-28T07:00',
end: '2026-10-28T15:00',
title: 'Apartment house SWP',
location: '628 Daylene Drive',
resource: ['d2', 'cm3', 'd3', 'cm1', 'cp2', 'c1', 'b1'],
color: '#c170c3',
cost: 53000,
},
{
start: '2026-10-29T10:00',
end: '2026-10-29T18:00',
title: 'Detached house OZL',
location: '1830 Rinehart Road',
resource: ['d3', 'cm2', 'd4', 'cp2', 'cm3', 'ce1', 'b2'],
color: '#ff1515',
cost: 47000,
},
{
start: '2026-10-30T11:00',
end: '2026-10-30T19:00',
title: 'Farmhouse PSZ',
location: '2410 Union Street',
resource: ['d1', 'cm3', 'd4', 'cp3', 'cm4', 'c2', 'b1', 'ce2'],
color: '#ff1515',
cost: 64000,
},
];
const myResources = [
{
id: 'contractors',
name: 'Contractors',
collapsed: true,
eventCreation: false,
children: [
{
id: 'builders',
name: 'Builders',
eventCreation: false,
children: [
{
id: 'b1',
name: 'Jude Chester',
},
{
id: 'b2',
name: 'Willis Kane',
},
],
},
{
id: 'carpenters',
name: 'Carpenters',
eventCreation: false,
children: [
{
id: 'c1',
name: 'Derek Austyn',
},
{
id: 'c2',
name: 'Merv Kenny',
},
],
},
],
},
{
id: 'employees',
name: 'Employees',
eventCreation: false,
children: [
{
id: 'cement_masons',
name: 'Cement masons',
eventCreation: false,
children: [
{
id: 'ce1',
name: 'Ford Kaiden',
},
{
id: 'ce2',
name: 'Jewell Ryder',
},
],
},
{
id: 'divers',
name: 'Drivers',
eventCreation: false,
children: [
{
id: 'd1',
name: 'Fred Valdez',
},
{
id: 'd2',
name: 'Jon Drake',
},
{
id: 'd3',
name: 'Lou Andie',
},
{
id: 'd4',
name: 'Leon Porter',
},
],
},
],
},
{
id: 'equipment',
name: 'Equipment',
collapsed: true,
eventCreation: false,
children: [
{
id: 'concrete_mixers',
name: 'Concrete mixers',
eventCreation: false,
children: [
{
id: 'cm1',
name: 'AL 45 RFT',
},
{
id: 'cm2',
name: 'KQ 62 PVZ',
},
{
id: 'cm3',
name: 'RG 91 ZAL',
},
{
id: 'cm4',
name: 'XF 83 GFM',
},
],
},
{
id: 'concrete_pumps',
name: 'Concrete pumps',
eventCreation: false,
children: [
{
id: 'cp1',
name: 'GF 61 BVM',
},
{
id: 'cp2',
name: 'YC 55 ECT',
},
],
},
],
},
];
const viewSettings = {
timeline: {
type: 'week',
startDay: 1,
endDay: 5,
},
};
const responsivePopup = {
medium: {
display: 'anchored',
width: 520,
fullScreen: false,
touchUi: false,
},
};
function App() {
const [myEvents, setMyEvents] = useState(defaultEvents);
const [tempEvent, setTempEvent] = useState(null);
const [isPopupOpen, setPopupOpen] = useState(false);
const [isEdit, setEdit] = useState(false);
const [anchor, setAnchor] = useState(null);
const [start, startRef] = useState(null);
const [end, endRef] = useState(null);
const [popupEventTitle, setTitle] = useState('');
const [popupEventLocation, setLocation] = useState('');
const [popupEventBill, setBill] = useState(0);
const [popupEventNotes, setNotes] = useState('');
const [popupEventDate, setDate] = useState([]);
const [checkedResources, setCheckedResources] = useState([]);
const [isSnackbarOpen, setSnackbarOpen] = useState(false);
const calInst = useRef();
const snackbarButton = useMemo(
() => ({
action: () => {
setMyEvents([...myEvents, tempEvent]);
},
text: 'Undo',
}),
[myEvents, tempEvent],
);
const checkboxChange = useCallback(
(ev) => {
const value = ev.target.value;
if (ev.target.checked) {
setCheckedResources((checkedResources) => [...checkedResources, value]);
} else {
setCheckedResources(checkedResources.filter((r) => r !== value));
}
},
[checkedResources],
);
const saveEvent = useCallback(() => {
const newEvent = {
id: tempEvent.id,
title: popupEventTitle,
location: popupEventLocation,
cost: popupEventBill,
notes: popupEventNotes,
start: popupEventDate[0],
end: popupEventDate[1],
color: tempEvent.color,
resource: checkedResources,
};
if (isEdit) {
// Update the event in the list
const index = myEvents.findIndex((x) => x.id === tempEvent.id);
const newEventList = [...myEvents];
newEventList.splice(index, 1, newEvent);
setMyEvents(newEventList);
// Here you can update the event in your storage as well
// ...
} else {
// Add the new event to the list
setMyEvents([...myEvents, newEvent]);
// Here you can add the event to your storage as well
// ...
}
calInst.current.navigateToEvent(newEvent);
// Close the popup
setPopupOpen(false);
}, [isEdit, myEvents, popupEventDate, popupEventNotes, popupEventTitle, popupEventLocation, popupEventBill, tempEvent, checkedResources]);
const deleteEvent = useCallback(
(event) => {
setMyEvents(myEvents.filter((item) => item.id !== event.id));
setTempEvent(event);
setSnackbarOpen(true);
},
[myEvents],
);
const loadPopupForm = useCallback((event) => {
setTitle(event.title);
setLocation(event.location);
setBill(event.cost);
setNotes(event.notes);
setDate([event.start, event.end]);
setCheckedResources(event.resource);
}, []);
const titleChange = useCallback((ev) => {
setTitle(ev.target.value);
}, []);
const locationChange = useCallback((ev) => {
setLocation(ev.target.value);
}, []);
const billChange = useCallback((ev) => {
setBill(+ev.target.value || 0);
}, []);
const notesChange = useCallback((ev) => {
setNotes(ev.target.value);
}, []);
const dateChange = useCallback((args) => {
setDate(args.value);
}, []);
const handleDeleteClick = useCallback(() => {
deleteEvent(tempEvent);
setPopupOpen(false);
}, [deleteEvent, tempEvent]);
const handleEventClick = useCallback(
(args) => {
setEdit(true);
setTempEvent({ ...args.event });
// Fill popup form with event data
loadPopupForm(args.event);
setAnchor(args.domEvent.target);
setPopupOpen(true);
},
[loadPopupForm],
);
const handleEventCreated = useCallback(
(args) => {
setEdit(false);
setTempEvent(args.event);
// Fill popup form with event data
loadPopupForm(args.event);
setAnchor(args.target);
// Open the popup
setPopupOpen(true);
},
[loadPopupForm],
);
const handleEventDeleted = useCallback(
(args) => {
deleteEvent(args.event);
},
[deleteEvent],
);
const headerText = useMemo(() => (isEdit ? 'Edit work order' : 'New work order'), [isEdit]);
const popupButtons = useMemo(() => {
if (isEdit) {
return [
'cancel',
{
handler: () => {
saveEvent();
},
keyCode: 'enter',
text: 'Save',
cssClass: 'mbsc-popup-button-primary',
},
];
} else {
return [
'cancel',
{
handler: () => {
saveEvent();
},
keyCode: 'enter',
text: 'Add',
cssClass: 'mbsc-popup-button-primary',
},
];
}
}, [isEdit, saveEvent]);
const handlePopupClose = useCallback(() => {
if (!isEdit) {
// Refresh the list, if add popup was canceled, to remove the temporary event
setMyEvents([...myEvents]);
}
setPopupOpen(false);
}, [isEdit, myEvents]);
const handleSnackbarClose = useCallback(() => setSnackbarOpen(false), []);
const extendMyDefaultEvent = useCallback(
() => ({
title: 'Work order',
location: '',
cost: 0,
}),
[],
);
const getCostString = useCallback((cost) => cost.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','), []);
const renderCustomDay = useCallback(
(args) => {
const events = args.events;
let costs = 0;
if (events) {
for (const event of events) {
costs += event.cost;
}
}
return (
<div>
<div className="md-work-order-date">{formatDate('DD DDD MMM YYYY', args.date)}</div>
<div className="md-work-order-date-title">{'Total revenue: $' + getCostString(costs)}</div>
</div>
);
},
[getCostString],
);
const myScheduleEvent = useCallback(
(event) => (
<div>
{event.title}
<span className="md-work-order-price-tag">${getCostString(event.original.cost)}</span>
</div>
),
[getCostString],
);
return (
<div>
<Eventcalendar
className="md-work-order-scheduling"
view={viewSettings}
data={myEvents}
ref={calInst}
resources={myResources}
clickToCreate="double"
dragToCreate={true}
dragToMove={true}
dragToResize={true}
dragTimeStep={30}
onEventClick={handleEventClick}
onEventCreated={handleEventCreated}
onEventDeleted={handleEventDeleted}
extendDefaultEvent={extendMyDefaultEvent}
renderTimelineDay={renderCustomDay}
renderTimelineEventContent={myScheduleEvent}
/>
<Popup
display="bottom"
fullScreen={true}
contentPadding={false}
headerText={headerText}
anchor={anchor}
buttons={popupButtons}
isOpen={isPopupOpen}
onClose={handlePopupClose}
responsive={responsivePopup}
>
<div className="mbsc-form-group">
<Input label="Title" value={popupEventTitle} onChange={titleChange} />
<Input label="Location" value={popupEventLocation} onChange={locationChange} />
<Input label="Bill to customer ($)" value={popupEventBill} onChange={billChange} />
<Textarea label="Notes" value={popupEventNotes} onChange={notesChange} />
</div>
<div className="mbsc-form-group">
<Input ref={startRef} label="Starts" />
<Input ref={endRef} label="Ends" />
<Datepicker
select="range"
controls={['time']}
touchUi={true}
startInput={start}
endInput={end}
showRangeLabels={false}
onChange={dateChange}
value={popupEventDate}
/>
</div>
<div className="mbsc-form-group">
<div className="mbsc-grid mbsc-no-padding">
<div className="mbsc-row">
{myResources.map((resources) =>
resources.children.map((res) => (
<div className="mbsc-col-sm-4" key={res.id}>
<>
<div className="mbsc-form-group-title">{res.name}</div>
{res.children.map((r) => (
<Checkbox
key={r.id}
value={r.id}
checked={checkedResources.indexOf(r.id) > -1}
onChange={checkboxChange}
theme="material"
className="md-work-order-checkbox-label"
>
{r.name}
</Checkbox>
))}
</>
</div>
)),
)}
</div>
</div>
</div>
<div className="mbsc-form-group">
{isEdit && (
<div className="mbsc-button-group">
<Button className="mbsc-button-block" color="danger" variant="outline" onClick={handleDeleteClick}>
Delete event
</Button>
</div>
)}
</div>
</Popup>
<Snackbar message="Event deleted" isOpen={isSnackbarOpen} onClose={handleSnackbarClose} button={snackbarButton} />
</div>
);
}
export default App;
import {
Button,
Checkbox,
Datepicker,
Eventcalendar,
formatDate,
Input,
MbscCalendarDayData,
MbscCalendarEvent,
MbscCalendarEventData,
MbscDatepickerChangeEvent,
MbscDateType,
MbscEventcalendarView,
MbscEventClickEvent,
MbscEventCreatedEvent,
MbscEventDeletedEvent,
MbscPopupButton,
MbscPopupOptions,
MbscResource,
MbscResponsiveOptions,
Popup,
setOptions,
Snackbar,
Textarea ,
} from '@mobiscroll/react';
import { ChangeEvent, FC, useCallback, useMemo, useRef, useState } from 'react';
setOptions({
locale: ,
theme: ' ',
themeVariant: ' '
});
const defaultEvents: MbscCalendarEvent[] = [
{
start: '2026-10-22T06:00',
end: '2026-10-22T14:00',
title: 'Farmhouse TPH',
location: '3339 Spruce Drive',
resource: ['d2', 'cm2', 'd4', 'cp1', 'cm2', 'ce2', 'b1'],
color: '#12ca6c',
cost: 48000,
},
{
start: '2026-10-23T08:00',
end: '2026-10-23T18:00',
title: 'Block of flats KXT',
location: '4698 Mercer Street',
resource: ['d1', 'cm1', 'd3', 'cp1', 'cm3', 'ce2', 'b2'],
color: '#c170c3',
cost: 36000,
},
{
start: '2026-10-24T12:00',
end: '2026-10-24T20:00',
title: 'Apartment house UGL',
location: '3647 Tavern Place',
resource: ['d3', 'cm2', 'd4', 'cp2', 'cm3', 'ce1', 'b2'],
color: '#03c9d2',
cost: 50000,
},
{
start: '2026-10-25T11:00',
end: '2026-10-25T19:00',
title: 'Detached house WKB',
location: '956 Dovetail Estates',
resource: ['d1', 'cm3', 'd4', 'cp3', 'cm4', 'c2', 'b1', 'ce2'],
color: '#ff1515',
cost: 55000,
},
{
start: '2026-10-26T10:00',
end: '2026-10-26T18:00',
title: 'Apartment house XAZ',
location: '4919 Jett Lane, Inglewood',
resource: ['d1', 'cm4', 'd4', 'cp1', 'cm2', 'c2', 'b2'],
color: '#12ca6c',
cost: 62000,
},
{
start: '2026-10-26T08:00',
end: '2026-10-26T16:00',
title: 'Block of flats DRG',
location: '486 Sycamore Fork Road',
resource: ['d2', 'cm1', 'd3', 'cp2', 'ce2', 'c1', 'b1'],
color: '#efd414',
cost: 39000,
},
{
start: '2026-10-27T09:00',
end: '2026-10-27T17:00',
title: 'Farmhouse YQK',
location: '1563 Retreat Avenue',
resource: ['d2', 'cm4', 'd4', 'cm2', 'cp1', 'c2', 'b2'],
color: '#cf49d8',
cost: 45000,
},
{
start: '2026-10-28T07:00',
end: '2026-10-28T15:00',
title: 'Apartment house SWP',
location: '628 Daylene Drive',
resource: ['d2', 'cm3', 'd3', 'cm1', 'cp2', 'c1', 'b1'],
color: '#c170c3',
cost: 53000,
},
{
start: '2026-10-29T10:00',
end: '2026-10-29T18:00',
title: 'Detached house OZL',
location: '1830 Rinehart Road',
resource: ['d3', 'cm2', 'd4', 'cp2', 'cm3', 'ce1', 'b2'],
color: '#ff1515',
cost: 47000,
},
{
start: '2026-10-30T11:00',
end: '2026-10-30T19:00',
title: 'Farmhouse PSZ',
location: '2410 Union Street',
resource: ['d1', 'cm3', 'd4', 'cp3', 'cm4', 'c2', 'b1', 'ce2'],
color: '#ff1515',
cost: 64000,
},
];
const myResources: MbscResource[] = [
{
id: 'contractors',
name: 'Contractors',
collapsed: true,
eventCreation: false,
children: [
{
id: 'builders',
name: 'Builders',
eventCreation: false,
children: [
{
id: 'b1',
name: 'Jude Chester',
},
{
id: 'b2',
name: 'Willis Kane',
},
],
},
{
id: 'carpenters',
name: 'Carpenters',
eventCreation: false,
children: [
{
id: 'c1',
name: 'Derek Austyn',
},
{
id: 'c2',
name: 'Merv Kenny',
},
],
},
],
},
{
id: 'employees',
name: 'Employees',
eventCreation: false,
children: [
{
id: 'cement_masons',
name: 'Cement masons',
eventCreation: false,
children: [
{
id: 'ce1',
name: 'Ford Kaiden',
},
{
id: 'ce2',
name: 'Jewell Ryder',
},
],
},
{
id: 'divers',
name: 'Drivers',
eventCreation: false,
children: [
{
id: 'd1',
name: 'Fred Valdez',
},
{
id: 'd2',
name: 'Jon Drake',
},
{
id: 'd3',
name: 'Lou Andie',
},
{
id: 'd4',
name: 'Leon Porter',
},
],
},
],
},
{
id: 'equipment',
name: 'Equipment',
collapsed: true,
eventCreation: false,
children: [
{
id: 'concrete_mixers',
name: 'Concrete mixers',
eventCreation: false,
children: [
{
id: 'cm1',
name: 'AL 45 RFT',
},
{
id: 'cm2',
name: 'KQ 62 PVZ',
},
{
id: 'cm3',
name: 'RG 91 ZAL',
},
{
id: 'cm4',
name: 'XF 83 GFM',
},
],
},
{
id: 'concrete_pumps',
name: 'Concrete pumps',
eventCreation: false,
children: [
{
id: 'cp1',
name: 'GF 61 BVM',
},
{
id: 'cp2',
name: 'YC 55 ECT',
},
],
},
],
},
];
const viewSettings: MbscEventcalendarView = {
timeline: {
type: 'week',
startDay: 1,
endDay: 5,
},
};
const responsivePopup: MbscResponsiveOptions<MbscPopupOptions> = {
medium: {
display: 'anchored',
width: 520,
fullScreen: false,
touchUi: false,
},
};
const App: FC = () => {
const [myEvents, setMyEvents] = useState<MbscCalendarEvent[]>(defaultEvents);
const [tempEvent, setTempEvent] = useState<MbscCalendarEvent>();
const [isPopupOpen, setPopupOpen] = useState<boolean>(false);
const [isEdit, setEdit] = useState<boolean>(false);
const [anchor, setAnchor] = useState<HTMLElement>();
const [start, startRef] = useState<Input | null>(null);
const [end, endRef] = useState<Input | null>(null);
const [popupEventTitle, setTitle] = useState<string | undefined>('');
const [popupEventLocation, setLocation] = useState<string | undefined>('');
const [popupEventBill, setBill] = useState<number>(0);
const [popupEventNotes, setNotes] = useState<string>('');
const [popupEventDate, setDate] = useState<MbscDateType[]>([]);
const [checkedResources, setCheckedResources] = useState<string[]>([]);
const [isSnackbarOpen, setSnackbarOpen] = useState<boolean>(false);
const calInst = useRef<Eventcalendar | null>(null);
const snackbarButton = useMemo(
() => ({
action: () => {
setMyEvents([...myEvents, tempEvent!]);
},
text: 'Undo',
}),
[myEvents, tempEvent],
);
const checkboxChange = useCallback(
(ev: ChangeEvent<HTMLInputElement>) => {
const value = ev.target.value;
if (ev.target.checked) {
setCheckedResources((checkedResources) => [...checkedResources, value]);
} else {
setCheckedResources(checkedResources.filter((r) => r !== value));
}
},
[checkedResources],
);
const saveEvent = useCallback(() => {
const newEvent = {
id: tempEvent!.id,
title: popupEventTitle,
location: popupEventLocation,
cost: popupEventBill,
notes: popupEventNotes,
start: popupEventDate[0],
end: popupEventDate[1],
color: tempEvent!.color,
resource: checkedResources,
};
if (isEdit) {
// Update the event in the list
const index = myEvents.findIndex((x) => x.id === tempEvent!.id);
const newEventList = [...myEvents];
newEventList.splice(index, 1, newEvent);
setMyEvents(newEventList);
// Here you can update the event in your storage as well
// ...
} else {
// Add the new event to the list
setMyEvents([...myEvents, newEvent]);
// Here you can add the event to your storage as well
// ...
}
calInst.current?.navigateToEvent(newEvent);
// Close the popup
setPopupOpen(false);
}, [isEdit, myEvents, popupEventDate, popupEventNotes, popupEventTitle, popupEventLocation, popupEventBill, tempEvent, checkedResources]);
const deleteEvent = useCallback(
(event: MbscCalendarEvent) => {
setMyEvents(myEvents.filter((item) => item.id !== event.id));
setTempEvent(event);
setSnackbarOpen(true);
},
[myEvents],
);
const loadPopupForm = useCallback((event: MbscCalendarEvent) => {
setTitle(event.title);
setLocation(event.location);
setBill(event.cost);
setNotes(event.notes);
setDate([event.start!, event.end!]);
setCheckedResources([event.resource as string]);
}, []);
// Handle popup form changes
const titleChange = useCallback((ev: ChangeEvent<HTMLInputElement>) => {
setTitle(ev.target.value);
}, []);
const locationChange = useCallback((ev: ChangeEvent<HTMLInputElement>) => {
setLocation(ev.target.value);
}, []);
const billChange = useCallback((ev: ChangeEvent<HTMLInputElement>) => {
setBill(+ev.target.value || 0);
}, []);
const notesChange = useCallback((ev: ChangeEvent<HTMLInputElement>) => {
setNotes(ev.target.value);
}, []);
const dateChange = useCallback((args: MbscDatepickerChangeEvent) => {
setDate(args.value as MbscDateType[]);
}, []);
const handleDeleteClick = useCallback(() => {
deleteEvent(tempEvent!);
setPopupOpen(false);
}, [deleteEvent, tempEvent]);
const handleEventClick = useCallback(
(args: MbscEventClickEvent) => {
setEdit(true);
setTempEvent({ ...args.event });
// Fill popup form with event data
loadPopupForm(args.event);
setAnchor(args.domEvent.target);
setPopupOpen(true);
},
[loadPopupForm],
);
const handleEventCreated = useCallback(
(args: MbscEventCreatedEvent) => {
setEdit(false);
setTempEvent(args.event);
// Fill popup form with event data
loadPopupForm(args.event);
setAnchor(args.target);
// Open the popup
setPopupOpen(true);
},
[loadPopupForm],
);
const handleEventDeleted = useCallback(
(args: MbscEventDeletedEvent) => {
deleteEvent(args.event);
},
[deleteEvent],
);
const headerText = useMemo(() => (isEdit ? 'Edit work order' : 'New work order'), [isEdit]);
const popupButtons = useMemo<(string | MbscPopupButton)[]>(() => {
if (isEdit) {
return [
'cancel',
{
handler: () => {
saveEvent();
},
keyCode: 'enter',
text: 'Save',
cssClass: 'mbsc-popup-button-primary',
},
];
} else {
return [
'cancel',
{
handler: () => {
saveEvent();
},
keyCode: 'enter',
text: 'Add',
cssClass: 'mbsc-popup-button-primary',
},
];
}
}, [isEdit, saveEvent]);
const handlePopupClose = useCallback(() => {
if (!isEdit) {
// Refresh the list, if add popup was canceled, to remove the temporary event
setMyEvents([...myEvents]);
}
setPopupOpen(false);
}, [isEdit, myEvents]);
const handleSnackbarClose = useCallback(() => setSnackbarOpen(false), []);
const extendDefaultEvent = useCallback(
() => ({
title: 'Work order',
location: '',
cost: 0,
}),
[],
);
const getCostString = (cost: number) => cost.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
const renderCustomDay = (args: MbscCalendarDayData) => {
const events = args.events;
let costs = 0;
if (events) {
for (const event of events) {
costs += event.cost;
}
}
return (
<div>
<div className="md-work-order-date">{formatDate('DD DDD MMM YYYY', args.date)}</div>
<div className="md-work-order-date-title">{'Total revenue: $' + getCostString(costs)}</div>
</div>
);
};
const myScheduleEvent = useCallback(
(event: MbscCalendarEventData) => (
<div>
{event.title}
<span className="md-work-order-price-tag">${getCostString(event.original!.cost)}</span>
</div>
),
[],
);
return (
<div>
<Eventcalendar
className="md-work-order-scheduling"
view={viewSettings}
data={myEvents}
ref={calInst}
resources={myResources}
clickToCreate="double"
dragToCreate={true}
dragToMove={true}
dragToResize={true}
dragTimeStep={30}
onEventClick={handleEventClick}
onEventCreated={handleEventCreated}
onEventDeleted={handleEventDeleted}
extendDefaultEvent={extendDefaultEvent}
renderTimelineDay={renderCustomDay}
renderTimelineEventContent={myScheduleEvent}
/>
<Popup
display="bottom"
fullScreen={true}
contentPadding={false}
headerText={headerText}
anchor={anchor}
buttons={popupButtons}
isOpen={isPopupOpen}
onClose={handlePopupClose}
responsive={responsivePopup}
>
<div className="mbsc-form-group">
<Input label="Title" value={popupEventTitle} onChange={titleChange} />
<Input label="Location" value={popupEventLocation} onChange={locationChange} />
<Input label="Bill to customer ($)" value={popupEventBill.toString()} onChange={billChange} />
<Textarea label="Notes" value={popupEventNotes} onChange={notesChange} />
</div>
<div className="mbsc-form-group">
<Input ref={startRef} label="Starts" />
<Input ref={endRef} label="Ends" />
<Datepicker
select="range"
controls={['time']}
touchUi={true}
startInput={start}
endInput={end}
showRangeLabels={false}
onChange={dateChange}
value={popupEventDate}
/>
</div>
<div className="mbsc-form-group">
<div className="mbsc-grid mbsc-no-padding">
<div className="mbsc-row">
{myResources.map(
(resources: MbscResource) =>
resources.children &&
resources.children.map((res: MbscResource) => (
<div className="mbsc-col-sm-4" key={res.id}>
<>
<div className="mbsc-form-group-title">{res.name}</div>
{res.children &&
res.children.map((r: MbscResource) => (
<Checkbox
key={r.id}
value={r.id}
checked={checkedResources.indexOf(r.id as string) > -1}
onChange={checkboxChange}
theme="material"
className="md-work-order-checkbox-label"
>
{r.name}
</Checkbox>
))}
</>
</div>
)),
)}
</div>
</div>
</div>
<div className="mbsc-form-group">
{isEdit && (
<div className="mbsc-button-group">
<Button className="mbsc-button-block" color="danger" variant="outline" onClick={handleDeleteClick}>
Delete work order
</Button>
</div>
)}
</div>
</Popup>
<Snackbar message="Event deleted" isOpen={isSnackbarOpen} onClose={handleSnackbarClose} button={snackbarButton} />
</div>
);
};
export default App;
.md-work-order-checkbox-label.mbsc-checkbox {
padding-top: 5px;
padding-bottom: 5px;
}
.md-work-order-date {
font-size: 14px;
border-bottom: 1px solid #ccc;
padding: 5px 10px;
}
.md-work-order-date-title {
font-size: 13px;
padding: 5px 10px;
line-height: 18px;
opacity: 0.6;
}
.md-work-order-price-tag {
display: inline-block;
font-size: 11px;
line-height: 16px;
vertical-align: middle;
border: 1px solid currentColor;
border-radius: 5px;
margin: 0 10px;
padding: 0px 5px;
opacity: 0.6;
}
.md-work-order-scheduling .mbsc-timeline-parent {
height: 32px;
}