diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 352c3b4fb0..1dc6744dd9 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -46,6 +46,7 @@ "react": "^17.0.2", "react-ace": "^9.4.1", "react-beautiful-dnd": "^13.1.0", + "react-big-calendar": "^0.35.0", "react-breadcrumbs-dynamic": "^1.2.1", "react-click-outside": "github:tj/react-click-outside", "react-color": "^2.19.3", @@ -2736,6 +2737,26 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.3.tgz", + "integrity": "sha512-xDu17cEfh7Kid/d95kB6tZsLOmSWKCZKtprnhVepjsSaCij+lM3mItSJDuuHDMbCWTh8Ejmebwb+KONcCJ0eXQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@restart/hooks": { + "version": "0.3.27", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.27.tgz", + "integrity": "sha512-s984xV/EapUIfkjlf8wz9weP2O9TNKR96C68FfMEy2bE69+H4cNv3RD4Mf97lW7Htt7PjZrYTjSC8f3SB9VCXw==", + "dependencies": { + "dequal": "^2.0.2" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz", @@ -3333,6 +3354,11 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" }, + "node_modules/@types/warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" + }, "node_modules/@types/webpack": { "version": "4.41.30", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.30.tgz", @@ -7924,6 +7950,14 @@ "integrity": "sha1-UYZnt2kUYKXn4KNBvnbrfOgJAYQ=", "dev": true }, + "node_modules/dequal": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", + "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", + "engines": { + "node": ">=6" + } + }, "node_modules/des.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", @@ -20807,6 +20841,47 @@ "react-dom": "^16.8.5 || ^17.0.0" } }, + "node_modules/react-big-calendar": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-0.35.0.tgz", + "integrity": "sha512-2jjPhfKRM6ka3pdzdqqYUPLEgoeyyP5ICPhgUZBitozM3nskz7B3tNaLpqNgTWCaAc7KJbe2TJiqCcCbHiZtRA==", + "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/csstype": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", + "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==" + }, + "node_modules/react-big-calendar/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/react-big-calendar/node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/react-breadcrumbs-dynamic": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/react-breadcrumbs-dynamic/-/react-breadcrumbs-dynamic-1.2.1.tgz", @@ -21200,6 +21275,47 @@ "react": ">=16" } }, + "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-overlays/node_modules/csstype": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", + "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==" + }, + "node_modules/react-overlays/node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/react-overlays/node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/react-phone-input-2": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.14.0.tgz", @@ -29753,6 +29869,19 @@ } } }, + "@popperjs/core": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.3.tgz", + "integrity": "sha512-xDu17cEfh7Kid/d95kB6tZsLOmSWKCZKtprnhVepjsSaCij+lM3mItSJDuuHDMbCWTh8Ejmebwb+KONcCJ0eXQ==" + }, + "@restart/hooks": { + "version": "0.3.27", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.27.tgz", + "integrity": "sha512-s984xV/EapUIfkjlf8wz9weP2O9TNKR96C68FfMEy2bE69+H4cNv3RD4Mf97lW7Htt7PjZrYTjSC8f3SB9VCXw==", + "requires": { + "dequal": "^2.0.2" + } + }, "@rollup/plugin-babel": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz", @@ -30223,6 +30352,11 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" }, + "@types/warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" + }, "@types/webpack": { "version": "4.41.30", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.30.tgz", @@ -33871,6 +34005,11 @@ "integrity": "sha1-UYZnt2kUYKXn4KNBvnbrfOgJAYQ=", "dev": true }, + "dequal": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", + "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==" + }, "des.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", @@ -43796,6 +43935,45 @@ "use-memo-one": "^1.1.1" } }, + "react-big-calendar": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-0.35.0.tgz", + "integrity": "sha512-2jjPhfKRM6ka3pdzdqqYUPLEgoeyyP5ICPhgUZBitozM3nskz7B3tNaLpqNgTWCaAc7KJbe2TJiqCcCbHiZtRA==", + "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": { + "csstype": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", + "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==" + }, + "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==" + }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + } + } + }, "react-breadcrumbs-dynamic": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/react-breadcrumbs-dynamic/-/react-breadcrumbs-dynamic-1.2.1.tgz", @@ -44093,6 +44271,45 @@ "vfile": "^4.0.0" } }, + "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" + }, + "dependencies": { + "csstype": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", + "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==" + }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, "react-phone-input-2": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.14.0.tgz", diff --git a/dashboard/package.json b/dashboard/package.json index 25317b19a4..7967f54d73 100755 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -40,6 +40,7 @@ "react": "^17.0.2", "react-ace": "^9.4.1", "react-beautiful-dnd": "^13.1.0", + "react-big-calendar": "^0.35.0", "react-breadcrumbs-dynamic": "^1.2.1", "react-click-outside": "github:tj/react-click-outside", "react-color": "^2.19.3", diff --git a/dashboard/src/App.js b/dashboard/src/App.js index 96bf2aa753..ad9369d3d5 100755 --- a/dashboard/src/App.js +++ b/dashboard/src/App.js @@ -16,6 +16,7 @@ import { setUserId, setUserProperties, identify, logEvent } from './analytics'; import { SHOULD_LOG_ANALYTICS } from './config'; import Dashboard from './components/Dashboard'; import { LoadingState } from './components/basic/Loader'; +import 'react-big-calendar/lib/sass/styles.scss'; if (!isServer) { history.listen(location => { diff --git a/dashboard/src/components/schedule/ScheduleCalender.js b/dashboard/src/components/schedule/ScheduleCalender.js new file mode 100644 index 0000000000..a60c3b708a --- /dev/null +++ b/dashboard/src/components/schedule/ScheduleCalender.js @@ -0,0 +1,172 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { Calendar, momentLocalizer } from 'react-big-calendar'; +import moment from 'moment'; + +function ScheduleCalender({ escalations }) { + const [dayOffset, setDayOffset] = useState(47); + const [defaultDate, setDefaultDate] = useState(new Date()); + + const teamSchedules = []; + escalations.forEach(escalation => { + if (escalation.activeTeam) { + if (escalation.activeTeam.teamMembers.length) { + teamSchedules.push(...escalation.activeTeam.teamMembers); + } + } + if (escalation.nextActiveTeam) { + if (escalation.nextActiveTeam.teamMembers.length) { + teamSchedules.push(...escalation.nextActiveTeam.teamMembers); + } + } + }); + + const extendedSchedule = []; + teamSchedules.forEach(schedule => { + for (let i = 0; i <= dayOffset; i++) { + const currentStart = new Date(schedule.startTime); + const currentEnd = new Date(schedule.endTime); + const scheduleData = { + title: `${schedule.user?.name || + schedule.user + ?.email} is on-call schedule during this period`, + start: new Date( + currentStart.setDate(currentStart.getDate() + i) + ), + end: new Date(currentEnd.setDate(currentEnd.getDate() + i)), + id: schedule._id + i, + }; + + extendedSchedule.push(scheduleData); + } + }); + + // Setup the localizer by providing the moment (or globalize) Object + // to the correct localizer. + const localizer = momentLocalizer(moment); // or globalizeLocalizer + + const handleNavigate = (date, view, action) => { + setDefaultDate(date); + if (view === 'month') { + setDayOffset(prevState => { + if (action && action === 'PREV') { + return prevState - 47; + } + if (action && action === 'TODAY') { + return 47; + } + return prevState + 47; + }); + } + if (view === 'week') { + setDayOffset(prevState => { + if (action && action === 'PREV') { + return prevState - 7; + } + if (action && action === 'TODAY') { + return 7; + } + return prevState + 7; + }); + } + if (view === 'day') { + setDayOffset(prevState => { + if (action && action === 'PREV') { + return prevState - 1; + } + if (action && action === 'TODAY') { + return 1; + } + return prevState + 1; + }); + } + if (view === 'agenda') { + setDayOffset(prevState => { + if (action && action === 'PREV') { + return prevState - 30; + } + if (action && action === 'TODAY') { + return 30; + } + return prevState + 30; + }); + } + }; + + const handleView = view => { + setDefaultDate(new Date()); + if (view === 'month') setDayOffset(47); + if (view === 'week') setDayOffset(7); + if (view === 'day') setDayOffset(1); + if (view === 'agenda') setDayOffset(30); + }; + + return ( +
+
+
+
+
+
+ + Call Schedule Calender + +

+ Calender to show team member that is on-call +

+
+
+
+
+
+
+
+
+
+ new Date()} + localizer={localizer} + events={extendedSchedule} + startAccessor="start" + endAccessor="end" + style={{ height: 500 }} + views={['month', 'week', 'day', 'agenda']} + showMultiDayTimes={true} + onView={view => handleView(view)} + onNavigate={(date, view, action) => + handleNavigate(date, view, action) + } + /> +
+
+
+ +
+
+
+
+ +
+
+
+
+
+ ); +} + +ScheduleCalender.displayName = 'ScheduleCalender'; + +ScheduleCalender.propTypes = { + escalations: PropTypes.array, +}; + +export default ScheduleCalender; diff --git a/dashboard/src/pages/Schedule.js b/dashboard/src/pages/Schedule.js index cedb4f6c1d..41d92befe0 100755 --- a/dashboard/src/pages/Schedule.js +++ b/dashboard/src/pages/Schedule.js @@ -14,6 +14,7 @@ import { getEscalation } from '../actions/schedule'; import { teamLoading } from '../actions/team'; import BreadCrumbItem from '../components/breadCrumb/BreadCrumbItem'; import getParentRoute from '../utils/getParentRoute'; +import ScheduleCalender from '../components/schedule/ScheduleCalender'; class Schedule extends Component { constructor(props) { super(props); @@ -106,6 +107,9 @@ class Schedule extends Component {
+