From d74d1583edcfb4e9e35c125b875b7546e00d91f0 Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Tue, 1 Sep 2020 11:55:00 -0300 Subject: [PATCH 1/3] Finished --- 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 | 102 +++++++ .../analytics/AnalyticsPage.stories.js | 10 + .../omnichannel/analytics/DateRangePicker.js | 122 ++++++++ .../analytics/DateRangePicker.stories.js | 10 + .../analytics/InterchangeableChart.js | 51 ++++ client/omnichannel/analytics/Overview.js | 59 ++++ client/omnichannel/routes.js | 5 + client/omnichannel/sidebarItems.js | 2 +- 17 files changed, 404 insertions(+), 560 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 @@ - 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..93b4f65cdc60 --- /dev/null +++ b/client/omnichannel/analytics/AnalyticsPage.js @@ -0,0 +1,102 @@ +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 border = { + borderStyle: 'solid', + borderWidth: 'x2', + borderRadius: 'x2', + borderColor: 'neutral-300', +}; + +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..4ce1b975f829 --- /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 }) => { + 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..8436597e8a73 --- /dev/null +++ b/client/omnichannel/analytics/Overview.js @@ -0,0 +1,59 @@ +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/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'), }, { From a74bc90cab50808da10aee897c3fa92179f0e687 Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Wed, 2 Sep 2020 11:15:43 -0300 Subject: [PATCH 2/3] Change Page layout --- client/omnichannel/analytics/AnalyticsPage.js | 11 +---------- client/omnichannel/analytics/InterchangeableChart.js | 2 +- client/omnichannel/analytics/Overview.js | 4 ---- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/client/omnichannel/analytics/AnalyticsPage.js b/client/omnichannel/analytics/AnalyticsPage.js index 93b4f65cdc60..b9de63f95ed2 100644 --- a/client/omnichannel/analytics/AnalyticsPage.js +++ b/client/omnichannel/analytics/AnalyticsPage.js @@ -28,13 +28,6 @@ const useOptions = (type) => { }, [t, type]); }; -const border = { - borderStyle: 'solid', - borderWidth: 'x2', - borderRadius: 'x2', - borderColor: 'neutral-300', -}; - const AnalyticsPage = () => { const t = useTranslation(); const [type, setType] = useState('Conversations'); @@ -67,6 +60,7 @@ const AnalyticsPage = () => { + { flexBasis='100%' p='x10' mis='x4' - {...border} > diff --git a/client/omnichannel/analytics/InterchangeableChart.js b/client/omnichannel/analytics/InterchangeableChart.js index 4ce1b975f829..60217007f65b 100644 --- a/client/omnichannel/analytics/InterchangeableChart.js +++ b/client/omnichannel/analytics/InterchangeableChart.js @@ -45,7 +45,7 @@ const InterchangeableChart = ({ departmentId, dateRange, chartName }) => { }); }, [chartName, departmentId, draw, end, start, t]); - return ; + return ; }; export default InterchangeableChart; diff --git a/client/omnichannel/analytics/Overview.js b/client/omnichannel/analytics/Overview.js index 8436597e8a73..bbed3f8ba74f 100644 --- a/client/omnichannel/analytics/Overview.js +++ b/client/omnichannel/analytics/Overview.js @@ -42,10 +42,6 @@ const Overview = ({ type, dateRange, departmentId }) => { return { From de30d3225b47c76d4cbc12e9d510b32c70fe3b2b Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Wed, 2 Sep 2020 16:24:35 -0300 Subject: [PATCH 3/3] Graph stretch, filter layout --- client/omnichannel/analytics/AnalyticsPage.js | 25 ++++++------------- .../analytics/InterchangeableChart.js | 4 +-- .../RealTimeMonitoringPage.js | 4 +-- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/client/omnichannel/analytics/AnalyticsPage.js b/client/omnichannel/analytics/AnalyticsPage.js index b9de63f95ed2..6ff55070356d 100644 --- a/client/omnichannel/analytics/AnalyticsPage.js +++ b/client/omnichannel/analytics/AnalyticsPage.js @@ -50,35 +50,24 @@ const AnalyticsPage = () => { - - + + - + + - - - diff --git a/client/omnichannel/analytics/InterchangeableChart.js b/client/omnichannel/analytics/InterchangeableChart.js index 60217007f65b..bfa9c90e8abc 100644 --- a/client/omnichannel/analytics/InterchangeableChart.js +++ b/client/omnichannel/analytics/InterchangeableChart.js @@ -8,7 +8,7 @@ import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; import { drawLineChart } from '../../../app/livechat/client/lib/chartHandler'; -const InterchangeableChart = ({ departmentId, dateRange, chartName }) => { +const InterchangeableChart = ({ departmentId, dateRange, chartName, ...props }) => { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); @@ -45,7 +45,7 @@ const InterchangeableChart = ({ departmentId, dateRange, chartName }) => { }); }, [chartName, departmentId, draw, end, start, t]); - return ; + return ; }; export default InterchangeableChart; 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 = () => { - + - +