Update Open Conversations

This commit is contained in:
Pranav
2025-02-05 20:59:08 -08:00
parent 3500b1a487
commit 401babb844
9 changed files with 172 additions and 132 deletions
@@ -29,6 +29,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
labelClass: {
type: String,
default: '',
},
});
const emit = defineEmits(['action']);
@@ -97,9 +101,13 @@ onMounted(() => {
</slot>
<Icon v-if="item.icon" :icon="item.icon" class="flex-shrink-0 size-3.5" />
<span v-if="item.emoji" class="flex-shrink-0">{{ item.emoji }}</span>
<span v-if="item.label" class="min-w-0 text-sm truncate">{{
item.label
}}</span>
<span
v-if="item.label"
class="min-w-0 text-sm truncate"
:class="labelClass"
>
{{ item.label }}
</span>
</button>
<div
v-if="filteredMenuItems.length === 0"
@@ -477,11 +477,12 @@
}
},
"TEAM_CONVERSATIONS": {
"ALL_TEAMS": "All Teams",
"HEADER": "Conversations by teams",
"LOADING_MESSAGE": "Loading team metrics...",
"NO_TEAMS": "There is no data available",
"TABLE_HEADER": {
"AGENT": "Team",
"TEAM": "Team",
"OPEN": "Open",
"UNATTENDED": "Unattended",
"STATUS": "Status"
@@ -1,132 +1,15 @@
<script>
import { mapGetters } from 'vuex';
import MetricCard from './components/overview/MetricCard.vue';
import { OVERVIEW_METRICS } from './constants';
<script setup>
import ReportHeader from './components/ReportHeader.vue';
import HeatmapContainer from './components/HeatmapContainer.vue';
import AgentLiveReportContainer from './components/AgentLiveReportContainer.vue';
import TeamLiveReportContainer from './components/TeamLiveReportContainer.vue';
export const FETCH_INTERVAL = 60000;
export default {
name: 'LiveReports',
components: {
ReportHeader,
MetricCard,
HeatmapContainer,
AgentLiveReportContainer,
TeamLiveReportContainer,
},
data() {
return {
// always start with 0, this is to manage the pagination in tanstack table
// when we send the data, we do a +1 to this value
pageIndex: 0,
};
},
computed: {
...mapGetters({
agentStatus: 'agents/getAgentStatus',
accountConversationMetric: 'getAccountConversationMetric',
uiFlags: 'getOverviewUIFlags',
}),
agentStatusMetrics() {
let metric = {};
Object.keys(this.agentStatus).forEach(key => {
const metricName = this.$t(
`OVERVIEW_REPORTS.AGENT_STATUS.${OVERVIEW_METRICS[key]}`
);
metric[metricName] = this.agentStatus[key];
});
return metric;
},
conversationMetrics() {
let metric = {};
Object.keys(this.accountConversationMetric).forEach(key => {
const metricName = this.$t(
`OVERVIEW_REPORTS.ACCOUNT_CONVERSATIONS.${OVERVIEW_METRICS[key]}`
);
metric[metricName] = this.accountConversationMetric[key];
});
return metric;
},
},
mounted() {
this.initalizeReport();
},
beforeUnmount() {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
},
methods: {
initalizeReport() {
this.fetchAllData();
this.scheduleReportRefresh();
},
scheduleReportRefresh() {
this.timeoutId = setTimeout(async () => {
await this.fetchAllData();
this.scheduleReportRefresh();
}, FETCH_INTERVAL);
},
fetchAllData() {
this.fetchAccountConversationMetric();
},
fetchAccountConversationMetric() {
this.$store.dispatch('fetchAccountConversationMetric', {
type: 'account',
});
},
},
};
import StatsLiveReportsContainer from './components/StatsLiveReportsContainer.vue';
</script>
<template>
<ReportHeader :header-title="$t('OVERVIEW_REPORTS.HEADER')" />
<div class="flex flex-col gap-4 pb-6">
<div class="flex flex-col items-center md:flex-row gap-4">
<div
class="flex-1 w-full max-w-full md:w-[65%] md:max-w-[65%] conversation-metric"
>
<MetricCard
:header="$t('OVERVIEW_REPORTS.ACCOUNT_CONVERSATIONS.HEADER')"
:is-loading="uiFlags.isFetchingAccountConversationMetric"
:loading-message="$t('OVERVIEW_REPORTS.ACCOUNT_CONVERSATIONS.LOADING_MESSAGE')
"
>
<div
v-for="(metric, name, index) in conversationMetrics"
:key="index"
class="flex-1 min-w-0 pb-2"
>
<h3 class="text-base text-n-slate-11">
{{ name }}
</h3>
<p class="text-n-slate-12 text-3xl mb-0 mt-1">
{{ metric }}
</p>
</div>
</MetricCard>
</div>
<div class="flex-1 w-full max-w-full md:w-[35%] md:max-w-[35%]">
<MetricCard :header="$t('OVERVIEW_REPORTS.AGENT_STATUS.HEADER')">
<div
v-for="(metric, name, index) in agentStatusMetrics"
:key="index"
class="flex-1 min-w-0 pb-2"
>
<h3 class="text-base text-n-slate-11">
{{ name }}
</h3>
<p class="text-n-slate-12 text-3xl mb-0 mt-1">
{{ metric }}
</p>
</div>
</MetricCard>
</div>
</div>
<StatsLiveReportsContainer />
<HeatmapContainer />
<AgentLiveReportContainer />
<TeamLiveReportContainer />
@@ -0,0 +1,142 @@
<script setup>
import { computed, onMounted, ref } from 'vue';
import { OVERVIEW_METRICS } from '../constants';
import { useToggle } from '@vueuse/core';
import MetricCard from './overview/MetricCard.vue';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useLiveRefresh } from 'dashboard/composables/useLiveRefresh';
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
import Button from 'dashboard/components-next/button/Button.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const uiFlags = useMapGetter('getOverviewUIFlags');
const agentStatus = useMapGetter('agents/getAgentStatus');
const accountConversationMetric = useMapGetter('getAccountConversationMetric');
const store = useStore();
const accounti18nKey = 'OVERVIEW_REPORTS.ACCOUNT_CONVERSATIONS';
const teams = useMapGetter('teams/getTeams');
const teamMenuList = computed(() => {
return [
{ label: t('OVERVIEW_REPORTS.TEAM_CONVERSATIONS.ALL_TEAMS'), value: null },
...teams.value.map(team => ({ label: team.name, value: team.id })),
];
});
const agentStatusMetrics = computed(() => {
let metric = {};
Object.keys(agentStatus.value).forEach(key => {
const metricName = t(
`OVERVIEW_REPORTS.AGENT_STATUS.${OVERVIEW_METRICS[key]}`
);
metric[metricName] = agentStatus.value[key];
});
return metric;
});
const conversationMetrics = computed(() => {
let metric = {};
Object.keys(accountConversationMetric.value).forEach(key => {
const metricName = t(`${accounti18nKey}.${OVERVIEW_METRICS[key]}`);
metric[metricName] = accountConversationMetric.value[key];
});
return metric;
});
const selectedTeam = ref(null);
const selectedTeamLabel = computed(() => {
const team =
teamMenuList.value.find(
menuItem => menuItem.value === selectedTeam.value
) || {};
return team.label;
});
const fetchData = () => {
const params = {};
if (selectedTeam.value) {
params.team_id = selectedTeam.value;
}
store.dispatch('fetchAccountConversationMetric', params);
};
const { startRefetching } = useLiveRefresh(fetchData);
const [showDropdown, toggleDropdown] = useToggle();
const handleAction = ({ value }) => {
toggleDropdown(false);
selectedTeam.value = value;
fetchData();
};
onMounted(() => {
fetchData();
startRefetching();
});
</script>
<template>
<div class="flex flex-col items-center md:flex-row gap-4">
<div
class="flex-1 w-full max-w-full md:w-[65%] md:max-w-[65%] conversation-metric"
>
<MetricCard
:header="t(`${accounti18nKey}.HEADER`)"
:is-loading="uiFlags.isFetchingAccountConversationMetric"
:loading-message="t(`${accounti18nKey}.LOADING_MESSAGE`)"
>
<template v-if="teams.length" #control>
<div
v-on-clickaway="() => toggleDropdown(false)"
class="relative flex items-center group z-50"
>
<Button
sm
slate
faded
:label="selectedTeamLabel"
class="rounded-md group-hover:bg-n-alpha-2"
@click="toggleDropdown()"
/>
<DropdownMenu
v-if="showDropdown"
:menu-items="teamMenuList"
class="mt-1 ltr:right-0 rtl:left-0 xl:ltr:right-0 xl:rtl:left-0 top-full"
label-class="capitalize"
@action="handleAction($event)"
/>
</div>
</template>
<div
v-for="(metric, name, index) in conversationMetrics"
:key="index"
class="flex-1 min-w-0 pb-2"
>
<h3 class="text-base text-n-slate-11">
{{ name }}
</h3>
<p class="text-n-slate-12 text-3xl mb-0 mt-1">
{{ metric }}
</p>
</div>
</MetricCard>
</div>
<div class="flex-1 w-full max-w-full md:w-[35%] md:max-w-[35%]">
<MetricCard :header="$t('OVERVIEW_REPORTS.AGENT_STATUS.HEADER')">
<div
v-for="(metric, name, index) in agentStatusMetrics"
:key="index"
class="flex-1 min-w-0 pb-2"
>
<h3 class="text-base text-n-slate-11">
{{ name }}
</h3>
<p class="text-n-slate-12 text-3xl mb-0 mt-1">
{{ metric }}
</p>
</div>
</MetricCard>
</div>
</div>
</template>
@@ -61,8 +61,11 @@ const tableData = computed(() =>
const defaulSpanRender = cellProps =>
h(
'span',
{
class: cellProps.getValue() ? '' : 'text-slate-300 dark:text-slate-700',
class: cellProps.getValue()
? 'capitalize text-n-slate-12'
: 'capitalize text-n-slate-11',
},
cellProps.getValue() ? cellProps.getValue() : '---'
);
@@ -19,7 +19,7 @@ defineProps({
<template>
<div
class="flex flex-col m-0.5 px-6 py-5 overflow-hidden rounded-xl flex-grow text-n-slate-12 shadow outline-1 outline outline-n-container bg-n-solid-2 min-h-[10rem]"
class="flex flex-col m-0.5 px-6 py-5 rounded-xl flex-grow text-n-slate-12 shadow outline-1 outline outline-n-container bg-n-solid-2 min-h-[10rem]"
>
<div
class="card-header grid w-full mb-6 grid-cols-[repeat(auto-fit,minmax(max-content,50%))] gap-y-2"
@@ -58,7 +58,9 @@ const defaulSpanRender = cellProps =>
h(
'span',
{
class: cellProps.getValue() ? '' : 'text-slate-300 dark:text-slate-700',
class: cellProps.getValue()
? 'capitalize text-n-slate-12'
: 'capitalize text-n-slate-11',
},
cellProps.getValue() ? cellProps.getValue() : '---'
);
@@ -66,9 +68,8 @@ const defaulSpanRender = cellProps =>
const columnHelper = createColumnHelper();
const columns = [
columnHelper.accessor('agent', {
header: t('OVERVIEW_REPORTS.TEAM_CONVERSATIONS.TABLE_HEADER.AGENT'),
header: t('OVERVIEW_REPORTS.TEAM_CONVERSATIONS.TABLE_HEADER.TEAM'),
cell: defaulSpanRender,
size: 250,
}),
columnHelper.accessor('open', {
@@ -151,9 +151,10 @@ export const actions = {
commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, false);
});
},
fetchAccountConversationMetric({ commit }, reportObj) {
fetchAccountConversationMetric({ commit }, params = {}) {
commit(types.default.TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING, true);
Report.getConversationMetric(reportObj.type)
liveReports
.getConversationMetric(params)
.then(accountConversationMetric => {
commit(
types.default.SET_ACCOUNT_CONVERSATION_METRIC,