* applied eslint * add locales and lint the project * removed error boundary * updated locales * fix min files * fix locales
332 lines
10 KiB
TypeScript
332 lines
10 KiB
TypeScript
import React from 'react';
|
|
import { Button, Typography, Tooltip } from 'antd';
|
|
import { Loader } from 'UI';
|
|
import {
|
|
generateListData,
|
|
defaultGraphs,
|
|
Graphs,
|
|
Member,
|
|
SessionsResponse,
|
|
PeriodKeys,
|
|
} from 'App/services/AssistStatsService';
|
|
import { FilePdfOutlined, ArrowUpOutlined } from '@ant-design/icons';
|
|
import Period, { LAST_24_HOURS } from 'Types/app/period';
|
|
import SelectDateRange from 'Shared/SelectDateRange/SelectDateRange';
|
|
import TeamMembers from 'Components/AssistStats/components/TeamMembers';
|
|
import { durationFromMsFormatted, formatTimeOrDate } from 'App/date';
|
|
import { exportCSVFile } from 'App/utils';
|
|
import { assistStatsService } from 'App/services';
|
|
|
|
import { getPdf2 } from 'Components/AssistStats/pdfGenerator';
|
|
import UserSearch from './components/UserSearch';
|
|
import Chart from './components/Charts';
|
|
import StatsTable from './components/Table';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { TFunction } from 'i18next';
|
|
|
|
const chartNames = (t: TFunction) => ({
|
|
assistTotal: t('Total Live Duration'),
|
|
assistAvg: t('Avg Live Duration'),
|
|
callTotal: t('Total Call Duration'),
|
|
callAvg: t('Avg Call Duration'),
|
|
controlTotal: t('Total Remote Duration'),
|
|
controlAvg: t('Avg Remote Duration'),
|
|
});
|
|
|
|
function calculatePercentageDelta(currP: number, prevP: number) {
|
|
return ((currP - prevP) / prevP) * 100;
|
|
}
|
|
|
|
function AssistStats() {
|
|
const { t } = useTranslation();
|
|
const [selectedUser, setSelectedUser] = React.useState<any>(null);
|
|
const [period, setPeriod] = React.useState<any>(
|
|
Period({ rangeName: LAST_24_HOURS }),
|
|
);
|
|
const [membersSort, setMembersSort] = React.useState('sessionsAssisted');
|
|
const [tableSort, setTableSort] = React.useState('timestamp');
|
|
const [topMembers, setTopMembers] = React.useState<{
|
|
list: Member[];
|
|
total: number;
|
|
}>({
|
|
list: [],
|
|
total: 0,
|
|
});
|
|
const [graphs, setGraphs] = React.useState<Graphs>(defaultGraphs);
|
|
const [sessions, setSessions] = React.useState<SessionsResponse>({
|
|
list: [],
|
|
total: 0,
|
|
page: 1,
|
|
});
|
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
const [page, setPage] = React.useState(1);
|
|
|
|
React.useEffect(() => {
|
|
void updateData();
|
|
}, []);
|
|
|
|
const onChangePeriod = async (period: any) => {
|
|
setPeriod(period);
|
|
void updateData(period);
|
|
};
|
|
|
|
const updateData = async (customPeriod?: any) => {
|
|
const usedP = customPeriod || period;
|
|
setIsLoading(true);
|
|
const topMembersPr = assistStatsService.getTopMembers({
|
|
startTimestamp: usedP.start,
|
|
endTimestamp: usedP.end,
|
|
userId: selectedUser || undefined,
|
|
sort: membersSort,
|
|
order: 'desc',
|
|
});
|
|
|
|
const graphsPr = assistStatsService.getGraphs(usedP);
|
|
const sessionsPr = assistStatsService.getSessions({
|
|
startTimestamp: usedP.start,
|
|
endTimestamp: usedP.end,
|
|
sort: tableSort,
|
|
order: 'desc',
|
|
userId: selectedUser || undefined,
|
|
page: 1,
|
|
limit: 10,
|
|
});
|
|
Promise.allSettled([topMembersPr, graphsPr, sessionsPr]).then(
|
|
([topMembers, graphs, sessions]) => {
|
|
topMembers.status === 'fulfilled' && setTopMembers(topMembers.value);
|
|
graphs.status === 'fulfilled' && setGraphs(graphs.value);
|
|
sessions.status === 'fulfilled' && setSessions(sessions.value);
|
|
},
|
|
);
|
|
setIsLoading(false);
|
|
};
|
|
|
|
const onPageChange = (page: number) => {
|
|
setPage(page);
|
|
assistStatsService
|
|
.getSessions({
|
|
startTimestamp: period.start,
|
|
endTimestamp: period.end,
|
|
sort: tableSort,
|
|
order: 'desc',
|
|
page,
|
|
limit: 10,
|
|
})
|
|
.then((sessions) => {
|
|
setSessions(sessions);
|
|
});
|
|
};
|
|
|
|
const onMembersSort = (sortBy: string) => {
|
|
setMembersSort(sortBy);
|
|
assistStatsService
|
|
.getTopMembers({
|
|
startTimestamp: period.start,
|
|
endTimestamp: period.end,
|
|
sort: sortBy,
|
|
order: 'desc',
|
|
})
|
|
.then((topMembers) => {
|
|
setTopMembers(topMembers);
|
|
});
|
|
};
|
|
|
|
const onTableSort = (sortBy: string) => {
|
|
setTableSort(sortBy);
|
|
assistStatsService
|
|
.getSessions({
|
|
startTimestamp: period.start,
|
|
endTimestamp: period.end,
|
|
sort: sortBy,
|
|
order: 'desc',
|
|
page: 1,
|
|
limit: 10,
|
|
})
|
|
.then((sessions) => {
|
|
setSessions(sessions);
|
|
});
|
|
};
|
|
|
|
const exportCSV = () => {
|
|
assistStatsService
|
|
.getSessions({
|
|
startTimestamp: period.start,
|
|
endTimestamp: period.end,
|
|
sort: tableSort,
|
|
order: 'desc',
|
|
page: 1,
|
|
limit: 10000,
|
|
})
|
|
.then((sessions) => {
|
|
const data = sessions.list.map((s) => ({
|
|
...s,
|
|
members: `"${s.teamMembers.map((m) => m.name).join(', ')}"`,
|
|
dateStr: `"${formatTimeOrDate(s.timestamp, undefined, true)}"`,
|
|
assistDuration: `"${durationFromMsFormatted(s.assistDuration)}"`,
|
|
callDuration: `"${durationFromMsFormatted(s.callDuration)}"`,
|
|
controlDuration: `"${durationFromMsFormatted(s.controlDuration)}"`,
|
|
}));
|
|
const headers = [
|
|
{ label: t('Date'), key: 'dateStr' },
|
|
{ label: t('Team Members'), key: 'members' },
|
|
{ label: t('Live Duration'), key: 'assistDuration' },
|
|
{ label: t('Call Duration'), key: 'callDuration' },
|
|
{ label: t('Remote Duration'), key: 'controlDuration' },
|
|
{ label: t('Session ID'), key: 'sessionId' },
|
|
];
|
|
|
|
exportCSVFile(
|
|
headers,
|
|
data,
|
|
`Assist_Stats_${new Date().toLocaleDateString()}`,
|
|
);
|
|
});
|
|
};
|
|
|
|
const onUserSelect = (id: any) => {
|
|
setSelectedUser(id);
|
|
setIsLoading(true);
|
|
const topMembersPr = assistStatsService.getTopMembers({
|
|
startTimestamp: period.start,
|
|
endTimestamp: period.end,
|
|
sort: membersSort,
|
|
userId: id,
|
|
order: 'desc',
|
|
});
|
|
|
|
const graphsPr = assistStatsService.getGraphs(period, id);
|
|
const sessionsPr = assistStatsService.getSessions({
|
|
startTimestamp: period.start,
|
|
endTimestamp: period.end,
|
|
sort: tableSort,
|
|
userId: id,
|
|
order: 'desc',
|
|
page: 1,
|
|
limit: 10,
|
|
});
|
|
|
|
Promise.allSettled([topMembersPr, graphsPr, sessionsPr]).then(
|
|
([topMembers, graphs, sessions]) => {
|
|
topMembers.status === 'fulfilled' && setTopMembers(topMembers.value);
|
|
graphs.status === 'fulfilled' && setGraphs(graphs.value);
|
|
sessions.status === 'fulfilled' && setSessions(sessions.value);
|
|
},
|
|
);
|
|
setIsLoading(false);
|
|
};
|
|
|
|
return (
|
|
<div className="w-full h-screen overflow-y-auto">
|
|
<div
|
|
className="mx-auto p-4 bg-white rounded border"
|
|
style={{ maxWidth: 1360 }}
|
|
id="pdf-anchor"
|
|
>
|
|
<div id="pdf-ignore" className="w-full flex items-center mb-2">
|
|
<Typography.Title style={{ marginBottom: 0 }} level={4}>
|
|
{t('Co-browsing Reports')}
|
|
</Typography.Title>
|
|
<div className="ml-auto flex items-center gap-2">
|
|
<UserSearch onUserSelect={onUserSelect} />
|
|
|
|
<SelectDateRange
|
|
period={period}
|
|
onChange={onChangePeriod}
|
|
right
|
|
isAnt
|
|
small
|
|
/>
|
|
<Tooltip
|
|
title={
|
|
!sessions || sessions.total === 0
|
|
? t('No data at the moment to export.')
|
|
: t('Export PDF')
|
|
}
|
|
>
|
|
<Button
|
|
onClick={getPdf2}
|
|
shape="default"
|
|
size="small"
|
|
disabled={!sessions || sessions.total === 0}
|
|
icon={<FilePdfOutlined rev={undefined} />}
|
|
/>
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
<div className="w-full grid grid-cols-3 gap-2 flex-2 col-span-2">
|
|
{Object.keys(graphs.currentPeriod).map((i: PeriodKeys) => (
|
|
<div className="bg-white rounded border">
|
|
<div className="pt-2 px-2">
|
|
<Typography.Text strong style={{ marginBottom: 0 }}>
|
|
{chartNames(t)[i]}
|
|
</Typography.Text>
|
|
<div className="flex gap-1 items-center">
|
|
<Typography.Title style={{ marginBottom: 0 }} level={5}>
|
|
{graphs.currentPeriod[i]
|
|
? durationFromMsFormatted(graphs.currentPeriod[i])
|
|
: null}
|
|
</Typography.Title>
|
|
{graphs.previousPeriod[i] ? (
|
|
<div
|
|
className={
|
|
graphs.currentPeriod[i] > graphs.previousPeriod[i]
|
|
? 'flex items-center gap-1 text-green'
|
|
: 'flex items-center gap-2 text-red'
|
|
}
|
|
>
|
|
<ArrowUpOutlined
|
|
rev={undefined}
|
|
rotate={
|
|
graphs.currentPeriod[i] > graphs.previousPeriod[i]
|
|
? 0
|
|
: 180
|
|
}
|
|
/>
|
|
{`${Math.round(
|
|
calculatePercentageDelta(
|
|
graphs.currentPeriod[i],
|
|
graphs.previousPeriod[i],
|
|
),
|
|
)}%`}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
<Loader
|
|
loading={isLoading}
|
|
style={{ minHeight: 90, height: 90 }}
|
|
size={36}
|
|
>
|
|
<Chart
|
|
data={generateListData(graphs.list, i)}
|
|
label={chartNames(t)[i]}
|
|
/>
|
|
</Loader>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="w-full mt-2">
|
|
<TeamMembers
|
|
isLoading={isLoading}
|
|
topMembers={topMembers}
|
|
onMembersSort={onMembersSort}
|
|
membersSort={membersSort}
|
|
/>
|
|
</div>
|
|
<div className="w-full mt-2">
|
|
<StatsTable
|
|
exportCSV={exportCSV}
|
|
sessions={sessions}
|
|
isLoading={isLoading}
|
|
onSort={onTableSort}
|
|
onPageChange={onPageChange}
|
|
page={page}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div id="stats-layer" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default AssistStats;
|