From e30af6171e9906cb57997400d61010a87134dc27 Mon Sep 17 00:00:00 2001
From: gabriellsh <40830821+gabriellsh@users.noreply.github.com>
Date: Wed, 2 Sep 2020 14:53:06 -0300
Subject: [PATCH] Refactor: Omnichannel Realtime Monitoring (#18666)

Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
---
 app/livechat/client/route.js                  |   8 -
 app/livechat/client/views/admin.js            |   1 -
 .../analytics/livechatRealTimeMonitoring.html | 164 ---------
 .../analytics/livechatRealTimeMonitoring.js   | 343 ------------------
 client/helpers/getDateRange.js                |   9 +
 client/omnichannel/DepartmentAutoComplete.js  |  24 ++
 .../RealTimeMonitoringPage.js                 | 109 ++++++
 .../charts/AgentStatusChart.js                |  72 ++++
 .../realTimeMonitoring/charts/Chart.js        |  17 +
 .../charts/ChatDurationChart.js               |  71 ++++
 .../realTimeMonitoring/charts/ChatsChart.js   |  73 ++++
 .../charts/ChatsPerAgentChart.js              |  64 ++++
 .../charts/ChatsPerDepartmentChart.js         |  64 ++++
 .../charts/ResponseTimesChart.js              |  79 ++++
 .../charts/getMomentChartLabelsAndData.js     |  14 +
 .../charts/getMomentCurrentLabel.js           |   8 +
 .../charts/useUpdateChartData.js              |  10 +
 .../counter/CounterContainer.js               |  29 ++
 .../realTimeMonitoring/counter/CounterItem.js |  20 +
 .../realTimeMonitoring/counter/CounterRow.js  |  23 ++
 .../counter/CounterRow.stories.js             |  16 +
 .../overviews/AgentsOverview.js               |  28 ++
 .../overviews/ChatsOverview.js                |  23 ++
 .../overviews/ConversationOverview.js         |  29 ++
 .../overviews/ProductivityOverview.js         |  27 ++
 client/omnichannel/routes.js                  |   5 +
 client/omnichannel/sidebarItems.js            |   2 +-
 packages/rocketchat-i18n/i18n/en.i18n.json    |   1 +
 28 files changed, 816 insertions(+), 517 deletions(-)
 delete mode 100644 app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.html
 delete mode 100644 app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.js
 create mode 100644 client/helpers/getDateRange.js
 create mode 100644 client/omnichannel/DepartmentAutoComplete.js
 create mode 100644 client/omnichannel/realTimeMonitoring/RealTimeMonitoringPage.js
 create mode 100644 client/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js
 create mode 100644 client/omnichannel/realTimeMonitoring/charts/Chart.js
 create mode 100644 client/omnichannel/realTimeMonitoring/charts/ChatDurationChart.js
 create mode 100644 client/omnichannel/realTimeMonitoring/charts/ChatsChart.js
 create mode 100644 client/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.js
 create mode 100644 client/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.js
 create mode 100644 client/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.js
 create mode 100644 client/omnichannel/realTimeMonitoring/charts/getMomentChartLabelsAndData.js
 create mode 100644 client/omnichannel/realTimeMonitoring/charts/getMomentCurrentLabel.js
 create mode 100644 client/omnichannel/realTimeMonitoring/charts/useUpdateChartData.js
 create mode 100644 client/omnichannel/realTimeMonitoring/counter/CounterContainer.js
 create mode 100644 client/omnichannel/realTimeMonitoring/counter/CounterItem.js
 create mode 100644 client/omnichannel/realTimeMonitoring/counter/CounterRow.js
 create mode 100644 client/omnichannel/realTimeMonitoring/counter/CounterRow.stories.js
 create mode 100644 client/omnichannel/realTimeMonitoring/overviews/AgentsOverview.js
 create mode 100644 client/omnichannel/realTimeMonitoring/overviews/ChatsOverview.js
 create mode 100644 client/omnichannel/realTimeMonitoring/overviews/ConversationOverview.js
 create mode 100644 client/omnichannel/realTimeMonitoring/overviews/ProductivityOverview.js

diff --git a/app/livechat/client/route.js b/app/livechat/client/route.js
index 98be3912c97..2e1b970d2b7 100644
--- a/app/livechat/client/route.js
+++ b/app/livechat/client/route.js
@@ -26,14 +26,6 @@ AccountBox.addRoute({
 	pageTemplate: 'livechatAnalytics',
 }, livechatManagerRoutes, load);
 
-AccountBox.addRoute({
-	name: 'livechat-real-time-monitoring',
-	path: '/real-time-monitoring',
-	sideNav: 'omnichannelFlex',
-	i18nPageTitle: 'Real_Time_Monitoring',
-	pageTemplate: 'livechatRealTimeMonitoring',
-}, 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 495de3e9d6d..0d27714086d 100644
--- a/app/livechat/client/views/admin.js
+++ b/app/livechat/client/views/admin.js
@@ -1,7 +1,6 @@
 import './app/analytics/livechatAnalytics';
 import './app/analytics/livechatAnalyticsCustomDaterange';
 import './app/analytics/livechatAnalyticsDaterange';
-import './app/analytics/livechatRealTimeMonitoring';
 import './app/livechatDashboard.html';
 import './app/livechatDepartmentForm';
 import './app/livechatDepartments';
diff --git a/app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.html b/app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.html
deleted file mode 100644
index 04818769e35..00000000000
--- a/app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.html
+++ /dev/null
@@ -1,164 +0,0 @@
-<template name="livechatRealTimeMonitoring">
-	{{#requiresPermission 'view-livechat-real-time-monitoring'}}
-	<form class="form-inline">
-		<div class="form-group rc-select lc-analytics-header">
-			<select id="lc-analytics-options" class="rc-select__element js-interval">
-				<option class="rc-select__option" value="5">5 {{_ "seconds"}}</option>
-				<option class="rc-select__option" value="10">10 {{_ "seconds"}}</option>
-				<option class="rc-select__option" value="30">30 {{_ "seconds"}}</option>
-				<option class="rc-select__option" value="60">1 {{_ "minute"}}</option>
-			</select>
-			<i class="icon-angle-down"></i>
-		</div>
-		{{#if hasDepartments }}
-			<div class="form-group ">
-				{{> livechatAutocompleteUser
-					onClickTag=onClickTagDepartment
-					list=selectedDepartments
-					onSelect=onSelectDepartments
-					collection='CachedDepartmentList'
-					endpoint='livechat/department.autocomplete'
-					field='name'
-					sort='name'
-					placeholder="Select_a_department"
-					name="department"
-					icon="queue"
-					noMatchTemplate="userSearchEmpty"
-					templateItem="popupList_item_channel"
-					template="roomSearch"
-					noMatchTemplate="roomSearchEmpty"
-					modifier=departmentModifier
-				}}
-			</div>
-		{{/if}}
-	</form>
-	{{#if isLoading}}
-		{{> loading }}
-	{{else}}
-	<div class="lc-monitoring-flex">
-		<div class="section lc-monitoring-line-chart-full">
-			<div class="section-content border-component-color">
-				<div class="lc-analytics-overview">
-					{{#each conversationsOverview}}
-					<div class="lc-analytics-ov-col">
-						<div class="lc-analytics-ov-case">
-							<span class="title">{{_ title}}</span>
-							<span class="value">{{value}}</span>
-						</div>
-					</div>
-					{{/each}}
-				</div>
-			</div>
-		</div>
-	</div>
-	{{/if}}
-	<div class="lc-monitoring-flex">
-		<div class="section lc-monitoring-doughnut-chart">
-			<div class="section-content border-component-color">
-				<div class="lc-monitoring-chart-container">
-					<canvas id="lc-chats-chart"></canvas>
-				</div>
-			</div>
-		</div>
-		<div class="section lc-monitoring-line-chart-full">
-			<div class="section-content border-component-color">
-				<div class="lc-monitoring-chart-container">
-					<canvas id="lc-chats-per-agent-chart"></canvas>
-				</div>
-			</div>
-		</div>
-	</div>
-	<div class="lc-monitoring-flex">
-		<div class="section lc-monitoring-line-chart-full">
-			<div class="section-content border-component-color">
-				{{#if isLoading}}
-					{{> loading }}
-				{{else}}
-				<div class="lc-analytics-overview">
-					{{#each chatsOverview}}
-					<div class="lc-analytics-ov-col">
-						<div class="lc-analytics-ov-case">
-							<span class="title">{{_ title}}</span>
-							<span class="value">{{value}}</span>
-						</div>
-					</div>
-					{{/each}}
-				</div>
-				{{/if}}
-			</div>
-		</div>
-	</div>
-	<div class="lc-monitoring-flex">
-		<div class="section lc-monitoring-doughnut-chart">
-			<div class="section-content border-component-color">
-				<div class="lc-monitoring-chart-container">
-					<canvas id="lc-agents-chart"></canvas>
-				</div>
-			</div>
-		</div>
-		<div class="section lc-monitoring-line-chart-full">
-			<div class="section-content border-component-color">
-				<div class="lc-monitoring-chart-container">
-					<canvas id="lc-chats-per-dept-chart"></canvas>
-				</div>
-			</div>
-		</div>
-	</div>
-	<div class="lc-monitoring-flex">
-		<div class="section lc-monitoring-line-chart-full">
-			<div class="section-content border-component-color">
-				{{#if isLoading}}
-					{{> loading }}
-				{{else}}
-				<div class="lc-analytics-overview">
-					{{#each agentsOverview}}
-					<div class="lc-analytics-ov-col">
-						<div class="lc-analytics-ov-case">
-							<span class="title">{{_ title}}</span>
-							<span class="value">{{value}}</span>
-						</div>
-					</div>
-					{{/each}}
-				</div>
-				{{/if}}
-			</div>
-		</div>
-	</div>
-	<div class="lc-monitoring-flex">
-		<div class="section lc-monitoring-line-chart-full">
-			<div class="section-content border-component-color">
-				<div class="lc-monitoring-chart-container">
-					<canvas id="lc-chat-duration-chart"></canvas>
-				</div>
-			</div>
-		</div>
-	</div>
-	<div class="lc-monitoring-flex">
-		<div class="section lc-monitoring-line-chart-full">
-			<div class="section-content border-component-color">
-				{{#if isLoading}}
-					{{> loading }}
-				{{else}}
-				<div class="lc-analytics-overview">
-					{{#each timingOverview}}
-					<div class="lc-analytics-ov-col">
-						<div class="lc-analytics-ov-case">
-							<span class="title">{{_ title}}</span>
-							<span class="value">{{value}}</span>
-						</div>
-					</div>
-					{{/each}}
-				</div>
-				{{/if}}
-			</div>
-		</div>
-	</div>
-	<div class="lc-monitoring-flex">
-		<div class="section lc-monitoring-line-chart-full">
-			<div class="lc-monitoring-chart-container">
-				<canvas id="lc-reaction-response-times-chart"></canvas>
-			</div>
-		</div>
-	</div>
-	{{/requiresPermission}}
-</template>
diff --git a/app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.js b/app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.js
deleted file mode 100644
index 584e0f003b1..00000000000
--- a/app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.js
+++ /dev/null
@@ -1,343 +0,0 @@
-import { Template } from 'meteor/templating';
-import moment from 'moment';
-import { ReactiveVar } from 'meteor/reactive-var';
-
-import { drawLineChart, drawDoughnutChart, updateChart } from '../../../lib/chartHandler';
-import { APIClient } from '../../../../../utils/client';
-import './livechatRealTimeMonitoring.html';
-
-const chartContexts = {}; // stores context of current chart, used to clean when redrawing
-let templateInstance;
-
-const initChart = {
-	'lc-chats-chart'() {
-		return drawDoughnutChart(
-			document.getElementById('lc-chats-chart'),
-			'Chats',
-			chartContexts['lc-chats-chart'],
-			['Open', 'Queue', 'Closed'], [0, 0, 0]);
-	},
-
-	'lc-agents-chart'() {
-		return drawDoughnutChart(
-			document.getElementById('lc-agents-chart'),
-			'Agents',
-			chartContexts['lc-agents-chart'],
-			['Available', 'Away', 'Busy', 'Offline'], [0, 0, 0, 0]);
-	},
-
-	'lc-chats-per-agent-chart'() {
-		return drawLineChart(
-			document.getElementById('lc-chats-per-agent-chart'),
-			chartContexts['lc-chats-per-agent-chart'],
-			['Open', 'Closed'],
-			[], [[], []], { legends: true, anim: true, smallTicks: true });
-	},
-
-	'lc-chats-per-dept-chart'() {
-		if (!document.getElementById('lc-chats-per-dept-chart')) {
-			return null;
-		}
-
-		return drawLineChart(
-			document.getElementById('lc-chats-per-dept-chart'),
-			chartContexts['lc-chats-per-dept-chart'],
-			['Open', 'Closed'],
-			[], [[], []], { legends: true, anim: true, smallTicks: true });
-	},
-
-	'lc-reaction-response-times-chart'() {
-		const timingLabels = [];
-		const initData = [];
-		const today = moment().startOf('day');
-		for (let m = today; m.diff(moment(), 'hours') < 0; m.add(1, 'hours')) {
-			const hour = m.format('H');
-			timingLabels.push(`${ moment(hour, ['H']).format('hA') }-${ moment((parseInt(hour) + 1) % 24, ['H']).format('hA') }`);
-			initData.push(0);
-		}
-
-		return drawLineChart(
-			document.getElementById('lc-reaction-response-times-chart'),
-			chartContexts['lc-reaction-response-times-chart'],
-			['Avg_reaction_time', 'Longest_reaction_time', 'Avg_response_time', 'Longest_response_time'],
-			timingLabels.slice(),
-			[initData.slice(), initData.slice(), initData.slice(), initData.slice()], { legends: true, anim: true, smallTicks: true });
-	},
-
-	'lc-chat-duration-chart'() {
-		const timingLabels = [];
-		const initData = [];
-		const today = moment().startOf('day');
-		for (let m = today; m.diff(moment(), 'hours') < 0; m.add(1, 'hours')) {
-			const hour = m.format('H');
-			timingLabels.push(`${ moment(hour, ['H']).format('hA') }-${ moment((parseInt(hour) + 1) % 24, ['H']).format('hA') }`);
-			initData.push(0);
-		}
-
-		return drawLineChart(
-			document.getElementById('lc-chat-duration-chart'),
-			chartContexts['lc-chat-duration-chart'],
-			['Avg_chat_duration', 'Longest_chat_duration'],
-			timingLabels.slice(),
-			[initData.slice(), initData.slice()], { legends: true, anim: true, smallTicks: true });
-	},
-};
-
-const initAllCharts = async () => {
-	chartContexts['lc-chats-chart'] = await initChart['lc-chats-chart']();
-	chartContexts['lc-agents-chart'] = await initChart['lc-agents-chart']();
-	chartContexts['lc-chats-per-agent-chart'] = await initChart['lc-chats-per-agent-chart']();
-	chartContexts['lc-chats-per-dept-chart'] = await initChart['lc-chats-per-dept-chart']();
-	chartContexts['lc-reaction-response-times-chart'] = await initChart['lc-reaction-response-times-chart']();
-	chartContexts['lc-chat-duration-chart'] = await initChart['lc-chat-duration-chart']();
-};
-
-const updateChartData = async (chartId, label, data) => {
-	if (!chartContexts[chartId]) {
-		chartContexts[chartId] = await initChart[chartId]();
-	}
-
-	await updateChart(chartContexts[chartId], label, data);
-};
-
-let timer;
-
-const getChartDepartment = (department) => department?._id;
-
-const getDaterange = () => {
-	const today = moment(new Date());
-	return {
-		start: `${ moment(new Date(today.year(), today.month(), today.date(), 0, 0, 0)).utc().format('YYYY-MM-DDTHH:mm:ss') }Z`,
-		end: `${ moment(new Date(today.year(), today.month(), today.date(), 23, 59, 59)).utc().format('YYYY-MM-DDTHH:mm:ss') }Z`,
-	};
-};
-
-const parseAdditionalParams = (options = {}, prefix = '') => `${ prefix }${ Object.keys(options).map((key) => `${ key }=${ options[key] }`).join('&') }`;
-
-const loadConversationOverview = async ({ start, end, ...options }) => {
-	const additionalParams = parseAdditionalParams(options, '&');
-	const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/conversation-totalizers?start=${ start }&end=${ end }${ additionalParams }`);
-	return totalizers;
-};
-
-const updateConversationOverview = async (totalizers) => {
-	if (totalizers && Array.isArray(totalizers)) {
-		templateInstance.conversationsOverview.set(totalizers);
-	}
-};
-
-const loadAgentsOverview = async ({ start, end, ...options }) => {
-	const additionalParams = parseAdditionalParams(options, '&');
-	const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/agents-productivity-totalizers?start=${ start }&end=${ end }${ additionalParams }`);
-	return totalizers;
-};
-
-const updateAgentsOverview = async (totalizers) => {
-	if (totalizers && Array.isArray(totalizers)) {
-		templateInstance.agentsOverview.set(totalizers);
-	}
-};
-const loadChatsOverview = async ({ start, end, ...options }) => {
-	const additionalParams = parseAdditionalParams(options, '&');
-	const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/chats-totalizers?start=${ start }&end=${ end }${ additionalParams }`);
-	return totalizers;
-};
-
-const updateChatsOverview = async (totalizers) => {
-	if (totalizers && Array.isArray(totalizers)) {
-		templateInstance.chatsOverview.set(totalizers);
-	}
-};
-
-const loadProductivityOverview = async ({ start, end, ...options }) => {
-	const additionalParams = parseAdditionalParams(options, '&');
-	const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/productivity-totalizers?start=${ start }&end=${ end }${ additionalParams }`);
-	return totalizers;
-};
-
-const updateProductivityOverview = async (totalizers) => {
-	if (totalizers && Array.isArray(totalizers)) {
-		templateInstance.timingOverview.set(totalizers);
-	}
-};
-
-const loadChatsChartData = ({ start, end, ...options }) => {
-	const additionalParams = parseAdditionalParams(options, '&');
-	return APIClient.v1.get(`livechat/analytics/dashboards/charts/chats?start=${ start }&end=${ end }${ additionalParams }`);
-};
-
-const updateChatsChart = async ({ open, closed, queued }) => {
-	await updateChartData('lc-chats-chart', 'Open', [open]);
-	await updateChartData('lc-chats-chart', 'Closed', [closed]);
-	await updateChartData('lc-chats-chart', 'Queue', [queued]);
-};
-
-const loadChatsPerAgentChartData = async ({ start, end, ...options }) => {
-	const additionalParams = parseAdditionalParams(options, '&');
-	const result = await APIClient.v1.get(`livechat/analytics/dashboards/charts/chats-per-agent?start=${ start }&end=${ end }${ additionalParams }`);
-	delete result.success;
-	return result;
-};
-
-const updateChatsPerAgentChart = async (agents) => {
-	// this chart need to reset before new updates
-	chartContexts['lc-chats-per-agent-chart'] = await initChart['lc-chats-per-agent-chart']();
-
-	Object
-		.keys(agents)
-		.forEach((agent) => updateChartData('lc-chats-per-agent-chart', agent, [agents[agent].open, agents[agent].closed]));
-};
-
-const loadAgentsStatusChartData = ({ departmentId }) => {
-	const additionalParams = parseAdditionalParams({ departmentId }, '?');
-	return APIClient.v1.get(`livechat/analytics/dashboards/charts/agents-status${ additionalParams }`);
-};
-
-const updateAgentStatusChart = async (statusData) => {
-	if (!statusData) {
-		return;
-	}
-
-	await updateChartData('lc-agents-chart', 'Offline', [statusData.offline]);
-	await updateChartData('lc-agents-chart', 'Available', [statusData.available]);
-	await updateChartData('lc-agents-chart', 'Away', [statusData.away]);
-	await updateChartData('lc-agents-chart', 'Busy', [statusData.busy]);
-};
-
-const loadChatsPerDepartmentChartData = async ({ start, end, ...options }) => {
-	const additionalParams = parseAdditionalParams(options, '&');
-	const result = await APIClient.v1.get(`livechat/analytics/dashboards/charts/chats-per-department?start=${ start }&end=${ end }${ additionalParams }`);
-	delete result.success;
-	return result;
-};
-
-const updateDepartmentsChart = async (departments) => {
-	// this chart need to reset before new updates
-	chartContexts['lc-chats-per-dept-chart'] = await initChart['lc-chats-per-dept-chart']();
-
-	Object
-		.keys(departments)
-		.forEach((department) => updateChartData('lc-chats-per-dept-chart', department, [departments[department].open, departments[department].closed]));
-};
-
-const loadTimingsChartData = ({ start, end, ...options }) => {
-	const additionalParams = parseAdditionalParams(options, '&');
-	return APIClient.v1.get(`livechat/analytics/dashboards/charts/timings?start=${ start }&end=${ end }${ additionalParams }`);
-};
-
-const updateTimingsChart = async (timingsData) => {
-	const hour = moment(new Date()).format('H');
-	const label = `${ moment(hour, ['H']).format('hA') }-${ moment((parseInt(hour) + 1) % 24, ['H']).format('hA') }`;
-
-	await updateChartData('lc-reaction-response-times-chart', label, [timingsData.reaction.avg, timingsData.reaction.longest, timingsData.response.avg, timingsData.response.longest]);
-	await updateChartData('lc-chat-duration-chart', label, [timingsData.chatDuration.avg, timingsData.chatDuration.longest]);
-};
-
-const getIntervalInMS = () => templateInstance.interval.get() * 1000;
-
-Template.livechatRealTimeMonitoring.helpers({
-	selected(value) {
-		return value === templateInstance.analyticsOptions.get().value || value === templateInstance.chartOptions.get().value ? 'selected' : false;
-	},
-	conversationsOverview() {
-		return templateInstance.conversationsOverview.get();
-	},
-	timingOverview() {
-		return templateInstance.timingOverview.get();
-	},
-	agentsOverview() {
-		return templateInstance.agentsOverview.get();
-	},
-	chatsOverview() {
-		return templateInstance.chatsOverview.get();
-	},
-	isLoading() {
-		return Template.instance().isLoading.get();
-	},
-	departmentModifier() {
-		return (filter, text = '') => {
-			const f = filter.get();
-			return `${ f.length === 0 ? text : text.replace(new RegExp(filter.get(), 'i'), (part) => `<strong>${ part }</strong>`) }`;
-		};
-	},
-	onClickTagDepartment() {
-		return Template.instance().onClickTagDepartment;
-	},
-	selectedDepartments() {
-		return Template.instance().selectedDepartments.get();
-	},
-	onSelectDepartments() {
-		return Template.instance().onSelectDepartments;
-	},
-	hasDepartments() {
-		return Template.instance().hasDepartments.get();
-	},
-});
-
-Template.livechatRealTimeMonitoring.onCreated(async function() {
-	templateInstance = Template.instance();
-	this.isLoading = new ReactiveVar(false);
-	this.conversationsOverview = new ReactiveVar();
-	this.timingOverview = new ReactiveVar();
-	this.chatsOverview = new ReactiveVar();
-	this.agentsOverview = new ReactiveVar();
-	this.conversationTotalizers = new ReactiveVar([]);
-	this.interval = new ReactiveVar(5);
-	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);
-});
-
-Template.livechatRealTimeMonitoring.onRendered(async function() {
-	await initAllCharts();
-
-	this.updateDashboard = async () => {
-		const [department] = this.selectedDepartments.get();
-		const departmentId = getChartDepartment(department);
-		const daterange = getDaterange();
-		const filters = Object.assign(
-			{ ...daterange },
-			departmentId && { departmentId },
-		);
-
-		updateConversationOverview(await loadConversationOverview(filters));
-		updateProductivityOverview(await loadProductivityOverview(filters));
-		updateChatsChart(await loadChatsChartData(filters));
-		updateChatsPerAgentChart(await loadChatsPerAgentChartData(filters));
-		updateAgentStatusChart(await loadAgentsStatusChartData(filters));
-		updateDepartmentsChart(await loadChatsPerDepartmentChartData(filters));
-		updateTimingsChart(await loadTimingsChartData(filters));
-		updateAgentsOverview(await loadAgentsOverview(filters));
-		updateChatsOverview(await loadChatsOverview(filters));
-	};
-	this.autorun(() => {
-		if (timer) {
-			clearInterval(timer);
-		}
-		timer = setInterval(() => this.updateDashboard(), getIntervalInMS());
-	});
-	this.isLoading.set(true);
-	await this.updateDashboard();
-	this.isLoading.set(false);
-});
-
-Template.livechatRealTimeMonitoring.events({
-	'change .js-interval': (event, instance) => {
-		instance.interval.set(event.target.value);
-	},
-});
-
-Template.livechatRealTimeMonitoring.onDestroyed(function() {
-	clearInterval(timer);
-});
diff --git a/client/helpers/getDateRange.js b/client/helpers/getDateRange.js
new file mode 100644
index 00000000000..84dd9ce73fc
--- /dev/null
+++ b/client/helpers/getDateRange.js
@@ -0,0 +1,9 @@
+import moment from 'moment';
+
+export const getDateRange = () => {
+	const today = moment(new Date());
+	return {
+		start: `${ moment(new Date(today.year(), today.month(), today.date(), 0, 0, 0)).utc().format('YYYY-MM-DDTHH:mm:ss') }Z`,
+		end: `${ moment(new Date(today.year(), today.month(), today.date(), 23, 59, 59)).utc().format('YYYY-MM-DDTHH:mm:ss') }Z`,
+	};
+};
diff --git a/client/omnichannel/DepartmentAutoComplete.js b/client/omnichannel/DepartmentAutoComplete.js
new file mode 100644
index 00000000000..499e62b21a3
--- /dev/null
+++ b/client/omnichannel/DepartmentAutoComplete.js
@@ -0,0 +1,24 @@
+import React, { useMemo, useState } from 'react';
+import { AutoComplete, Option, Icon } from '@rocket.chat/fuselage';
+import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
+
+import { useEndpointDataExperimental } from '../hooks/useEndpointDataExperimental';
+
+const query = (term = '') => ({ selector: JSON.stringify({ term }) });
+
+const DepartmentAutoComplete = React.memo((props) => {
+	const [filter, setFilter] = useState('');
+	const { data } = useEndpointDataExperimental('livechat/department.autocomplete', useMemo(() => query(filter), [filter]));
+	const options = useMemo(() => (data && data.items.map((department) => ({ value: department._id, label: department.name }))) || [], [data]);
+	const onClickRemove = useMutableCallback(() => props.onChange(''));
+	return <AutoComplete
+		{...props}
+		filter={filter}
+		setFilter={setFilter}
+		renderSelected={({ value, label, ...props }) => <Option key={value} {...props} onClick={onClickRemove}>{label}<Icon name='cross' /></Option>}
+		renderItem={({ value, label, ...props }) => <Option key={value} {...props} >{label}</Option>}
+		options={ options }
+	/>;
+});
+
+export default DepartmentAutoComplete;
diff --git a/client/omnichannel/realTimeMonitoring/RealTimeMonitoringPage.js b/client/omnichannel/realTimeMonitoring/RealTimeMonitoringPage.js
new file mode 100644
index 00000000000..7e991f74f07
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/RealTimeMonitoringPage.js
@@ -0,0 +1,109 @@
+import React, { useRef, useState, useMemo, useEffect } from 'react';
+import { Box, Select, Field, Margins } from '@rocket.chat/fuselage';
+import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
+
+import Page from '../../components/basic/Page';
+import ChatsChart from './charts/ChatsChart';
+import ChatsPerAgentChart from './charts/ChatsPerAgentChart';
+import AgentStatusChart from './charts/AgentStatusChart';
+import ChatsPerDepartmentChart from './charts/ChatsPerDepartmentChart';
+import ChatDurationChart from './charts/ChatDurationChart';
+import ResponseTimesChart from './charts/ResponseTimesChart';
+import ConversationOverview from './overviews/ConversationOverview';
+import AgentsOverview from './overviews/AgentsOverview';
+import ChatsOverview from './overviews/ChatsOverview';
+import ProductivityOverview from './overviews/ProductivityOverview';
+import DepartmentAutoComplete from '../DepartmentAutoComplete';
+import { getDateRange } from '../../helpers/getDateRange';
+import { useTranslation } from '../../contexts/TranslationContext';
+
+const dateRange = getDateRange();
+
+const RealTimeMonitoringPage = () => {
+	const t = useTranslation();
+
+	const [reloadFrequency, setReloadFrequency] = useState(5);
+	const [department, setDepartment] = useState('');
+
+	const reloadRef = useRef({});
+
+	const departmentParams = useMemo(() => ({
+		...department && { departmentId: department },
+	}), [department]);
+
+	const allParams = useMemo(() => ({
+		...departmentParams,
+		...dateRange,
+	}), [departmentParams]);
+
+	const reloadCharts = useMutableCallback(() => {
+		Object.values(reloadRef.current).forEach((reload) => {
+			reload();
+		});
+	});
+
+	useEffect(() => {
+		const interval = setInterval(reloadCharts, reloadFrequency * 1000);
+		return () => {
+			clearInterval(interval);
+		};
+	}, [reloadCharts, reloadFrequency]);
+
+	const reloadOptions = useMemo(() => [
+		[5, <>5 {t('seconds')}</>],
+		[10, <>10 {t('seconds')}</>],
+		[30, <>30 {t('seconds')}</>],
+		[60, <>1 {t('minute')}</>],
+	], [t]);
+
+	return <Page>
+		<Page.Header title={t('Real_Time_Monitoring')}>
+		</Page.Header>
+		<Page.ScrollableContentWithShadow>
+			<Margins block='x4'>
+				<Box flexDirection='row' display='flex' justifyContent='space-between' alignSelf='center' w='full'>
+					<Field mie='x4'>
+						<Field.Label>{t('Department')}</Field.Label>
+						<Field.Row>
+							<DepartmentAutoComplete placeholder={t('All')} value={department} onChange={setDepartment}/>
+						</Field.Row>
+					</Field>
+					<Field mis='x4'>
+						<Field.Label>{t('Update_every')}</Field.Label>
+						<Field.Row>
+							<Select options={reloadOptions} onChange={useMutableCallback((val) => setReloadFrequency(val))} value={reloadFrequency}/>
+						</Field.Row>
+					</Field>
+				</Box>
+				<Box display='flex' flexDirection='row' w='full' alignItems='stretch' flexShrink={1}>
+					<ConversationOverview flexGrow={1} flexShrink={1} width='50%' reloadRef={reloadRef} params={allParams}/>
+				</Box>
+				<Box display='flex' flexDirection='row' w='full' alignItems='stretch' flexShrink={1}>
+					<ChatsChart flexGrow={1} flexShrink={1} width='50%' mie='x2' reloadRef={reloadRef} params={allParams}/>
+					<ChatsPerAgentChart flexGrow={1} flexShrink={1} width='50%' mis='x2' reloadRef={reloadRef} params={allParams}/>
+				</Box>
+				<Box display='flex' flexDirection='row' w='full' alignItems='stretch' flexShrink={1}>
+					<ChatsOverview flexGrow={1} flexShrink={1} width='50%' reloadRef={reloadRef} params={allParams}/>
+				</Box>
+				<Box display='flex' flexDirection='row' w='full' alignItems='stretch' flexShrink={1}>
+					<AgentStatusChart flexGrow={1} flexShrink={1} width='50%' mie='x2' reloadRef={reloadRef} params={allParams}/>
+					<ChatsPerDepartmentChart flexGrow={1} flexShrink={1} width='50%' mis='x2' reloadRef={reloadRef} params={allParams}/>
+				</Box>
+				<Box display='flex' flexDirection='row' w='full' alignItems='stretch' flexShrink={1}>
+					<AgentsOverview flexGrow={1} flexShrink={1} reloadRef={reloadRef} params={allParams}/>
+				</Box>
+				<Box display='flex' w='full' flexShrink={1}>
+					<ChatDurationChart flexGrow={1} flexShrink={1} reloadRef={reloadRef} params={allParams}/>
+				</Box>
+				<Box display='flex' flexDirection='row' w='full' alignItems='stretch' flexShrink={1}>
+					<ProductivityOverview flexGrow={1} flexShrink={1} reloadRef={reloadRef} params={allParams}/>
+				</Box>
+				<Box display='flex' w='full' flexShrink={1}>
+					<ResponseTimesChart flexGrow={1} flexShrink={1} reloadRef={reloadRef} params={allParams}/>
+				</Box>
+			</Margins>
+		</Page.ScrollableContentWithShadow>
+	</Page>;
+};
+
+export default RealTimeMonitoringPage;
diff --git a/client/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js b/client/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js
new file mode 100644
index 00000000000..8a9d96b9620
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js
@@ -0,0 +1,72 @@
+import React, { useRef, useEffect } from 'react';
+
+import Chart from './Chart';
+import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental';
+import { useTranslation } from '../../../contexts/TranslationContext';
+import { drawDoughnutChart } from '../../../../app/livechat/client/lib/chartHandler';
+import { useUpdateChartData } from './useUpdateChartData';
+
+const labels = ['Available', 'Away', 'Busy', 'Offline'];
+
+const initialData = {
+	available: 0,
+	away: 0,
+	busy: 0,
+	offline: 0,
+};
+
+const init = (canvas, context, t) => drawDoughnutChart(
+	canvas,
+	t('Agents'),
+	context,
+	labels,
+	Object.values(initialData),
+);
+
+const AgentStatusChart = ({ params, reloadRef, ...props }) => {
+	const t = useTranslation();
+
+	const canvas = useRef();
+	const context = useRef();
+
+	const updateChartData = useUpdateChartData({
+		context,
+		canvas,
+		t,
+		init,
+	});
+
+	const { data, state, reload } = useEndpointDataExperimental(
+		'livechat/analytics/dashboards/charts/agents-status',
+		params,
+	);
+
+	reloadRef.current.agentStatusChart = reload;
+
+	const {
+		offline = 0,
+		available = 0,
+		away = 0,
+		busy = 0,
+	} = data ?? initialData;
+
+	useEffect(() => {
+		const initChart = async () => {
+			context.current = await init(canvas.current, context.current, t);
+		};
+		initChart();
+	}, [t]);
+
+	useEffect(() => {
+		if (state === ENDPOINT_STATES.DONE) {
+			updateChartData('Offline', [offline]);
+			updateChartData('Available', [available]);
+			updateChartData('Away', [away]);
+			updateChartData('Busy', [busy]);
+		}
+	}, [available, away, busy, offline, state, t, updateChartData]);
+
+	return <Chart ref={canvas} {...props}/>;
+};
+
+export default AgentStatusChart;
diff --git a/client/omnichannel/realTimeMonitoring/charts/Chart.js b/client/omnichannel/realTimeMonitoring/charts/Chart.js
new file mode 100644
index 00000000000..19d66f066fc
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/charts/Chart.js
@@ -0,0 +1,17 @@
+import React, { forwardRef } from 'react';
+import { Box } from '@rocket.chat/fuselage';
+
+const style = {
+	minHeight: '250px',
+};
+const Chart = forwardRef(function Chart(props, ref) {
+	return <Box
+		padding='x20'
+		height='x300'
+		{...props}
+	>
+		<canvas ref={ref} style={style}></canvas>
+	</Box>;
+});
+
+export default Chart;
diff --git a/client/omnichannel/realTimeMonitoring/charts/ChatDurationChart.js b/client/omnichannel/realTimeMonitoring/charts/ChatDurationChart.js
new file mode 100644
index 00000000000..38cfa059874
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/charts/ChatDurationChart.js
@@ -0,0 +1,71 @@
+import React, { useRef, useEffect } from 'react';
+
+import Chart from './Chart';
+import { useUpdateChartData } from './useUpdateChartData';
+import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental';
+import { useTranslation } from '../../../contexts/TranslationContext';
+import { drawLineChart } from '../../../../app/livechat/client/lib/chartHandler';
+import { getMomentChartLabelsAndData } from './getMomentChartLabelsAndData';
+import { getMomentCurrentLabel } from './getMomentCurrentLabel';
+
+const [labels, initialData] = getMomentChartLabelsAndData();
+
+const init = (canvas, context, t) => drawLineChart(
+	canvas,
+	context,
+	[t('Avg_chat_duration'), t('Longest_chat_duration')],
+	labels,
+	[initialData, initialData],
+	{ legends: true, anim: true, smallTicks: true },
+);
+
+const ChatDurationChart = ({ params, reloadRef, ...props }) => {
+	const t = useTranslation();
+
+	const canvas = useRef();
+	const context = useRef();
+
+	const updateChartData = useUpdateChartData({
+		context,
+		canvas,
+		t,
+		init,
+	});
+
+	const { data, state, reload } = useEndpointDataExperimental(
+		'livechat/analytics/dashboards/charts/timings',
+		params,
+	);
+
+	reloadRef.current.chatDurationChart = reload;
+
+	const {
+		chatDuration: {
+			avg,
+			longest,
+		},
+	} = data ?? {
+		chatDuration: {
+			avg: 0,
+			longest: 0,
+		},
+	};
+
+	useEffect(() => {
+		const initChart = async () => {
+			context.current = await init(canvas.current, context.current, t);
+		};
+		initChart();
+	}, [t]);
+
+	useEffect(() => {
+		if (state === ENDPOINT_STATES.DONE) {
+			const label = getMomentCurrentLabel();
+			updateChartData(label, [avg, longest]);
+		}
+	}, [avg, longest, state, t, updateChartData]);
+
+	return <Chart ref={canvas} {...props}/>;
+};
+
+export default ChatDurationChart;
diff --git a/client/omnichannel/realTimeMonitoring/charts/ChatsChart.js b/client/omnichannel/realTimeMonitoring/charts/ChatsChart.js
new file mode 100644
index 00000000000..032a9439ba6
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/charts/ChatsChart.js
@@ -0,0 +1,73 @@
+import React, { useRef, useEffect } from 'react';
+
+import Chart from './Chart';
+import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental';
+import { useTranslation } from '../../../contexts/TranslationContext';
+import { drawDoughnutChart } from '../../../../app/livechat/client/lib/chartHandler';
+import { useUpdateChartData } from './useUpdateChartData';
+
+const labels = [
+	'Open',
+	'Queued',
+	'Closed',
+];
+
+const initialData = {
+	open: 0,
+	queued: 0,
+	closed: 0,
+};
+
+const init = (canvas, context, t) => drawDoughnutChart(
+	canvas,
+	t('Chats'),
+	context,
+	labels,
+	Object.values(initialData),
+);
+
+const ChatsChart = ({ params, reloadRef, ...props }) => {
+	const t = useTranslation();
+
+	const canvas = useRef();
+	const context = useRef();
+
+	const updateChartData = useUpdateChartData({
+		context,
+		canvas,
+		t,
+		init,
+	});
+
+	const { data, state, reload } = useEndpointDataExperimental(
+		'livechat/analytics/dashboards/charts/chats',
+		params,
+	);
+
+	reloadRef.current.chatsChart = reload;
+
+	const {
+		open,
+		queued,
+		closed,
+	} = data ?? initialData;
+
+	useEffect(() => {
+		const initChart = async () => {
+			context.current = await init(canvas.current, context.current, t);
+		};
+		initChart();
+	}, [t]);
+
+	useEffect(() => {
+		if (state === ENDPOINT_STATES.DONE) {
+			updateChartData(t('Open'), [open]);
+			updateChartData(t('Closed'), [closed]);
+			updateChartData(t('Queued'), [queued]);
+		}
+	}, [closed, open, queued, state, t, updateChartData]);
+
+	return <Chart ref={canvas} {...props}/>;
+};
+
+export default ChatsChart;
diff --git a/client/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.js b/client/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.js
new file mode 100644
index 00000000000..3b18b98e19e
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.js
@@ -0,0 +1,64 @@
+import React, { useRef, useEffect } from 'react';
+
+import Chart from './Chart';
+import { useUpdateChartData } from './useUpdateChartData';
+import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental';
+import { useTranslation } from '../../../contexts/TranslationContext';
+import { drawLineChart } from '../../../../app/livechat/client/lib/chartHandler';
+
+const initialData = {
+	agents: {},
+};
+
+const init = (canvas, context, t) => drawLineChart(
+	canvas,
+	context,
+	[t('Open'), t('Closed')],
+	[],
+	[[], []],
+	{ legends: true, anim: true, smallTicks: true },
+);
+
+const ChatsPerAgentChart = ({ params, reloadRef, ...props }) => {
+	const t = useTranslation();
+
+	const canvas = useRef();
+	const context = useRef();
+
+	const updateChartData = useUpdateChartData({
+		context,
+		canvas,
+		t,
+		init,
+	});
+
+	const { data, state, reload } = useEndpointDataExperimental(
+		'livechat/analytics/dashboards/charts/chats-per-agent',
+		params,
+	);
+
+	reloadRef.current.chatsPerAgentChart = reload;
+
+	const {
+		agents = {},
+	} = data ?? initialData;
+
+	useEffect(() => {
+		const initChart = async () => {
+			context.current = await init(canvas.current, context.current, t);
+		};
+		initChart();
+	}, [t]);
+
+	useEffect(() => {
+		if (state === ENDPOINT_STATES.DONE) {
+			Object.entries(agents).forEach(([name, value]) => {
+				updateChartData(name, [value.open, value.closed]);
+			});
+		}
+	}, [agents, state, t, updateChartData]);
+
+	return <Chart ref={canvas} {...props}/>;
+};
+
+export default ChatsPerAgentChart;
diff --git a/client/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.js b/client/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.js
new file mode 100644
index 00000000000..493c7d9288a
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.js
@@ -0,0 +1,64 @@
+import React, { useRef, useEffect } from 'react';
+
+import Chart from './Chart';
+import { useUpdateChartData } from './useUpdateChartData';
+import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental';
+import { useTranslation } from '../../../contexts/TranslationContext';
+import { drawLineChart } from '../../../../app/livechat/client/lib/chartHandler';
+
+const initialData = {
+	departments: {},
+};
+
+const init = (canvas, context, t) => drawLineChart(
+	canvas,
+	context,
+	[t('Open'), t('Closed')],
+	[],
+	[[], []],
+	{ legends: true, anim: true, smallTicks: true },
+);
+
+const ChatsPerDepartmentChart = ({ params, reloadRef, ...props }) => {
+	const t = useTranslation();
+
+	const canvas = useRef();
+	const context = useRef();
+
+	const updateChartData = useUpdateChartData({
+		context,
+		canvas,
+		t,
+		init,
+	});
+
+	const { data, state, reload } = useEndpointDataExperimental(
+		'livechat/analytics/dashboards/charts/chats-per-department',
+		params,
+	);
+
+	reloadRef.current.chatsPerDepartmentChart = reload;
+
+	const {
+		departments = {},
+	} = data ?? initialData;
+
+	useEffect(() => {
+		const initChart = async () => {
+			context.current = await init(canvas.current, context.current, t);
+		};
+		initChart();
+	}, [t]);
+
+	useEffect(() => {
+		if (state === ENDPOINT_STATES.DONE) {
+			Object.entries(departments).forEach(([name, value]) => {
+				updateChartData(name, [value.open, value.closed]);
+			});
+		}
+	}, [departments, state, t, updateChartData]);
+
+	return <Chart ref={canvas} {...props}/>;
+};
+
+export default ChatsPerDepartmentChart;
diff --git a/client/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.js b/client/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.js
new file mode 100644
index 00000000000..5f34715280c
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.js
@@ -0,0 +1,79 @@
+import React, { useRef, useEffect } from 'react';
+
+import Chart from './Chart';
+import { useUpdateChartData } from './useUpdateChartData';
+import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental';
+import { useTranslation } from '../../../contexts/TranslationContext';
+import { drawLineChart } from '../../../../app/livechat/client/lib/chartHandler';
+import { getMomentChartLabelsAndData } from './getMomentChartLabelsAndData';
+import { getMomentCurrentLabel } from './getMomentCurrentLabel';
+
+const [labels, initialData] = getMomentChartLabelsAndData();
+
+const init = (canvas, context, t) => drawLineChart(
+	canvas,
+	context,
+	[t('Avg_reaction_time'), t('Longest_reaction_time'), t('Avg_response_time'), t('Longest_response_time')],
+	labels,
+	[initialData, initialData, initialData, initialData],
+	{ legends: true, anim: true, smallTicks: true },
+);
+
+const ResponseTimesChart = ({ params, reloadRef, ...props }) => {
+	const t = useTranslation();
+
+	const canvas = useRef();
+	const context = useRef();
+
+	const updateChartData = useUpdateChartData({
+		context,
+		canvas,
+		t,
+		init,
+	});
+
+	const { data, state, reload } = useEndpointDataExperimental(
+		'livechat/analytics/dashboards/charts/timings',
+		params,
+	);
+
+	reloadRef.current.responseTimesChart = reload;
+
+	const {
+		reaction: {
+			avg: reactionAvg,
+			longest: reactionLongest,
+		},
+		response: {
+			avg: responseAvg,
+			longest: responseLongest,
+		},
+	} = data ?? {
+		reaction: {
+			avg: 0,
+			longest: 0,
+		},
+		response: {
+			avg: 0,
+			longest: 0,
+		},
+	};
+
+	useEffect(() => {
+		const initChart = async () => {
+			context.current = await init(canvas.current, context.current, t);
+		};
+		initChart();
+	}, [t]);
+
+	useEffect(() => {
+		if (state === ENDPOINT_STATES.DONE) {
+			const label = getMomentCurrentLabel();
+			updateChartData(label, [reactionAvg, reactionLongest, responseAvg, responseLongest]);
+		}
+	}, [reactionAvg, reactionLongest, responseAvg, responseLongest, state, t, updateChartData]);
+
+	return <Chart ref={canvas} {...props}/>;
+};
+
+export default ResponseTimesChart;
diff --git a/client/omnichannel/realTimeMonitoring/charts/getMomentChartLabelsAndData.js b/client/omnichannel/realTimeMonitoring/charts/getMomentChartLabelsAndData.js
new file mode 100644
index 00000000000..64b1ace72cb
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/charts/getMomentChartLabelsAndData.js
@@ -0,0 +1,14 @@
+import moment from 'moment';
+
+export const getMomentChartLabelsAndData = () => {
+	const timingLabels = [];
+	const initData = [];
+	const today = moment().startOf('day');
+	for (let m = today; m.diff(moment(), 'hours') < 0; m.add(1, 'hours')) {
+		const hour = m.format('H');
+		timingLabels.push(`${ moment(hour, ['H']).format('hA') }-${ moment((parseInt(hour) + 1) % 24, ['H']).format('hA') }`);
+		initData.push(0);
+	}
+
+	return [timingLabels, initData];
+};
diff --git a/client/omnichannel/realTimeMonitoring/charts/getMomentCurrentLabel.js b/client/omnichannel/realTimeMonitoring/charts/getMomentCurrentLabel.js
new file mode 100644
index 00000000000..964a21b4298
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/charts/getMomentCurrentLabel.js
@@ -0,0 +1,8 @@
+
+import moment from 'moment';
+
+export const getMomentCurrentLabel = () => {
+	const hour = moment(new Date()).format('H');
+
+	return `${ moment(hour, ['H']).format('hA') }-${ moment((parseInt(hour) + 1) % 24, ['H']).format('hA') }`;
+};
diff --git a/client/omnichannel/realTimeMonitoring/charts/useUpdateChartData.js b/client/omnichannel/realTimeMonitoring/charts/useUpdateChartData.js
new file mode 100644
index 00000000000..7deaf9d1f65
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/charts/useUpdateChartData.js
@@ -0,0 +1,10 @@
+import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
+
+import { updateChart } from '../../../../app/livechat/client/lib/chartHandler';
+
+export const useUpdateChartData = ({ context, canvas, init, t }) => useMutableCallback(async (label, data) => {
+	if (!context.current) {
+		context.current = await init(canvas.current, context.current, t);
+	}
+	await updateChart(context.current, label, data);
+});
diff --git a/client/omnichannel/realTimeMonitoring/counter/CounterContainer.js b/client/omnichannel/realTimeMonitoring/counter/CounterContainer.js
new file mode 100644
index 00000000000..e260e80585a
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/counter/CounterContainer.js
@@ -0,0 +1,29 @@
+import React, { useEffect, useState } from 'react';
+import { Skeleton } from '@rocket.chat/fuselage';
+
+import { ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental';
+import { useTranslation } from '../../../contexts/TranslationContext';
+import CounterRow from './CounterRow';
+import CounterItem from './CounterItem';
+
+const CounterContainer = ({ data, state, initialData, ...props }) => {
+	const t = useTranslation();
+
+	const [displayData, setDisplayData] = useState(initialData);
+
+	const {
+		totalizers,
+	} = data || { totalizers: initialData };
+
+	useEffect(() => {
+		if (state === ENDPOINT_STATES.DONE) {
+			setDisplayData(totalizers);
+		}
+	}, [state, t, totalizers]);
+
+	return <CounterRow {...props}>
+		{displayData.map(({ title, value }, i) => <CounterItem key={i} title={title ? t(title) : <Skeleton width='x60' />} count={value}/>)}
+	</CounterRow>;
+};
+
+export default CounterContainer;
diff --git a/client/omnichannel/realTimeMonitoring/counter/CounterItem.js b/client/omnichannel/realTimeMonitoring/counter/CounterItem.js
new file mode 100644
index 00000000000..8cfc7016d92
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/counter/CounterItem.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import { Box } from '@rocket.chat/fuselage';
+
+const CounterItem = ({ title = '', count = '-', ...props }) => <Box
+	display='flex'
+	flexDirection='column'
+	justifyContent='space-between'
+	alignItems='center'
+	flexGrow={1}
+	{...props}
+>
+	<Box fontScale='s1' textTransform='uppercase' color='hint' textAlign='center' pi='x8'>
+		{title}
+	</Box>
+	<Box fontScale='h1'>
+		{count}
+	</Box>
+</Box>;
+
+export default CounterItem;
diff --git a/client/omnichannel/realTimeMonitoring/counter/CounterRow.js b/client/omnichannel/realTimeMonitoring/counter/CounterRow.js
new file mode 100644
index 00000000000..526372e1580
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/counter/CounterRow.js
@@ -0,0 +1,23 @@
+import React, { Fragment } from 'react';
+import { Box, Divider } from '@rocket.chat/fuselage';
+import flattenChildren from 'react-keyed-flatten-children';
+
+const CounterRow = ({ children, ...props }) => <Box
+	pb='x28'
+	pi='x20'
+	display='flex'
+	flexDirection='row'
+	justifyContent='space-around'
+	alignItems='center'
+	flexGrow={1}
+	{...props}
+>
+	{children && flattenChildren(children).reduce((acc, child, i) => {
+		acc = children.length - 1 !== i
+			? [...acc, <Fragment key={i}>{child}</Fragment>, <Divider key={(i + 1) * children.length} width='x2' m='none' alignSelf='stretch'/>]
+			: [...acc, child];
+		return acc;
+	}, [])}
+</Box>;
+
+export default CounterRow;
diff --git a/client/omnichannel/realTimeMonitoring/counter/CounterRow.stories.js b/client/omnichannel/realTimeMonitoring/counter/CounterRow.stories.js
new file mode 100644
index 00000000000..ca1bd8d550e
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/counter/CounterRow.stories.js
@@ -0,0 +1,16 @@
+import React from 'react';
+
+import CounterRow from './CounterRow';
+import CounterItem from './CounterItem';
+
+export default {
+	title: 'omnichannel/RealtimeMonitoring/Counter',
+	component: CounterRow,
+};
+
+export const Default = () => <CounterRow>
+	<CounterItem title='total conversations' count={10}/>
+	<CounterItem title='open conversations' count={10}/>
+	<CounterItem title='total messages' count={10}/>
+	<CounterItem title='total visitors'/>
+</CounterRow>;
diff --git a/client/omnichannel/realTimeMonitoring/overviews/AgentsOverview.js b/client/omnichannel/realTimeMonitoring/overviews/AgentsOverview.js
new file mode 100644
index 00000000000..29dc5457d21
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/overviews/AgentsOverview.js
@@ -0,0 +1,28 @@
+import React from 'react';
+
+import { useEndpointDataExperimental } from '../../../hooks/useEndpointDataExperimental';
+import CounterContainer from '../counter/CounterContainer';
+
+const overviewInitalValue = {
+	title: '',
+	value: '-',
+};
+
+const initialData = [
+	overviewInitalValue,
+	overviewInitalValue,
+	overviewInitalValue,
+];
+
+const AgentsOverview = ({ params, reloadRef, ...props }) => {
+	const { data, state, reload } = useEndpointDataExperimental(
+		'livechat/analytics/dashboards/agents-productivity-totalizers',
+		params,
+	);
+
+	reloadRef.current.agentsOverview = reload;
+
+	return <CounterContainer state={state} data={data} initialData={initialData} {...props}/>;
+};
+
+export default AgentsOverview;
diff --git a/client/omnichannel/realTimeMonitoring/overviews/ChatsOverview.js b/client/omnichannel/realTimeMonitoring/overviews/ChatsOverview.js
new file mode 100644
index 00000000000..08f6cec7a37
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/overviews/ChatsOverview.js
@@ -0,0 +1,23 @@
+import React from 'react';
+
+import { useEndpointDataExperimental } from '../../../hooks/useEndpointDataExperimental';
+import CounterContainer from '../counter/CounterContainer';
+
+const initialData = [
+	{ title: '', value: 0 },
+	{ title: '', value: '0%' },
+	{ title: '', value: '00:00:00' },
+];
+
+const ChatsOverview = ({ params, reloadRef, ...props }) => {
+	const { data, state, reload } = useEndpointDataExperimental(
+		'livechat/analytics/dashboards/chats-totalizers',
+		params,
+	);
+
+	reloadRef.current.chatsOverview = reload;
+
+	return <CounterContainer state={state} data={data} initialData={initialData} {...props}/>;
+};
+
+export default ChatsOverview;
diff --git a/client/omnichannel/realTimeMonitoring/overviews/ConversationOverview.js b/client/omnichannel/realTimeMonitoring/overviews/ConversationOverview.js
new file mode 100644
index 00000000000..6fe604dd9fe
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/overviews/ConversationOverview.js
@@ -0,0 +1,29 @@
+import React from 'react';
+
+import { useEndpointDataExperimental } from '../../../hooks/useEndpointDataExperimental';
+import CounterContainer from '../counter/CounterContainer';
+
+const overviewInitalValue = {
+	title: '',
+	value: 0,
+};
+
+const initialData = [
+	overviewInitalValue,
+	overviewInitalValue,
+	overviewInitalValue,
+	overviewInitalValue,
+];
+
+const ConversationOverview = ({ params, reloadRef, ...props }) => {
+	const { data, state, reload } = useEndpointDataExperimental(
+		'livechat/analytics/dashboards/conversation-totalizers',
+		params,
+	);
+
+	reloadRef.current.conversationOverview = reload;
+
+	return <CounterContainer state={state} data={data} initialData={initialData} {...props}/>;
+};
+
+export default ConversationOverview;
diff --git a/client/omnichannel/realTimeMonitoring/overviews/ProductivityOverview.js b/client/omnichannel/realTimeMonitoring/overviews/ProductivityOverview.js
new file mode 100644
index 00000000000..a86cadf5c9a
--- /dev/null
+++ b/client/omnichannel/realTimeMonitoring/overviews/ProductivityOverview.js
@@ -0,0 +1,27 @@
+import React from 'react';
+
+import { useEndpointDataExperimental } from '../../../hooks/useEndpointDataExperimental';
+import CounterContainer from '../counter/CounterContainer';
+
+const defaultValue = { title: '', value: '00:00:00' };
+
+
+const initialData = [
+	defaultValue,
+	defaultValue,
+	defaultValue,
+	defaultValue,
+];
+
+const ProductivityOverview = ({ params, reloadRef, ...props }) => {
+	const { data, state, reload } = useEndpointDataExperimental(
+		'livechat/analytics/dashboards/productivity-totalizers',
+		params,
+	);
+
+	reloadRef.current.productivityOverview = reload;
+
+	return <CounterContainer state={state} data={data} initialData={initialData} {...props}/>;
+};
+
+export default ProductivityOverview;
diff --git a/client/omnichannel/routes.js b/client/omnichannel/routes.js
index 9def7135dea..fe9cd83a93c 100644
--- a/client/omnichannel/routes.js
+++ b/client/omnichannel/routes.js
@@ -73,3 +73,8 @@ registerOmnichannelRoute('/current', {
 	name: 'omnichannel-current-chats',
 	lazyRouteComponent: () => import('./currentChats/CurrentChatsRoute'),
 });
+
+registerOmnichannelRoute('/realtime-monitoring', {
+	name: 'omnichannel-realTime',
+	lazyRouteComponent: () => import('./realTimeMonitoring/RealTimeMonitoringPage'),
+});
diff --git a/client/omnichannel/sidebarItems.js b/client/omnichannel/sidebarItems.js
index 5aee7ef1d01..82586bc50da 100644
--- a/client/omnichannel/sidebarItems.js
+++ b/client/omnichannel/sidebarItems.js
@@ -15,7 +15,7 @@ export const {
 		i18nLabel: 'Analytics',
 		permissionGranted: () => hasPermission('view-livechat-analytics'),
 	}, {
-		href: 'omnichannel/real-time-monitoring',
+		href: 'omnichannel-realTime',
 		i18nLabel: 'Real_Time_Monitoring',
 		permissionGranted: () => hasPermission('view-livechat-real-time-monitoring'),
 	}, {
diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json
index 36e5dd92e21..d6e264059b4 100644
--- a/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -3682,6 +3682,7 @@
   "Unread_Tray_Icon_Alert": "Unread Tray Icon Alert",
   "Unstar_Message": "Remove Star",
   "Update": "Update",
+  "Update_every": "Update every",
   "Update_LatestAvailableVersion": "Update Latest Available Version",
   "Update_EnableChecker": "Enable the Update Checker",
   "Update_to_version": "Update to __version__",
-- 
GitLab