Add liveagentreports
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
/* global axios */
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class LiveReportsAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('live_reports', { accountScoped: true, apiVersion: 'v2' });
|
||||
}
|
||||
|
||||
getConversationMetric(params = {}) {
|
||||
return axios.get(`${this.url}/conversation_metrics`, { params });
|
||||
}
|
||||
|
||||
getGroupedConversations({ groupBy } = { groupBy: 'assignee_id' }) {
|
||||
return axios.get(`${this.url}/grouped_conversation_metrics`, {
|
||||
params: { group_by: groupBy },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new LiveReportsAPI();
|
||||
@@ -1,22 +1,20 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import AgentTable from './components/overview/AgentTable.vue';
|
||||
import MetricCard from './components/overview/MetricCard.vue';
|
||||
import { OVERVIEW_METRICS } from './constants';
|
||||
|
||||
import endOfDay from 'date-fns/endOfDay';
|
||||
import getUnixTime from 'date-fns/getUnixTime';
|
||||
import ReportHeader from './components/ReportHeader.vue';
|
||||
import HeatmapContainer from './components/HeatmapContainer.vue';
|
||||
import AgentLiveReportContainer from './components/AgentLiveReportContainer.vue';
|
||||
export const FETCH_INTERVAL = 60000;
|
||||
|
||||
export default {
|
||||
name: 'LiveReports',
|
||||
components: {
|
||||
ReportHeader,
|
||||
AgentTable,
|
||||
MetricCard,
|
||||
HeatmapContainer,
|
||||
AgentLiveReportContainer,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -28,9 +26,7 @@ export default {
|
||||
computed: {
|
||||
...mapGetters({
|
||||
agentStatus: 'agents/getAgentStatus',
|
||||
agents: 'agents/getAgents',
|
||||
accountConversationMetric: 'getAccountConversationMetric',
|
||||
agentConversationMetric: 'getAgentConversationMetric',
|
||||
uiFlags: 'getOverviewUIFlags',
|
||||
}),
|
||||
agentStatusMetrics() {
|
||||
@@ -55,7 +51,6 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('agents/get');
|
||||
this.initalizeReport();
|
||||
},
|
||||
beforeUnmount() {
|
||||
@@ -76,31 +71,12 @@ export default {
|
||||
},
|
||||
fetchAllData() {
|
||||
this.fetchAccountConversationMetric();
|
||||
this.fetchAgentConversationMetric();
|
||||
},
|
||||
downloadHeatmapData() {
|
||||
let to = endOfDay(new Date());
|
||||
|
||||
this.$store.dispatch('downloadAccountConversationHeatmap', {
|
||||
to: getUnixTime(to),
|
||||
});
|
||||
},
|
||||
|
||||
fetchAccountConversationMetric() {
|
||||
this.$store.dispatch('fetchAccountConversationMetric', {
|
||||
type: 'account',
|
||||
});
|
||||
},
|
||||
fetchAgentConversationMetric() {
|
||||
this.$store.dispatch('fetchAgentConversationMetric', {
|
||||
type: 'agent',
|
||||
page: this.pageIndex + 1,
|
||||
});
|
||||
},
|
||||
onPageNumberChange(pageIndex) {
|
||||
this.pageIndex = pageIndex;
|
||||
this.fetchAgentConversationMetric();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -115,9 +91,8 @@ export default {
|
||||
<MetricCard
|
||||
:header="$t('OVERVIEW_REPORTS.ACCOUNT_CONVERSATIONS.HEADER')"
|
||||
:is-loading="uiFlags.isFetchingAccountConversationMetric"
|
||||
:loading-message="
|
||||
$t('OVERVIEW_REPORTS.ACCOUNT_CONVERSATIONS.LOADING_MESSAGE')
|
||||
"
|
||||
:loading-message="$t('OVERVIEW_REPORTS.ACCOUNT_CONVERSATIONS.LOADING_MESSAGE')
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-for="(metric, name, index) in conversationMetrics"
|
||||
@@ -151,16 +126,6 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
<HeatmapContainer />
|
||||
<div class="flex flex-row flex-wrap max-w-full">
|
||||
<MetricCard :header="$t('OVERVIEW_REPORTS.AGENT_CONVERSATIONS.HEADER')">
|
||||
<AgentTable
|
||||
:agents="agents"
|
||||
:agent-metrics="agentConversationMetric"
|
||||
:page-index="pageIndex"
|
||||
:is-loading="uiFlags.isFetchingAgentConversationMetric"
|
||||
@page-change="onPageNumberChange"
|
||||
/>
|
||||
</MetricCard>
|
||||
</div>
|
||||
<AgentLiveReportContainer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
import AgentTable from './overview/AgentTable.vue';
|
||||
import MetricCard from './overview/MetricCard.vue';
|
||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||
import { useLiveRefresh } from 'dashboard/composables/useLiveRefresh';
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const uiFlags = useMapGetter('getOverviewUIFlags');
|
||||
const agentConversationMetric = useMapGetter('getAgentConversationMetric');
|
||||
const agents = useMapGetter('agents/getAgents');
|
||||
|
||||
const fetchData = () => store.dispatch('fetchAgentConversationMetric');
|
||||
|
||||
const { startRefetching } = useLiveRefresh(fetchData);
|
||||
|
||||
onMounted(() => {
|
||||
store.dispatch('agents/get');
|
||||
fetchData();
|
||||
startRefetching();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-row flex-wrap max-w-full">
|
||||
<MetricCard :header="$t('OVERVIEW_REPORTS.AGENT_CONVERSATIONS.HEADER')">
|
||||
<AgentTable
|
||||
:agents="agents"
|
||||
:agent-metrics="agentConversationMetric"
|
||||
:is-loading="uiFlags.isFetchingAgentConversationMetric"
|
||||
/>
|
||||
</MetricCard>
|
||||
</div>
|
||||
</template>
|
||||
+34
-100
@@ -4,6 +4,7 @@ import {
|
||||
useVueTable,
|
||||
createColumnHelper,
|
||||
getCoreRowModel,
|
||||
getPaginationRowModel,
|
||||
} from '@tanstack/vue-table';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -13,7 +14,7 @@ import Table from 'dashboard/components/table/Table.vue';
|
||||
import Pagination from 'dashboard/components/table/Pagination.vue';
|
||||
import AgentCell from './AgentCell.vue';
|
||||
|
||||
const { agents, agentMetrics, pageIndex } = defineProps({
|
||||
const { agents, agentMetrics } = defineProps({
|
||||
agents: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
@@ -26,36 +27,36 @@ const { agents, agentMetrics, pageIndex } = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
pageIndex: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['pageChange']);
|
||||
const { t } = useI18n();
|
||||
|
||||
function getAgentInformation(id) {
|
||||
return agents?.find(agent => agent.id === Number(id));
|
||||
}
|
||||
const getAgentMetrics = id =>
|
||||
agentMetrics.find(metrics => metrics.assignee_id === Number(id)) || {};
|
||||
|
||||
const totalCount = computed(() => agents.length);
|
||||
|
||||
const tableData = computed(() => {
|
||||
return agentMetrics
|
||||
.filter(agentMetric => getAgentInformation(agentMetric.id))
|
||||
const tableData = computed(() =>
|
||||
agents
|
||||
.map(agent => {
|
||||
const agentInformation = getAgentInformation(agent.id);
|
||||
const metric = getAgentMetrics(agent.id);
|
||||
return {
|
||||
agent: agentInformation.name || agentInformation.available_name,
|
||||
email: agentInformation.email,
|
||||
thumbnail: agentInformation.thumbnail,
|
||||
open: agent.metric.open ?? 0,
|
||||
unattended: agent.metric.unattended ?? 0,
|
||||
status: agentInformation.availability_status,
|
||||
agent: agent.available_name || agent.name,
|
||||
email: agent.email,
|
||||
thumbnail: agent.thumbnail,
|
||||
open: metric.open || 0,
|
||||
unattended: metric.unattended || 0,
|
||||
status: agent.availability_status,
|
||||
};
|
||||
});
|
||||
});
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// First sort by open tickets (descending)
|
||||
const openDiff = b.open - a.open;
|
||||
// If open tickets are equal, sort by name (ascending)
|
||||
if (openDiff === 0) {
|
||||
return a.agent.localeCompare(b.agent);
|
||||
}
|
||||
return openDiff;
|
||||
})
|
||||
);
|
||||
|
||||
const defaulSpanRender = cellProps =>
|
||||
h(
|
||||
@@ -86,100 +87,33 @@ const columns = [
|
||||
}),
|
||||
];
|
||||
|
||||
const paginationParams = computed(() => {
|
||||
return {
|
||||
pageIndex: pageIndex,
|
||||
pageSize: 25,
|
||||
};
|
||||
});
|
||||
|
||||
const table = useVueTable({
|
||||
get data() {
|
||||
return tableData.value;
|
||||
},
|
||||
columns,
|
||||
manualPagination: true,
|
||||
enableSorting: false,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
get rowCount() {
|
||||
return totalCount.value;
|
||||
},
|
||||
state: {
|
||||
get pagination() {
|
||||
return paginationParams.value;
|
||||
},
|
||||
},
|
||||
onPaginationChange: updater => {
|
||||
const newPagintaion = updater(paginationParams.value);
|
||||
emit('pageChange', newPagintaion.pageIndex);
|
||||
},
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="agent-table-container">
|
||||
<div class="flex flex-col flex-1">
|
||||
<Table :table="table" class="max-h-[calc(100vh-21.875rem)]" />
|
||||
<Pagination class="mt-2" :table="table" />
|
||||
<div v-if="isLoading" class="agents-loader">
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="items-center flex text-base justify-center p-8"
|
||||
>
|
||||
<Spinner />
|
||||
<span>{{
|
||||
$t('OVERVIEW_REPORTS.AGENT_CONVERSATIONS.LOADING_MESSAGE')
|
||||
}}</span>
|
||||
<span>
|
||||
{{ $t('OVERVIEW_REPORTS.AGENT_CONVERSATIONS.LOADING_MESSAGE') }}
|
||||
</span>
|
||||
</div>
|
||||
<EmptyState
|
||||
v-else-if="!isLoading && !agentMetrics.length"
|
||||
v-else-if="!isLoading && !agents.length"
|
||||
:title="$t('OVERVIEW_REPORTS.AGENT_CONVERSATIONS.NO_AGENTS')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.agent-table-container {
|
||||
@apply flex flex-col flex-1;
|
||||
|
||||
.ve-table {
|
||||
&::v-deep {
|
||||
th.ve-table-header-th {
|
||||
@apply text-sm rounded-xl;
|
||||
padding: var(--space-small) var(--space-two) !important;
|
||||
}
|
||||
|
||||
td.ve-table-body-td {
|
||||
padding: var(--space-one) var(--space-two) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&::v-deep .ve-pagination {
|
||||
@apply bg-transparent dark:bg-transparent;
|
||||
}
|
||||
|
||||
&::v-deep .ve-pagination-select {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.row-user-block {
|
||||
@apply items-center flex text-left;
|
||||
|
||||
.user-block {
|
||||
@apply items-start flex flex-col min-w-0 my-0 mx-2;
|
||||
|
||||
.title {
|
||||
@apply text-sm m-0 leading-[1.2] text-slate-800 dark:text-slate-100;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
@apply text-xs text-slate-600 dark:text-slate-200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-pagination {
|
||||
@apply mt-4 text-right;
|
||||
}
|
||||
}
|
||||
|
||||
.agents-loader {
|
||||
@apply items-center flex text-base justify-center p-8;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { downloadCsvFile, generateFileName } from '../../helper/downloadHelper';
|
||||
import AnalyticsHelper from '../../helper/AnalyticsHelper';
|
||||
import { REPORTS_EVENTS } from '../../helper/AnalyticsHelper/events';
|
||||
import { clampDataBetweenTimeline } from 'shared/helpers/ReportsDataHelper';
|
||||
import liveReports from '../../api/liveReports';
|
||||
|
||||
const state = {
|
||||
fetchingStatus: false,
|
||||
@@ -159,9 +160,10 @@ export const actions = {
|
||||
commit(types.default.TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING, false);
|
||||
});
|
||||
},
|
||||
fetchAgentConversationMetric({ commit }, reportObj) {
|
||||
fetchAgentConversationMetric({ commit }) {
|
||||
commit(types.default.TOGGLE_AGENT_CONVERSATION_METRIC_LOADING, true);
|
||||
Report.getConversationMetric(reportObj.type, reportObj.page)
|
||||
liveReports
|
||||
.getGroupedConversations({ groupBy: 'assignee_id' })
|
||||
.then(agentConversationMetric => {
|
||||
commit(
|
||||
types.default.SET_AGENT_CONVERSATION_METRIC,
|
||||
|
||||
Reference in New Issue
Block a user