Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { SelectOption } from '@rocket.chat/fuselage';
import { Box, Select, Margins, Option } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
import type { MutableRefObject } from 'react';
import React, { useRef, useState, useMemo, useEffect, Fragment } from 'react';

import AutoCompleteDepartment from '../../../components/AutoCompleteDepartment';
Expand All @@ -18,7 +21,7 @@ import ChatsOverview from './overviews/ChatsOverview';
import ConversationOverview from './overviews/ConversationOverview';
import ProductivityOverview from './overviews/ProductivityOverview';

const randomizeKeys = (keys) => {
const randomizeKeys = (keys: MutableRefObject<string[]>) => {
keys.current = keys.current.map((_key, i) => {
return `${i}_${new Date().getTime()}`;
});
Expand All @@ -28,13 +31,14 @@ const dateRange = getDateRange();

const RealTimeMonitoringPage = () => {
const t = useTranslation();
const queryClient = useQueryClient();

const keys = useRef([...Array(10).keys()]);
const keys = useRef<string[]>([...Array(10).map((_, i) => `${i}_${new Date().getTime()}`)]);

const [reloadFrequency, setReloadFrequency] = useState(5);
const [reloadFrequency, setReloadFrequency] = useState<number>(5);
const [departmentId, setDepartment] = useState('');

const reloadRef = useRef({});
const reloadRef = useRef<{ [x: string]: () => void }>({});

const departmentParams = useMemo(
() => ({
Expand Down Expand Up @@ -62,20 +66,22 @@ const RealTimeMonitoringPage = () => {
});

useEffect(() => {
const interval = setInterval(reloadCharts, reloadFrequency * 1000);
const interval = setInterval(reloadCharts, Number(reloadFrequency) * 1000);
return () => {
clearInterval(interval);
randomizeKeys(keys);
};
}, [reloadCharts, reloadFrequency]);
}, [queryClient, reloadCharts, reloadFrequency]);

// TODO Check if Select Options does indeed accepts Elements as labels
const reloadOptions = useMemo(
() => [
[5, <Fragment key='5 seconds'>5 {t('seconds')}</Fragment>],
[10, <Fragment key='10 seconds'>10 {t('seconds')}</Fragment>],
[30, <Fragment key='30 seconds'>30 {t('seconds')}</Fragment>],
[60, <Fragment key='1 minute'>1 {t('minute')}</Fragment>],
],
() =>
[
['5', <Fragment key='5 seconds'>5 {t('seconds')}</Fragment>],
['10', <Fragment key='10 seconds'>10 {t('seconds')}</Fragment>],
['30', <Fragment key='30 seconds'>30 {t('seconds')}</Fragment>],
['60', <Fragment key='1 minute'>1 {t('minute')}</Fragment>],
] as unknown as SelectOption[],
[t],
);

Expand All @@ -99,21 +105,33 @@ const RealTimeMonitoringPage = () => {
</Box>
<Box maxWidth='50%' display='flex' mi={4} flexGrow={1} flexDirection='column'>
<Label mb={4}>{t('Update_every')}</Label>
<Select options={reloadOptions} onChange={useMutableCallback((val) => setReloadFrequency(val))} value={reloadFrequency} />
<Select
options={reloadOptions}
onChange={useMutableCallback((val) => setReloadFrequency(Number(val)))}
value={reloadFrequency}
/>
</Box>
</Box>
<Box display='flex' flexDirection='row' w='full' alignItems='stretch' flexShrink={1}>
<ConversationOverview key={keys?.current[0]} flexGrow={1} flexShrink={1} width='50%' reloadRef={reloadRef} params={allParams} />
</Box>
<Box display='flex' flexDirection='row' w='full' alignItems='stretch' flexShrink={1}>
<ChatsChart key={keys?.current[1]} flexGrow={1} flexShrink={1} width='50%' mie={2} reloadRef={reloadRef} params={allParams} />
<ChatsChart
reloadFrequency={reloadFrequency}
key={keys?.current[1]}
flexGrow={1}
flexShrink={1}
width='50%'
mie={2}
params={allParams}
/>
<ChatsPerAgentChart
reloadFrequency={reloadFrequency}
key={keys?.current[2]}
flexGrow={1}
flexShrink={1}
width='50%'
mis={2}
reloadRef={reloadRef}
params={allParams}
/>
</Box>
Expand All @@ -122,35 +140,49 @@ const RealTimeMonitoringPage = () => {
</Box>
<Box display='flex' flexDirection='row' w='full' alignItems='stretch' flexShrink={1}>
<AgentStatusChart
reloadFrequency={reloadFrequency}
key={keys?.current[4]}
flexGrow={1}
flexShrink={1}
width='50%'
mie={2}
reloadRef={reloadRef}
params={allParams}
/>
<ChatsPerDepartmentChart
reloadFrequency={reloadFrequency}
key={keys?.current[5]}
flexGrow={1}
flexShrink={1}
width='50%'
mis={2}
reloadRef={reloadRef}
params={allParams}
/>
</Box>
<Box display='flex' flexDirection='row' w='full' alignItems='stretch' flexShrink={1}>
<AgentsOverview key={keys?.current[6]} flexGrow={1} flexShrink={1} reloadRef={reloadRef} params={allParams} />
</Box>
<Box display='flex' w='full' flexShrink={1}>
<ChatDurationChart key={keys?.current[7]} flexGrow={1} flexShrink={1} w='100%' reloadRef={reloadRef} params={allParams} />
<ChatDurationChart
reloadFrequency={reloadFrequency}
key={keys?.current[7]}
flexGrow={1}
flexShrink={1}
w='100%'
params={allParams}
/>
</Box>
<Box display='flex' flexDirection='row' w='full' alignItems='stretch' flexShrink={1}>
<ProductivityOverview key={keys?.current[8]} flexGrow={1} flexShrink={1} reloadRef={reloadRef} params={allParams} />
</Box>
<Box display='flex' w='full' flexShrink={1}>
<ResponseTimesChart key={keys?.current[9]} flexGrow={1} flexShrink={1} w='100%' reloadRef={reloadRef} params={allParams} />
<ResponseTimesChart
reloadFrequency={reloadFrequency}
key={keys?.current[9]}
flexGrow={1}
flexShrink={1}
w='100%'
params={allParams}
/>
</Box>
</Margins>
</PageScrollableContentWithShadow>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Box } from '@rocket.chat/fuselage';
import type { OperationParams } from '@rocket.chat/rest-typings';
import type { TranslationContextValue, TranslationKey } from '@rocket.chat/ui-contexts';
import { useTranslation } from '@rocket.chat/ui-contexts';
import { useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type { Chart as ChartType } from 'chart.js';
import type { MutableRefObject } from 'react';
import React, { useRef, useEffect } from 'react';
import type { ComponentProps, MutableRefObject } from 'react';
import React, { useRef, useEffect, useMemo, useState } from 'react';

import { drawDoughnutChart } from '../../../../../app/livechat/client/lib/chartHandler';
import { AsyncStatePhase } from '../../../../hooks/useAsyncState';
import { useEndpointData } from '../../../../hooks/useEndpointData';
import Chart from './Chart';
import { useUpdateChartData } from './useUpdateChartData';

Expand All @@ -31,14 +31,15 @@ const init = (canvas: HTMLCanvasElement, context: ChartType | undefined, t: Tran

type AgentStatusChartsProps = {
params: OperationParams<'GET', '/v1/livechat/analytics/dashboards/charts/agents-status'>;
reloadRef: MutableRefObject<{ [x: string]: () => void }>;
};
reloadFrequency: number;
} & ComponentProps<typeof Box>;

const AgentStatusChart = ({ params, reloadRef, ...props }: AgentStatusChartsProps) => {
const AgentStatusChart = ({ params, reloadFrequency, ...props }: AgentStatusChartsProps) => {
const t = useTranslation();

const canvas: MutableRefObject<HTMLCanvasElement | null> = useRef(null);
const context: MutableRefObject<ChartType | undefined> = useRef();
const [isInitialized, setIsInitialized] = useState(false);

const updateChartData = useUpdateChartData({
context,
Expand All @@ -47,29 +48,34 @@ const AgentStatusChart = ({ params, reloadRef, ...props }: AgentStatusChartsProp
init,
});

const { value: data, phase: state, reload } = useEndpointData('/v1/livechat/analytics/dashboards/charts/agents-status', { params });
const memoizedParams = useMemo(() => params, [params]);

const getChartData = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts/agents-status');

reloadRef.current.agentStatusChart = reload;
const { data, isLoading } = useQuery(['AgentStatusChart', memoizedParams], async () => getChartData(memoizedParams), {
refetchInterval: reloadFrequency * 1000,
});

const { offline = 0, available = 0, away = 0, busy = 0 } = data ?? initialData;

useEffect(() => {
const initChart = async () => {
if (canvas?.current) {
context.current = await init(canvas.current, context.current, t);
setIsInitialized(true);
}
};
initChart();
}, [t]);

useEffect(() => {
if (state === AsyncStatePhase.RESOLVED && context.current) {
if (!isLoading && isInitialized) {
updateChartData(t('Offline'), [offline]);
updateChartData(t('Available'), [available]);
updateChartData(t('Away'), [away]);
updateChartData(t('Busy'), [busy]);
}
}, [available, away, busy, offline, state, t, updateChartData]);
}, [context, available, away, busy, isLoading, offline, t, updateChartData, isInitialized]);

return <Chart canvasRef={canvas} {...props} />;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Box } from '@rocket.chat/fuselage';
import type { MutableRefObject } from 'react';
import type { ComponentProps, MutableRefObject } from 'react';
import React from 'react';

type ChartProps = { canvasRef: MutableRefObject<HTMLCanvasElement | null> };

type ChartProps = { canvasRef: MutableRefObject<HTMLCanvasElement | null> } & ComponentProps<typeof Box>;
const style = {
minHeight: '250px',
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { Box } from '@rocket.chat/fuselage';
import type { OperationParams } from '@rocket.chat/rest-typings';
import type { TranslationContextValue } from '@rocket.chat/ui-contexts';
import { useTranslation } from '@rocket.chat/ui-contexts';
import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type { Chart as ChartType } from 'chart.js';
import type { MutableRefObject } from 'react';
import React, { useRef, useEffect } from 'react';
import type { ComponentProps, MutableRefObject } from 'react';
import React, { useRef, useEffect, useMemo, useState } from 'react';

import { drawLineChart } from '../../../../../app/livechat/client/lib/chartHandler';
import { secondsToHHMMSS } from '../../../../../lib/utils/secondsToHHMMSS';
import { AsyncStatePhase } from '../../../../hooks/useAsyncState';
import { useEndpointData } from '../../../../hooks/useEndpointData';
import Chart from './Chart';
import { getMomentChartLabelsAndData } from './getMomentChartLabelsAndData';
import { getMomentCurrentLabel } from './getMomentCurrentLabel';
Expand Down Expand Up @@ -36,12 +36,19 @@ const init = (canvas: HTMLCanvasElement, context: ChartType | undefined, t: Tran
tooltipCallbacks,
});

const defaultData = {
chatDuration: {
avg: 0,
longest: 0,
},
};

type ChatDurationChartProps = {
params: OperationParams<'GET', '/v1/livechat/analytics/dashboards/charts/timings'>;
reloadRef: MutableRefObject<{ [x: string]: () => void }>;
};
reloadFrequency: number;
} & ComponentProps<typeof Box>;

const ChatDurationChart = ({ params, reloadRef, ...props }: ChatDurationChartProps) => {
const ChatDurationChart = ({ params, reloadFrequency, ...props }: ChatDurationChartProps) => {
const t = useTranslation();

const canvas: MutableRefObject<HTMLCanvasElement | null> = useRef(null);
Expand All @@ -54,34 +61,34 @@ const ChatDurationChart = ({ params, reloadRef, ...props }: ChatDurationChartPro
init,
});

const { value: data, phase: state, reload } = useEndpointData('/v1/livechat/analytics/dashboards/charts/timings', { params });
const [isInitialized, setIsInitialized] = useState(false);

reloadRef.current.chatDurationChart = reload;
const memoizedParams = useMemo(() => params, [params]);

const {
chatDuration: { avg, longest },
} = data ?? {
chatDuration: {
avg: 0,
longest: 0,
},
};
const getChartData = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts/timings');

const { data, isLoading } = useQuery(['ChatDurationChart', memoizedParams], async () => getChartData(memoizedParams), {
refetchInterval: reloadFrequency * 1000,
});

const { avg, longest } = data?.chatDuration ?? defaultData.chatDuration;

useEffect(() => {
const initChart = async () => {
if (canvas?.current) {
context.current = await init(canvas.current, context.current, t);
setIsInitialized(true);
}
};
initChart();
}, [t]);

useEffect(() => {
if (state === AsyncStatePhase.RESOLVED) {
if (!isLoading && isInitialized) {
const label = getMomentCurrentLabel();
updateChartData(label, [avg, longest]);
}
}, [avg, longest, state, t, updateChartData]);
}, [avg, data, isInitialized, isLoading, longest, t, updateChartData]);

return <Chart canvasRef={canvas} {...props} />;
};
Expand Down
Loading