From b3d132fe5c92ae38bf98874e6ab71616081116df Mon Sep 17 00:00:00 2001
From: gabriellsh <40830821+gabriellsh@users.noreply.github.com>
Date: Fri, 18 Sep 2020 17:11:55 -0300
Subject: [PATCH] Refactor: Omnichannel Analytics (#18766)
---
app/livechat/client/route.js | 8 -
app/livechat/client/views/admin.js | 3 -
.../app/analytics/livechatAnalytics.html | 122 --------
.../views/app/analytics/livechatAnalytics.js | 264 ------------------
.../livechatAnalyticsCustomDaterange.html | 15 -
.../livechatAnalyticsCustomDaterange.js | 42 ---
.../analytics/livechatAnalyticsDaterange.html | 48 ----
.../analytics/livechatAnalyticsDaterange.js | 57 ----
client/omnichannel/analytics/AgentOverview.js | 44 +++
client/omnichannel/analytics/AnalyticsPage.js | 82 ++++++
.../analytics/AnalyticsPage.stories.js | 10 +
.../omnichannel/analytics/DateRangePicker.js | 122 ++++++++
.../analytics/DateRangePicker.stories.js | 10 +
.../analytics/InterchangeableChart.js | 51 ++++
client/omnichannel/analytics/Overview.js | 55 ++++
.../RealTimeMonitoringPage.js | 4 +-
client/omnichannel/routes.js | 5 +
client/omnichannel/sidebarItems.js | 2 +-
18 files changed, 382 insertions(+), 562 deletions(-)
delete mode 100644 app/livechat/client/views/app/analytics/livechatAnalytics.html
delete mode 100644 app/livechat/client/views/app/analytics/livechatAnalytics.js
delete mode 100644 app/livechat/client/views/app/analytics/livechatAnalyticsCustomDaterange.html
delete mode 100644 app/livechat/client/views/app/analytics/livechatAnalyticsCustomDaterange.js
delete mode 100644 app/livechat/client/views/app/analytics/livechatAnalyticsDaterange.html
delete mode 100644 app/livechat/client/views/app/analytics/livechatAnalyticsDaterange.js
create mode 100644 client/omnichannel/analytics/AgentOverview.js
create mode 100644 client/omnichannel/analytics/AnalyticsPage.js
create mode 100644 client/omnichannel/analytics/AnalyticsPage.stories.js
create mode 100644 client/omnichannel/analytics/DateRangePicker.js
create mode 100644 client/omnichannel/analytics/DateRangePicker.stories.js
create mode 100644 client/omnichannel/analytics/InterchangeableChart.js
create mode 100644 client/omnichannel/analytics/Overview.js
diff --git a/app/livechat/client/route.js b/app/livechat/client/route.js
index 2e1b970d2b72..670aa2574ea0 100644
--- a/app/livechat/client/route.js
+++ b/app/livechat/client/route.js
@@ -18,14 +18,6 @@ AccountBox.addRoute({
pageTemplate: 'livechatDashboard',
}, livechatManagerRoutes, load);
-AccountBox.addRoute({
- name: 'livechat-analytics',
- path: '/analytics',
- sideNav: 'omnichannelFlex',
- i18nPageTitle: 'Analytics',
- pageTemplate: 'livechatAnalytics',
-}, livechatManagerRoutes, load);
-
AccountBox.addRoute({
name: 'livechat-departments',
path: '/departments',
diff --git a/app/livechat/client/views/admin.js b/app/livechat/client/views/admin.js
index 0d27714086d3..78a7e39c94d5 100644
--- a/app/livechat/client/views/admin.js
+++ b/app/livechat/client/views/admin.js
@@ -1,6 +1,3 @@
-import './app/analytics/livechatAnalytics';
-import './app/analytics/livechatAnalyticsCustomDaterange';
-import './app/analytics/livechatAnalyticsDaterange';
import './app/livechatDashboard.html';
import './app/livechatDepartmentForm';
import './app/livechatDepartments';
diff --git a/app/livechat/client/views/app/analytics/livechatAnalytics.html b/app/livechat/client/views/app/analytics/livechatAnalytics.html
deleted file mode 100644
index 05bacc18aabb..000000000000
--- a/app/livechat/client/views/app/analytics/livechatAnalytics.html
+++ /dev/null
@@ -1,122 +0,0 @@
-
- {{#requiresPermission 'view-livechat-analytics'}}
-
-
-
-
- {{#each analyticsOverviewData}}
-
- {{#each this}}
-
- {{value}}
- {{_ title}}
-
- {{/each}}
-
- {{/each}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{#with agentOverviewData}}
-
-
- {{#each head}}
- {{_ name}} |
- {{/each}}
-
-
-
- {{#each data}}
-
- {{name}} |
- {{value}} |
-
- {{/each}}
-
- {{/with}}
-
-
-
-
-
-
-
-
- {{/requiresPermission}}
-
diff --git a/app/livechat/client/views/app/analytics/livechatAnalytics.js b/app/livechat/client/views/app/analytics/livechatAnalytics.js
deleted file mode 100644
index ec74d3eb2411..000000000000
--- a/app/livechat/client/views/app/analytics/livechatAnalytics.js
+++ /dev/null
@@ -1,264 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { ReactiveVar } from 'meteor/reactive-var';
-import { Tracker } from 'meteor/tracker';
-import { Template } from 'meteor/templating';
-import moment from 'moment';
-
-import { handleError } from '../../../../../utils';
-import { popover } from '../../../../../ui-utils';
-import { drawLineChart } from '../../../lib/chartHandler';
-import { setDateRange, updateDateRange } from '../../../lib/dateHandler';
-import { APIClient } from '../../../../../utils/client';
-import './livechatAnalytics.html';
-
-let templateInstance; // current template instance/context
-let chartContext; // stores context of current chart, used to clean when redrawing
-
-const analyticsAllOptions = () => [{
- name: 'Conversations',
- value: 'conversations',
- chartOptions: [{
- name: 'Total_conversations',
- value: 'total-conversations',
- }, {
- name: 'Avg_chat_duration',
- value: 'avg-chat-duration',
- }, {
- name: 'Total_messages',
- value: 'total-messages',
- }],
-}, {
- name: 'Productivity',
- value: 'productivity',
- chartOptions: [{
- name: 'Avg_first_response_time',
- value: 'avg-first-response-time',
- }, {
- name: 'Best_first_response_time',
- value: 'best_first_response_time',
- }, {
- name: 'Avg_response_time',
- value: 'avg-response-time',
- }, {
- name: 'Avg_reaction_time',
- value: 'avg-reaction-time',
- }],
-}];
-
-/**
- *
- * @param {Array} arr
- * @param {Integer} chunkCount
- *
- * @returns {Array{Array}} Array containing arrays
- */
-const chunkArray = (arr, chunkCount) => { // split array into n almost equal arrays
- const chunks = [];
- while (arr.length) {
- const chunkSize = Math.ceil(arr.length / chunkCount--);
- const chunk = arr.slice(0, chunkSize);
- chunks.push(chunk);
- arr = arr.slice(chunkSize);
- }
- return chunks;
-};
-
-const getChartDepartment = (department) => department?._id;
-
-const updateAnalyticsChart = () => {
- const [department] = templateInstance.selectedDepartments.get();
- const departmentId = getChartDepartment(department);
-
- const options = {
- daterange: {
- from: moment(templateInstance.daterange.get().from, 'MMM D YYYY').toISOString(),
- to: moment(templateInstance.daterange.get().to, 'MMM D YYYY').toISOString(),
- },
- chartOptions: templateInstance.chartOptions.get(),
- ...departmentId && { departmentId },
- };
-
- Meteor.call('livechat:getAnalyticsChartData', options, async function(error, result) {
- if (error) {
- return handleError(error);
- }
-
- if (!(result && result.chartLabel && result.dataLabels && result.dataPoints)) {
- console.log('livechat:getAnalyticsChartData => Missing Data');
- }
-
- chartContext = await drawLineChart(document.getElementById('lc-analytics-chart'), chartContext, [result.chartLabel], result.dataLabels, [result.dataPoints]);
- });
-
- Meteor.call('livechat:getAgentOverviewData', options, function(error, result) {
- if (error) {
- return handleError(error);
- }
-
- if (!result) {
- console.log('livechat:getAgentOverviewData => Missing Data');
- }
-
- templateInstance.agentOverviewData.set(result);
- });
-};
-
-const updateAnalyticsOverview = () => {
- const [department] = templateInstance.selectedDepartments.get();
- const departmentId = getChartDepartment(department);
-
- const options = {
- daterange: {
- from: moment(templateInstance.daterange.get().from, 'MMM D YYYY').toISOString(),
- to: moment(templateInstance.daterange.get().to, 'MMM D YYYY').toISOString(),
- },
- analyticsOptions: templateInstance.analyticsOptions.get(),
- ...departmentId && { departmentId },
- };
-
- Meteor.call('livechat:getAnalyticsOverviewData', options, (error, result) => {
- if (error) {
- return handleError(error);
- }
-
- if (!result) {
- console.log('livechat:getAnalyticsOverviewData => Missing Data');
- }
-
- templateInstance.analyticsOverviewData.set(chunkArray(result, 3));
- });
-};
-
-Template.livechatAnalytics.helpers({
- analyticsOverviewData() {
- return templateInstance.analyticsOverviewData.get();
- },
- agentOverviewData() {
- return templateInstance.agentOverviewData.get();
- },
- analyticsAllOptions() {
- return analyticsAllOptions();
- },
- analyticsOptions() {
- return templateInstance.analyticsOptions.get();
- },
- daterange() {
- return templateInstance.daterange.get();
- },
- selected(value) {
- if (value === templateInstance.analyticsOptions.get().value || value === templateInstance.chartOptions.get().value) { return 'selected'; }
- return false;
- },
- showLeftNavButton() {
- if (templateInstance.daterange.get().value === 'custom') {
- return false;
- }
- return true;
- },
- showRightNavButton() {
- if (templateInstance.daterange.get().value === 'custom' || templateInstance.daterange.get().value === 'today' || templateInstance.daterange.get().value === 'this-week' || templateInstance.daterange.get().value === 'this-month') {
- return false;
- }
- return true;
- },
- departmentModifier() {
- return (filter, text = '') => {
- const f = filter.get();
- return `${ f.length === 0 ? text : text.replace(new RegExp(filter.get(), 'i'), (part) => `${ part }`) }`;
- };
- },
- onClickTagDepartment() {
- return Template.instance().onClickTagDepartment;
- },
- selectedDepartments() {
- return Template.instance().selectedDepartments.get();
- },
- onSelectDepartments() {
- return Template.instance().onSelectDepartments;
- },
- hasDepartments() {
- return Template.instance().hasDepartments.get();
- },
-});
-
-
-Template.livechatAnalytics.onCreated(async function() {
- templateInstance = Template.instance();
-
- this.analyticsOverviewData = new ReactiveVar();
- this.agentOverviewData = new ReactiveVar();
- this.daterange = new ReactiveVar({});
- this.analyticsOptions = new ReactiveVar(analyticsAllOptions()[0]); // default selected first
- this.chartOptions = new ReactiveVar(analyticsAllOptions()[0].chartOptions[0]); // default selected first
- this.selectedDepartments = new ReactiveVar([]);
- this.hasDepartments = new ReactiveVar(false);
-
- this.onSelectDepartments = ({ item: department }) => {
- department.text = department.name;
- this.selectedDepartments.set([department]);
- };
-
- this.onClickTagDepartment = () => {
- this.selectedDepartments.set([]);
- };
-
- const { departments } = await APIClient.v1.get('livechat/department?count=1');
- this.hasDepartments.set(departments?.length > 0);
-
- this.autorun(() => {
- templateInstance.daterange.set(setDateRange());
- });
-});
-
-Template.livechatAnalytics.onRendered(() => {
- Tracker.autorun(() => {
- if (templateInstance.daterange.get()
- && templateInstance.analyticsOptions.get()
- && templateInstance.chartOptions.get()) {
- updateAnalyticsOverview();
- updateAnalyticsChart();
- }
- });
-});
-
-Template.livechatAnalytics.events({
- 'click .lc-date-picker-btn'(e) {
- e.preventDefault();
- const options = [];
- const config = {
- template: 'livechatAnalyticsDaterange',
- currentTarget: e.currentTarget,
- data: {
- options,
- daterange: templateInstance.daterange,
- },
- offsetVertical: e.currentTarget.clientHeight + 10,
- };
- popover.open(config);
- },
- 'click .lc-daterange-prev'(e) {
- e.preventDefault();
-
- templateInstance.daterange.set(updateDateRange(templateInstance.daterange.get(), -1));
- },
- 'click .lc-daterange-next'(e) {
- e.preventDefault();
-
- templateInstance.daterange.set(updateDateRange(templateInstance.daterange.get(), 1));
- },
- 'change #lc-analytics-options'(e) {
- e.preventDefault();
-
- templateInstance.analyticsOptions.set(analyticsAllOptions().filter(function(obj) {
- return obj.value === e.currentTarget.value;
- })[0]);
- templateInstance.chartOptions.set(templateInstance.analyticsOptions.get().chartOptions[0]);
- },
- 'change #lc-analytics-chart-options'(e) {
- e.preventDefault();
-
- templateInstance.chartOptions.set(templateInstance.analyticsOptions.get().chartOptions.filter(function(obj) {
- return obj.value === e.currentTarget.value;
- })[0]);
- },
-});
diff --git a/app/livechat/client/views/app/analytics/livechatAnalyticsCustomDaterange.html b/app/livechat/client/views/app/analytics/livechatAnalyticsCustomDaterange.html
deleted file mode 100644
index fa166b5a5b72..000000000000
--- a/app/livechat/client/views/app/analytics/livechatAnalyticsCustomDaterange.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
diff --git a/app/livechat/client/views/app/analytics/livechatAnalyticsCustomDaterange.js b/app/livechat/client/views/app/analytics/livechatAnalyticsCustomDaterange.js
deleted file mode 100644
index d2c11e9cc449..000000000000
--- a/app/livechat/client/views/app/analytics/livechatAnalyticsCustomDaterange.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Template } from 'meteor/templating';
-import moment from 'moment';
-
-import { handleError } from '../../../../../utils';
-import { popover } from '../../../../../ui-utils';
-import { setDateRange } from '../../../lib/dateHandler';
-import './livechatAnalyticsCustomDaterange.html';
-
-
-Template.livechatAnalyticsCustomDaterange.helpers({
- from() {
- return moment(Template.currentData().daterange.get().from, 'MMM D YYYY').format('L');
- },
- to() {
- return moment(Template.currentData().daterange.get().to, 'MMM D YYYY').format('L');
- },
-});
-
-Template.livechatAnalyticsCustomDaterange.onRendered(function() {
- this.$('.lc-custom-daterange').datepicker({
- autoclose: true,
- todayHighlight: true,
- format: moment.localeData().longDateFormat('L').toLowerCase(),
- });
-});
-
-
-Template.livechatAnalyticsCustomDaterange.events({
- 'click .lc-custom-daterange-submit'(e) {
- e.preventDefault();
- const from = document.getElementsByClassName('lc-custom-daterange-from')[0].value;
- const to = document.getElementsByClassName('lc-custom-daterange-to')[0].value;
-
- if (moment(from).isValid() && moment(to).isValid()) {
- Template.currentData().daterange.set(setDateRange('custom', moment(new Date(from)), moment(new Date(to))));
- } else {
- handleError({ details: { errorTitle: 'Invalid_dates' }, error: 'Error_in_custom_dates' });
- }
-
- popover.close();
- },
-});
diff --git a/app/livechat/client/views/app/analytics/livechatAnalyticsDaterange.html b/app/livechat/client/views/app/analytics/livechatAnalyticsDaterange.html
deleted file mode 100644
index e6f87416ac91..000000000000
--- a/app/livechat/client/views/app/analytics/livechatAnalyticsDaterange.html
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
diff --git a/app/livechat/client/views/app/analytics/livechatAnalyticsDaterange.js b/app/livechat/client/views/app/analytics/livechatAnalyticsDaterange.js
deleted file mode 100644
index fa0f851a357e..000000000000
--- a/app/livechat/client/views/app/analytics/livechatAnalyticsDaterange.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Template } from 'meteor/templating';
-import moment from 'moment';
-
-import { popover } from '../../../../../ui-utils';
-import { setDateRange } from '../../../lib/dateHandler';
-import './livechatAnalyticsDaterange.html';
-
-Template.livechatAnalyticsDaterange.helpers({
- bold(prop) {
- return prop === Template.currentData().daterange.get().value ? 'rc-popover__item--bold' : '';
- },
-});
-
-Template.livechatAnalyticsDaterange.events({
- 'change input'(e) {
- e.preventDefault();
-
- const value = e.currentTarget.getAttribute('type') === 'checkbox' ? e.currentTarget.checked : e.currentTarget.value;
-
- popover.close();
-
- switch (value) {
- case 'custom':
- const target = document.getElementsByClassName('lc-date-picker-btn')[0];
- const options = [];
- const config = {
- template: 'livechatAnalyticsCustomDaterange',
- currentTarget: target,
- data: {
- options,
- daterange: Template.currentData().daterange,
- },
- offsetVertical: target.clientHeight + 10,
- };
- popover.open(config);
- break;
- case 'today':
- Template.currentData().daterange.set(setDateRange(value, moment().startOf('day'), moment().startOf('day')));
- break;
- case 'yesterday':
- Template.currentData().daterange.set(setDateRange(value, moment().subtract(1, 'days').startOf('day'), moment().subtract(1, 'days').startOf('day')));
- break;
- case 'this-week':
- Template.currentData().daterange.set(setDateRange(value, moment().startOf('week'), moment().endOf('week')));
- break;
- case 'prev-week':
- Template.currentData().daterange.set(setDateRange(value, moment().subtract(1, 'weeks').startOf('week'), moment().subtract(1, 'weeks').endOf('week')));
- break;
- case 'this-month':
- Template.currentData().daterange.set(setDateRange(value, moment().startOf('month'), moment().endOf('month')));
- break;
- case 'prev-month':
- Template.currentData().daterange.set(setDateRange(value, moment().subtract(1, 'months').startOf('month'), moment().subtract(1, 'months').endOf('month')));
- break;
- }
- },
-});
diff --git a/client/omnichannel/analytics/AgentOverview.js b/client/omnichannel/analytics/AgentOverview.js
new file mode 100644
index 000000000000..f48a6c07f7e9
--- /dev/null
+++ b/client/omnichannel/analytics/AgentOverview.js
@@ -0,0 +1,44 @@
+import React, { useEffect, useState, useMemo } from 'react';
+import { Table } from '@rocket.chat/fuselage';
+
+import { useMethodData, AsyncState } from '../../contexts/ServerContext';
+import { useTranslation } from '../../contexts/TranslationContext';
+
+const style = { width: '100%' };
+
+const AgentOverview = ({ type, dateRange, departmentId }) => {
+ const t = useTranslation();
+ const { start, end } = dateRange;
+
+ const params = useMemo(() => [{
+ chartOptions: { name: type },
+ daterange: { from: start, to: end },
+ ...departmentId && { departmentId },
+ }], [departmentId, end, start, type]);
+
+ const [data, state] = useMethodData('livechat:getAgentOverviewData', params);
+
+ const [displayData, setDisplayData] = useState();
+
+ useEffect(() => {
+ if (state === AsyncState.DONE) {
+ setDisplayData(data);
+ }
+ }, [data, state]);
+
+ return
+
+
+ {displayData?.head?.map(({ name }, i) => { t(name) })}
+
+
+
+ {displayData?.data?.map(({ name, value }, i) =>
+ {name}
+ {value}
+ )}
+
+
;
+};
+
+export default AgentOverview;
diff --git a/client/omnichannel/analytics/AnalyticsPage.js b/client/omnichannel/analytics/AnalyticsPage.js
new file mode 100644
index 000000000000..6ff55070356d
--- /dev/null
+++ b/client/omnichannel/analytics/AnalyticsPage.js
@@ -0,0 +1,82 @@
+import React, { useMemo, useState, useEffect } from 'react';
+import { Box, Select, Margins } from '@rocket.chat/fuselage';
+
+import DepartmentAutoComplete from '../DepartmentAutoComplete';
+import DateRangePicker from './DateRangePicker';
+import Overview from './Overview';
+import AgentOverview from './AgentOverview';
+import Page from '../../components/basic/Page';
+import InterchangeableChart from './InterchangeableChart';
+import { useTranslation } from '../../contexts/TranslationContext';
+
+const useOptions = (type) => {
+ const t = useTranslation();
+ return useMemo(() => {
+ if (type === 'Conversations') {
+ return [
+ ['Total_conversations', t('Total_conversations')],
+ ['Avg_chat_duration', t('Avg_chat_duration')],
+ ['Total_messages', t('Total_messages')],
+ ];
+ }
+ return [
+ ['Avg_first_response_time', t('Avg_first_response_time')],
+ ['Best_first_response_time', t('Best_first_response_time')],
+ ['Avg_response_time', t('Avg_response_time')],
+ ['Avg_reaction_time', t('Avg_reaction_time')],
+ ];
+ }, [t, type]);
+};
+
+const AnalyticsPage = () => {
+ const t = useTranslation();
+ const [type, setType] = useState('Conversations');
+ const [departmentId, setDepartmentId] = useState(null);
+ const [dateRange, setDateRange] = useState({ start: null, end: null });
+ const [chartName, setChartName] = useState();
+
+ const typeOptions = useMemo(() => [
+ ['Conversations', t('Conversations')],
+ ['Productivity', t('Productivity')],
+ ], [t]);
+
+ const graphOptions = useOptions(type);
+
+ useEffect(() => {
+ setChartName(graphOptions[0][0]);
+ }, [graphOptions]);
+
+ return
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ;
+};
+
+export default AnalyticsPage;
diff --git a/client/omnichannel/analytics/AnalyticsPage.stories.js b/client/omnichannel/analytics/AnalyticsPage.stories.js
new file mode 100644
index 000000000000..bb389cf9df45
--- /dev/null
+++ b/client/omnichannel/analytics/AnalyticsPage.stories.js
@@ -0,0 +1,10 @@
+import React from 'react';
+
+import AnalyticsPage from './AnalyticsPage';
+
+export default {
+ title: 'omnichannel/AnalyticsPage',
+ component: AnalyticsPage,
+};
+
+export const Default = () => ;
diff --git a/client/omnichannel/analytics/DateRangePicker.js b/client/omnichannel/analytics/DateRangePicker.js
new file mode 100644
index 000000000000..ce9e34aee236
--- /dev/null
+++ b/client/omnichannel/analytics/DateRangePicker.js
@@ -0,0 +1,122 @@
+import React, { useState, useMemo, useEffect } from 'react';
+import { Box, InputBox, Menu, Margins } from '@rocket.chat/fuselage';
+import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
+
+import { useTranslation } from '../../contexts/TranslationContext';
+
+const date = new Date();
+
+const formatToDateInput = (date) => date.toISOString().slice(0, 10);
+
+const todayDate = formatToDateInput(date);
+
+const getMonthRange = (monthsToSubtractFromToday) => {
+ const date = new Date();
+ return {
+ start: formatToDateInput(new Date(
+ date.getFullYear(),
+ date.getMonth() - monthsToSubtractFromToday,
+ 1)),
+ end: formatToDateInput(new Date(
+ date.getFullYear(),
+ date.getMonth() - monthsToSubtractFromToday + 1,
+ 0)),
+ };
+};
+
+const getWeekRange = (daysToSubtractFromStart, daysToSubtractFromEnd) => {
+ const date = new Date();
+ return {
+ start: formatToDateInput(new Date(
+ date.getFullYear(),
+ date.getMonth(),
+ date.getDate() - daysToSubtractFromStart)),
+ end: formatToDateInput(new Date(
+ date.getFullYear(),
+ date.getMonth(),
+ date.getDate() - daysToSubtractFromEnd)),
+ };
+};
+
+const DateRangePicker = ({ onChange = () => {}, ...props }) => {
+ const t = useTranslation();
+ const [range, setRange] = useState({ start: '', end: '' });
+
+ const {
+ start,
+ end,
+ } = range;
+
+ const handleStart = useMutableCallback(({ currentTarget }) => {
+ const rangeObj = {
+ start: currentTarget.value,
+ end: range.end,
+ };
+ setRange(rangeObj);
+ onChange(rangeObj);
+ });
+
+ const handleEnd = useMutableCallback(({ currentTarget }) => {
+ const rangeObj = {
+ end: currentTarget.value,
+ start: range.start,
+ };
+ setRange(rangeObj);
+ onChange(rangeObj);
+ });
+
+ const handleRange = useMutableCallback((range) => {
+ setRange(range);
+ onChange(range);
+ });
+
+ useEffect(() => {
+ handleRange({
+ start: todayDate,
+ end: todayDate,
+ });
+ }, [handleRange]);
+
+ const options = useMemo(() => ({
+ today: {
+ icon: 'history',
+ label: t('Today'),
+ action: () => { handleRange(getWeekRange(0, 0)); },
+ },
+ yesterday: {
+ icon: 'history',
+ label: t('Yesterday'),
+ action: () => { handleRange(getWeekRange(1, 1)); },
+ },
+ thisWeek: {
+ icon: 'history',
+ label: t('This_week'),
+ action: () => { handleRange(getWeekRange(7, 0)); },
+ },
+ previousWeek: {
+ icon: 'history',
+ label: t('Previous_week'),
+ action: () => { handleRange(getWeekRange(14, 7)); },
+ },
+ thisMonth: {
+ icon: 'history',
+ label: t('This_month'),
+ action: () => { handleRange(getMonthRange(0)); },
+ },
+ lastMonth: {
+ icon: 'history',
+ label: t('Previous_month'),
+ action: () => { handleRange(getMonthRange(1)); },
+ },
+ }), [handleRange, t]);
+
+ return
+
+
+
+
+ ;
+};
+
+export default DateRangePicker;
diff --git a/client/omnichannel/analytics/DateRangePicker.stories.js b/client/omnichannel/analytics/DateRangePicker.stories.js
new file mode 100644
index 000000000000..9266229a7ca8
--- /dev/null
+++ b/client/omnichannel/analytics/DateRangePicker.stories.js
@@ -0,0 +1,10 @@
+import React from 'react';
+
+import DateRangePicker from './DateRangePicker';
+
+export default {
+ title: 'DateRange',
+ component: DateRangePicker,
+};
+
+export const Default = () => ;
diff --git a/client/omnichannel/analytics/InterchangeableChart.js b/client/omnichannel/analytics/InterchangeableChart.js
new file mode 100644
index 000000000000..bfa9c90e8abc
--- /dev/null
+++ b/client/omnichannel/analytics/InterchangeableChart.js
@@ -0,0 +1,51 @@
+import React, { useRef, useEffect } from 'react';
+import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
+
+import Chart from '../realTimeMonitoring/charts/Chart';
+import { useTranslation } from '../../contexts/TranslationContext';
+import { useMethod } from '../../contexts/ServerContext';
+import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext';
+import { drawLineChart } from '../../../app/livechat/client/lib/chartHandler';
+
+
+const InterchangeableChart = ({ departmentId, dateRange, chartName, ...props }) => {
+ const t = useTranslation();
+ const dispatchToastMessage = useToastMessageDispatch();
+
+ const canvas = useRef();
+ const context = useRef();
+
+ const {
+ start,
+ end,
+ } = dateRange;
+
+ const loadData = useMethod('livechat:getAnalyticsChartData');
+
+ const draw = useMutableCallback(async (params) => {
+ try {
+ const result = await loadData(params);
+ if (!(result && result.chartLabel && result.dataLabels && result.dataPoints)) {
+ return console.log('livechat:getAnalyticsChartData => Missing Data');
+ }
+ context.current = await drawLineChart(canvas.current, context.current, [result.chartLabel], result.dataLabels, [result.dataPoints]);
+ } catch (error) {
+ dispatchToastMessage({ type: 'error', message: error });
+ }
+ });
+
+ useEffect(() => {
+ draw({
+ daterange: {
+ from: start,
+ to: end,
+ },
+ chartOptions: { name: chartName },
+ ...departmentId && { departmentId },
+ });
+ }, [chartName, departmentId, draw, end, start, t]);
+
+ return ;
+};
+
+export default InterchangeableChart;
diff --git a/client/omnichannel/analytics/Overview.js b/client/omnichannel/analytics/Overview.js
new file mode 100644
index 000000000000..bbed3f8ba74f
--- /dev/null
+++ b/client/omnichannel/analytics/Overview.js
@@ -0,0 +1,55 @@
+import React, { useEffect, useState, useMemo } from 'react';
+import { Box, Skeleton } from '@rocket.chat/fuselage';
+
+import CounterItem from '../realTimeMonitoring/counter/CounterItem';
+import CounterRow from '../realTimeMonitoring/counter/CounterRow';
+import { useMethodData, AsyncState } from '../../contexts/ServerContext';
+import { useTranslation } from '../../contexts/TranslationContext';
+
+const initialData = Array.from({ length: 3 }).map(() => ({ title: '', value: '' }));
+
+const conversationsInitialData = [initialData, initialData];
+const productivityInitialData = [initialData];
+
+const Overview = ({ type, dateRange, departmentId }) => {
+ const t = useTranslation();
+
+ const { start, end } = dateRange;
+
+ const params = useMemo(() => [{
+ analyticsOptions: { name: type },
+ daterange: { from: start, to: end },
+ ...departmentId && { departmentId },
+ }], [departmentId, end, start, type]);
+
+ const [data, state] = useMethodData('livechat:getAnalyticsOverviewData', params);
+
+ const [displayData, setDisplayData] = useState(conversationsInitialData);
+
+ useEffect(() => {
+ setDisplayData(type === 'Conversations' ? conversationsInitialData : productivityInitialData);
+ }, [type]);
+
+ useEffect(() => {
+ if (state === AsyncState.DONE) {
+ if (data?.length > 3) {
+ setDisplayData([data.slice(0, 3), data.slice(3)]);
+ } else if (data) {
+ setDisplayData([data]);
+ }
+ }
+ }, [data, state]);
+
+ return
+ {
+ displayData.map((items = [], i) =>
+ {items.map(({ title, value }, i) => } count={value}/>)}
+ )
+ }
+ ;
+};
+
+export default Overview;
diff --git a/client/omnichannel/realTimeMonitoring/RealTimeMonitoringPage.js b/client/omnichannel/realTimeMonitoring/RealTimeMonitoringPage.js
index 7e991f74f076..8de043577b44 100644
--- a/client/omnichannel/realTimeMonitoring/RealTimeMonitoringPage.js
+++ b/client/omnichannel/realTimeMonitoring/RealTimeMonitoringPage.js
@@ -93,13 +93,13 @@ const RealTimeMonitoringPage = () => {
-
+
-
+
diff --git a/client/omnichannel/routes.js b/client/omnichannel/routes.js
index fe9cd83a93c5..3f6769ae5793 100644
--- a/client/omnichannel/routes.js
+++ b/client/omnichannel/routes.js
@@ -78,3 +78,8 @@ registerOmnichannelRoute('/realtime-monitoring', {
name: 'omnichannel-realTime',
lazyRouteComponent: () => import('./realTimeMonitoring/RealTimeMonitoringPage'),
});
+
+registerOmnichannelRoute('/analytics', {
+ name: 'omnichannel-analytics',
+ lazyRouteComponent: () => import('./analytics/AnalyticsPage'),
+});
diff --git a/client/omnichannel/sidebarItems.js b/client/omnichannel/sidebarItems.js
index 82586bc50daf..238d384e6c40 100644
--- a/client/omnichannel/sidebarItems.js
+++ b/client/omnichannel/sidebarItems.js
@@ -11,7 +11,7 @@ export const {
i18nLabel: 'Current_Chats',
permissionGranted: () => hasPermission('view-livechat-current-chats'),
}, {
- href: 'omnichannel/analytics',
+ href: 'omnichannel-analytics',
i18nLabel: 'Analytics',
permissionGranted: () => hasPermission('view-livechat-analytics'),
}, {