diff --git a/docs/docs/widgets/calendar.md b/docs/docs/widgets/calendar.md
new file mode 100644
index 0000000000..717d308ba3
--- /dev/null
+++ b/docs/docs/widgets/calendar.md
@@ -0,0 +1,109 @@
+---
+sidebar_position: 22
+---
+
+# Calendar
+Calendar widget comes with the following features:
+- Day, month and week level views
+- Events
+- Resource scheduling
+
+
+
+### Properties
+
+#### Date format
+Determines the format in which any date passed to the calendar via any of the properties will be parsed.
+It also determines the format in which any date made available by the calendar via exposed variables will be displayed.
+It uses the date format conventions of [moment.js](https://momentjs.com/).
+#### Default date
+Determines the date on which the calendar's view will be centered on.
+If the calendar is on `month` view, it will show the month on which this date exists.
+If the calendar is on `week` view, it will show the week on which this date exists.
+This property needs to be formatted using the `Date format` property which is configurable on the inspector.
+#### Events
+`Events` property should contain an array of objects, each of which describes the events that the calendar needs to display.
+
+Assuming that you set the date format to `MM-DD-YYYY HH:mm:ss A Z`, setting the `Events` property to the following code snippet will display an event titled `Sample Event` at the first hour of this day, as displayed in the image of calendar at the beginning of this page.
+
+```javascript
+{{[
+ {
+ title: 'Sample event',
+ start: `${moment().startOf('day').format('MM-DD-YYYY HH:mm:ss A Z')}`,
+ end: `${moment().endOf('day').format('MM-DD-YYYY HH:mm:ss A Z')}`,
+ allDay: false,
+ tooltip: 'Sample event',
+ color: 'lightgreen',
+ }
+]}}
+```
+
+##### Event object properties
+
+| Name | Description |
+|------|-------------|
+| title | Title of the event |
+| start | The date(and time) on which this event begins. Needs to be formatted in the `Date format` you've supplied |
+| end | The date(and time) on which this event ends. Needs to be formatted in the `Date format` you've supplied |
+| allDay | Optional. Qualifies the event as an 'All day event', which will pin it to date headers on `day` and `week` level views |
+| tooltip | Tooltip which will be display when the user hovers over the event |
+| color | Background color of the event, any css supported color name or hex code can be used |
+| textOrientation | Optional. If it is set to `vertical`, the title of the event will be oriented vertically. |
+| resourceId | Applicable only if you're using resource scheduling. This is the id of the resource to which this event correspond to. |
+
+You may supply any other additional property to the event(s). These additional properties will available to you when the calendar widget
+exposes any of the events via its exposed variables.
+
+#### Resources
+
+Specifying resources will make the calendar categorize `week` view and `day` view for each of the resources specified.
+
+ For example, to categorize week/day view into for three rooms, we specify `resources` this way:
+
+```javascript
+{{
+ [
+ {resourceId: 1, title: 'Room A'},
+ {resourceId: 2, title: 'Room B'},
+ {resourceId: 3, title: 'Room C'},
+ ]
+}}
+```
+
+If we specify the `resourceId` of any of the events as `1`, then that event will be assigned to `Room A`, generating the following calendar, assuming that we've set the view to `day` and are viewing the day on which this event exists.
+
+
+
+#### Default view
+
+Determines whether the calendar would display a `day`, a `week` or a `month`.
+Setting this property to anything other than these values will make the calendar default to `month` view.
+
+#### Show toolbar
+
+Determines whether the calendar toolbar should be displayed or not.
+
+#### Show view switcher
+
+Determinues whether the calendar's buttons that allow user to switch between `month`, `week` and `day` level views will be displayed.
+### Styles
+#### Cell size in views classified by resource
+
+When `resources` are specified, the calendar could take up quite a lot of horizontal space, making the horizontal scroll bar of calendar having to be relied upon all the time.
+
+If we set this property to `compact`, the cell sizes will be smaller in `week` and `day` views.
+
+### Events
+
+#### On Event selected
+
+This event is fired when the user clicks on a calendar event.
+
+Last selected event is exposed as `selectedEvent`.
+
+#### on Slot selected
+
+This event is fired when the user either clicks on an calendar slot(empty cell or empty space of a cell with event) or when they click and drag to select multiple slots.
+
+Last selected slot(s) are exposed as `selectedSlots`.
\ No newline at end of file
diff --git a/docs/static/img/widgets/calendar/calendar-day.png b/docs/static/img/widgets/calendar/calendar-day.png
new file mode 100644
index 0000000000..740f9f09e3
Binary files /dev/null and b/docs/static/img/widgets/calendar/calendar-day.png differ
diff --git a/docs/static/img/widgets/calendar/calendar-resource.png b/docs/static/img/widgets/calendar/calendar-resource.png
new file mode 100644
index 0000000000..3bd551187c
Binary files /dev/null and b/docs/static/img/widgets/calendar/calendar-resource.png differ
diff --git a/docs/static/img/widgets/calendar/calendar-week.png b/docs/static/img/widgets/calendar/calendar-week.png
new file mode 100644
index 0000000000..81a9743620
Binary files /dev/null and b/docs/static/img/widgets/calendar/calendar-week.png differ
diff --git a/docs/static/img/widgets/calendar/calendar1.png b/docs/static/img/widgets/calendar/calendar1.png
new file mode 100644
index 0000000000..a6d6a090e2
Binary files /dev/null and b/docs/static/img/widgets/calendar/calendar1.png differ
diff --git a/frontend/assets/images/icons/widgets/calendar.svg b/frontend/assets/images/icons/widgets/calendar.svg
new file mode 100644
index 0000000000..ec39ebc307
--- /dev/null
+++ b/frontend/assets/images/icons/widgets/calendar.svg
@@ -0,0 +1,69 @@
+
+
+
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 386ffb856a..72b571e3c0 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -41,6 +41,7 @@
"plotly.js-basic-dist-min": "^1.58.4",
"query-string": "^6.13.6",
"react": "^16.14.0",
+ "react-big-calendar": "^0.38.0",
"react-bootstrap": "^1.5.2",
"react-color": "^2.19.3",
"react-copy-to-clipboard": "^5.0.3",
@@ -7594,6 +7595,11 @@
"webidl-conversions": "^4.0.2"
}
},
+ "node_modules/date-arithmetic": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-arithmetic/-/date-arithmetic-4.1.0.tgz",
+ "integrity": "sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg=="
+ },
"node_modules/date-fns": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
@@ -14233,6 +14239,11 @@
"node": ">= 0.6"
}
},
+ "node_modules/memoize-one": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
+ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
+ },
"node_modules/memory-fs": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@@ -17404,6 +17415,47 @@
"pure-color": "^1.2.0"
}
},
+ "node_modules/react-big-calendar": {
+ "version": "0.38.0",
+ "resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-0.38.0.tgz",
+ "integrity": "sha512-eoVkt9gTo+f1HBL09+o7dYLxp6QxHv52fcn50P5PfaWp3S98uGLQqoqsvghT85koMKvGfDVa5V0+J7yHcaF07Q==",
+ "dependencies": {
+ "@babel/runtime": "^7.1.5",
+ "clsx": "^1.0.4",
+ "date-arithmetic": "^4.1.0",
+ "dom-helpers": "^5.1.0",
+ "invariant": "^2.2.4",
+ "lodash": "^4.17.11",
+ "lodash-es": "^4.17.11",
+ "memoize-one": "^5.1.1",
+ "prop-types": "^15.7.2",
+ "react-overlays": "^4.1.1",
+ "uncontrollable": "^7.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.6.1 || ^17",
+ "react-dom": "^16.6.1 || ^17"
+ }
+ },
+ "node_modules/react-big-calendar/node_modules/react-overlays": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-4.1.1.tgz",
+ "integrity": "sha512-WtJifh081e6M24KnvTQoNjQEpz7HoLxqt8TwZM7LOYIkYJ8i/Ly1Xi7RVte87ZVnmqQ4PFaFiNHZhSINPSpdBQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.1",
+ "@popperjs/core": "^2.5.3",
+ "@restart/hooks": "^0.3.25",
+ "@types/warning": "^3.0.0",
+ "dom-helpers": "^5.2.0",
+ "prop-types": "^15.7.2",
+ "uncontrollable": "^7.0.0",
+ "warning": "^4.0.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.3.0",
+ "react-dom": ">=16.3.0"
+ }
+ },
"node_modules/react-bootstrap": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.5.2.tgz",
@@ -30304,6 +30356,11 @@
}
}
},
+ "date-arithmetic": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-arithmetic/-/date-arithmetic-4.1.0.tgz",
+ "integrity": "sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg=="
+ },
"date-fns": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
@@ -35470,6 +35527,11 @@
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
+ "memoize-one": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
+ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
+ },
"memory-fs": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@@ -38058,6 +38120,41 @@
"pure-color": "^1.2.0"
}
},
+ "react-big-calendar": {
+ "version": "0.38.0",
+ "resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-0.38.0.tgz",
+ "integrity": "sha512-eoVkt9gTo+f1HBL09+o7dYLxp6QxHv52fcn50P5PfaWp3S98uGLQqoqsvghT85koMKvGfDVa5V0+J7yHcaF07Q==",
+ "requires": {
+ "@babel/runtime": "^7.1.5",
+ "clsx": "^1.0.4",
+ "date-arithmetic": "^4.1.0",
+ "dom-helpers": "^5.1.0",
+ "invariant": "^2.2.4",
+ "lodash": "^4.17.11",
+ "lodash-es": "^4.17.11",
+ "memoize-one": "^5.1.1",
+ "prop-types": "^15.7.2",
+ "react-overlays": "^4.1.1",
+ "uncontrollable": "^7.0.0"
+ },
+ "dependencies": {
+ "react-overlays": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-4.1.1.tgz",
+ "integrity": "sha512-WtJifh081e6M24KnvTQoNjQEpz7HoLxqt8TwZM7LOYIkYJ8i/Ly1Xi7RVte87ZVnmqQ4PFaFiNHZhSINPSpdBQ==",
+ "requires": {
+ "@babel/runtime": "^7.12.1",
+ "@popperjs/core": "^2.5.3",
+ "@restart/hooks": "^0.3.25",
+ "@types/warning": "^3.0.0",
+ "dom-helpers": "^5.2.0",
+ "prop-types": "^15.7.2",
+ "uncontrollable": "^7.0.0",
+ "warning": "^4.0.3"
+ }
+ }
+ }
+ },
"react-bootstrap": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.5.2.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index deb6477167..d44ad27a84 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -36,6 +36,7 @@
"plotly.js-basic-dist-min": "^1.58.4",
"query-string": "^6.13.6",
"react": "^16.14.0",
+ "react-big-calendar": "^0.38.0",
"react-bootstrap": "^1.5.2",
"react-color": "^2.19.3",
"react-copy-to-clipboard": "^5.0.3",
diff --git a/frontend/src/Editor/Box.jsx b/frontend/src/Editor/Box.jsx
index 43b5e4ed75..ec1d1b0aab 100644
--- a/frontend/src/Editor/Box.jsx
+++ b/frontend/src/Editor/Box.jsx
@@ -23,6 +23,7 @@ import { StarRating } from './Components/StarRating';
import { Divider } from './Components/Divider';
import { FilePicker } from './Components/FilePicker';
import { PasswordInput } from './Components/PasswordInput';
+import { Calendar } from './Components/Calendar';
import { renderTooltip } from '../_helpers/appUtils';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import '@/_styles/custom.scss';
@@ -54,6 +55,7 @@ const AllComponents = {
Divider,
FilePicker,
PasswordInput,
+ Calendar,
};
export const Box = function Box({
diff --git a/frontend/src/Editor/Components/Calendar.jsx b/frontend/src/Editor/Components/Calendar.jsx
new file mode 100644
index 0000000000..3d1121f351
--- /dev/null
+++ b/frontend/src/Editor/Components/Calendar.jsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import { Calendar as ReactCalendar, momentLocalizer } from 'react-big-calendar';
+import moment from 'moment';
+import 'react-big-calendar/lib/css/react-big-calendar.css';
+
+const localizer = momentLocalizer(moment);
+
+const prepareEvent = (event, dateFormat) => ({
+ ...event,
+ start: moment(event.start, dateFormat).toDate(),
+ end: moment(event.end, dateFormat).toDate(),
+});
+
+const parseDate = (date, dateFormat) => moment(date, dateFormat).toDate();
+
+const allowedCalendarViews = ['month', 'week', 'day'];
+
+export const Calendar = function ({ height, width, properties, styles, fireEvent, darkMode }) {
+ const style = { height, width };
+ const resourcesParam = properties.resources?.length === 0 ? {} : { resources: properties.resources };
+
+ const events = properties.events ? properties.events.map((event) => prepareEvent(event, properties.dateFormat)) : [];
+ const defaultDate = parseDate(properties.defaultDate, properties.dateFormat);
+
+ const eventPropGetter = (event) => {
+ const backgroundColor = event.color;
+ const textStyle =
+ event.textOrientation === 'vertical' ? { writingMode: 'vertical-rl', textOrientation: 'mixed' } : {};
+ const style = { backgroundColor, ...textStyle, padding: 3, paddingLeft: 5, paddingRight: 5 };
+
+ return { style };
+ };
+
+ const slotSelectHandler = (calendarSlots) => {
+ const { slots, start, end, resourceId, action } = calendarSlots;
+ const formattedSlots = slots.map((slot) => moment(slot).format(properties.dateFormat));
+ const formattedStart = moment(start).format(properties.dateFormat);
+ const formattedEnd = moment(end).format(properties.dateFormat);
+
+ const selectedSlots = {
+ slots: formattedSlots,
+ start: formattedStart,
+ end: formattedEnd,
+ resourceId,
+ action,
+ };
+
+ fireEvent('onCalendarSlotSelect', { selectedSlots });
+ };
+
+ const defaultView = allowedCalendarViews.includes(properties.defaultView)
+ ? properties.defaultView
+ : allowedCalendarViews[0];
+
+ return (
+