Filter for live conversations
This commit is contained in:
@@ -6,8 +6,8 @@ class LiveReportsAPI extends ApiClient {
|
||||
super('live_reports', { accountScoped: true, apiVersion: 'v2' });
|
||||
}
|
||||
|
||||
getConversationMetric() {
|
||||
return axios.get(`${this.url}/conversation_metrics`);
|
||||
getConversationMetric(params = {}) {
|
||||
return axios.get(`${this.url}/conversation_metrics`, { params });
|
||||
}
|
||||
|
||||
getGroupedConversations({ groupBy } = { groupBy: 'assignee_id' }) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/* global axios */
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class SummaryReportsAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('summary_reports', { accountScoped: true, apiVersion: 'v2' });
|
||||
}
|
||||
|
||||
getTeamReports({ since, until, businessHours }) {
|
||||
return axios.get(`${this.url}/team`, {
|
||||
params: {
|
||||
since,
|
||||
until,
|
||||
business_hours: businessHours,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getAgentReports({ since, until, businessHours }) {
|
||||
return axios.get(`${this.url}/agents`, {
|
||||
params: {
|
||||
since,
|
||||
until,
|
||||
business_hours: businessHours,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new SummaryReportsAPI();
|
||||
@@ -1,28 +1,13 @@
|
||||
<template>
|
||||
<div class="flex-1 overflow-auto p-4">
|
||||
<div class="row">
|
||||
<div class="column small-12 medium-8 conversation-metric">
|
||||
<open-conversations />
|
||||
|
||||
<div class="column small-12 medium-4 flex">
|
||||
<metric-card
|
||||
:header="$t('OVERVIEW_REPORTS.ACCOUNT_CONVERSATIONS.HEADER')"
|
||||
:is-loading="uiFlags.isFetchingAccountConversationMetric"
|
||||
:loading-message="
|
||||
$t('OVERVIEW_REPORTS.ACCOUNT_CONVERSATIONS.LOADING_MESSAGE')
|
||||
"
|
||||
:header="$t('OVERVIEW_REPORTS.AGENT_STATUS.HEADER')"
|
||||
class="flex-1"
|
||||
>
|
||||
<div
|
||||
v-for="(metric, name, index) in conversationMetrics"
|
||||
:key="index"
|
||||
class="metric-content column"
|
||||
>
|
||||
<h3 class="heading">
|
||||
{{ name }}
|
||||
</h3>
|
||||
<p class="metric">{{ metric }}</p>
|
||||
</div>
|
||||
</metric-card>
|
||||
</div>
|
||||
<div class="column small-12 medium-4">
|
||||
<metric-card :header="$t('OVERVIEW_REPORTS.AGENT_STATUS.HEADER')">
|
||||
<div
|
||||
v-for="(metric, name, index) in agentStatusMetrics"
|
||||
:key="index"
|
||||
@@ -79,20 +64,22 @@
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import OpenConversations from './components/LiveReports/OpenConversations.vue';
|
||||
import AgentTable from './components/overview/AgentTable.vue';
|
||||
import TeamTable from './components/overview/TeamTable.vue';
|
||||
import MetricCard from './components/overview/MetricCard.vue';
|
||||
import { OVERVIEW_METRICS } from './constants';
|
||||
import ReportHeatmap from './components/Heatmap.vue';
|
||||
|
||||
import endOfDay from 'date-fns/endOfDay';
|
||||
import getUnixTime from 'date-fns/getUnixTime';
|
||||
import startOfDay from 'date-fns/startOfDay';
|
||||
import subDays from 'date-fns/subDays';
|
||||
import { OVERVIEW_METRICS } from './constants';
|
||||
|
||||
export default {
|
||||
name: 'LiveReports',
|
||||
components: {
|
||||
OpenConversations,
|
||||
TeamTable,
|
||||
AgentTable,
|
||||
MetricCard,
|
||||
@@ -108,7 +95,6 @@ export default {
|
||||
agentStatus: 'agents/getAgentStatus',
|
||||
agents: 'agents/getAgents',
|
||||
teams: 'teams/getTeams',
|
||||
accountConversationMetric: 'getAccountConversationMetric',
|
||||
agentConversationMetric: 'getAgentConversationMetric',
|
||||
teamConversationMetric: 'getTeamConversationMetric',
|
||||
accountConversationHeatmap: 'getAccountConversationHeatmapData',
|
||||
@@ -124,16 +110,6 @@ export default {
|
||||
});
|
||||
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.$store.dispatch('agents/get');
|
||||
@@ -146,7 +122,6 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
fetchAllData() {
|
||||
this.$store.dispatch('fetchLiveConversationMetric');
|
||||
this.$store.dispatch('fetchAgentConversationMetric');
|
||||
this.$store.dispatch('fetchTeamConversationMetric');
|
||||
this.fetchHeatmapData();
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
<template>
|
||||
<woot-reports
|
||||
key="team-reports"
|
||||
type="team"
|
||||
getter-key="teams/getTeams"
|
||||
action-key="teams/get"
|
||||
:download-button-label="$t('TEAM_REPORTS.DOWNLOAD_TEAM_REPORTS')"
|
||||
/>
|
||||
<div class="overflow-auto">
|
||||
<metric-card :is-live="false" header="Team wise reports" />
|
||||
<metric-card :is-live="false" header="Team wise reports">
|
||||
<woot-reports
|
||||
key="team-reports"
|
||||
class="!p-0"
|
||||
type="team"
|
||||
getter-key="teams/getTeams"
|
||||
action-key="teams/get"
|
||||
:download-button-label="$t('TEAM_REPORTS.DOWNLOAD_TEAM_REPORTS')"
|
||||
/>
|
||||
</metric-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WootReports from './components/WootReports.vue';
|
||||
import MetricCard from './components/overview/MetricCard.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WootReports,
|
||||
MetricCard,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { OVERVIEW_METRICS } from '../../constants';
|
||||
import MetricCard from '../overview/MetricCard.vue';
|
||||
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MultiselectDropdown,
|
||||
MetricCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedTeam: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
teams: 'teams/getTeams',
|
||||
accountConversationMetric: 'getAccountConversationMetric',
|
||||
uiFlags: 'getOverviewUIFlags',
|
||||
}),
|
||||
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.$store.dispatch('fetchLiveConversationMetric');
|
||||
},
|
||||
methods: {
|
||||
onSelectTeam(team) {
|
||||
this.$store.dispatch('fetchLiveConversationMetric', { team_id: team.id });
|
||||
this.selectedTeam = team;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="column small-12 medium-8 conversation-metric">
|
||||
<metric-card
|
||||
class="overflow-visible"
|
||||
:header="$t('OVERVIEW_REPORTS.ACCOUNT_CONVERSATIONS.HEADER')"
|
||||
:is-loading="uiFlags.isFetchingAccountConversationMetric"
|
||||
loading-message="Loading metrics"
|
||||
>
|
||||
<template #control>
|
||||
<multiselect-dropdown
|
||||
class="!mb-0 !w-1/2"
|
||||
:options="teams"
|
||||
:selected-item="selectedTeam"
|
||||
multiselector-title=""
|
||||
multiselector-placeholder="Select a team"
|
||||
no-search-result="No teams found"
|
||||
input-placeholder="Search for a team"
|
||||
:is-filter="true"
|
||||
@click="onSelectTeam"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
v-for="(metric, name, index) in conversationMetrics"
|
||||
:key="index"
|
||||
class="metric-content column"
|
||||
>
|
||||
<h3 class="heading">
|
||||
{{ name }}
|
||||
</h3>
|
||||
<p class="metric">{{ metric }}</p>
|
||||
</div>
|
||||
</metric-card>
|
||||
</div>
|
||||
</template>
|
||||
+12
-2
@@ -4,7 +4,7 @@
|
||||
<slot name="header">
|
||||
<div class="card-header--title-area">
|
||||
<h5>{{ header }}</h5>
|
||||
<span class="live">
|
||||
<span v-if="isLive" class="live">
|
||||
<span class="ellipse" /><span>{{
|
||||
$t('OVERVIEW_REPORTS.LIVE')
|
||||
}}</span>
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
<div v-if="!isLoading" class="card-body row">
|
||||
<div v-if="!isLoading" class="card-body row" :class="!isLive ? 'px-0' : ''">
|
||||
<slot />
|
||||
</div>
|
||||
<div v-else-if="isLoading" class="conversation-metric-loader">
|
||||
@@ -33,6 +33,10 @@ export default {
|
||||
Spinner,
|
||||
},
|
||||
props: {
|
||||
isLive: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
header: {
|
||||
type: String,
|
||||
default: '',
|
||||
@@ -45,6 +49,10 @@ export default {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isFilter: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -95,9 +103,11 @@ export default {
|
||||
.card-body {
|
||||
.metric-content {
|
||||
@apply pb-2;
|
||||
|
||||
.heading {
|
||||
@apply text-base text-slate-700 dark:text-slate-100;
|
||||
}
|
||||
|
||||
.metric {
|
||||
@apply text-woot-800 dark:text-woot-300 text-3xl mb-0 mt-1;
|
||||
}
|
||||
|
||||
@@ -131,10 +131,10 @@ export const actions = {
|
||||
commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, false);
|
||||
});
|
||||
},
|
||||
fetchLiveConversationMetric({ commit }) {
|
||||
fetchLiveConversationMetric({ commit }, params = {}) {
|
||||
commit(types.default.TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING, true);
|
||||
liveReports
|
||||
.getConversationMetric()
|
||||
.getConversationMetric(params)
|
||||
.then(accountConversationMetric => {
|
||||
commit(
|
||||
types.default.SET_ACCOUNT_CONVERSATION_METRIC,
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
||||
import types from '../mutation-types';
|
||||
import LabelsAPI from '../../api/labels';
|
||||
import AnalyticsHelper from '../../helper/AnalyticsHelper';
|
||||
import { LABEL_EVENTS } from '../../helper/AnalyticsHelper/events';
|
||||
|
||||
export const state = {
|
||||
teamSummaryReports: [],
|
||||
agentSummaryReports: [],
|
||||
uiFlags: {
|
||||
isFetching: false,
|
||||
isFetchingItem: false,
|
||||
isCreating: false,
|
||||
isDeleting: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const getters = {
|
||||
getAgentSummaryReport(_state) {
|
||||
return _state.agentSummaryReports;
|
||||
},
|
||||
getTeamSummaryReports(_state) {
|
||||
return _state.teamSummaryReports;
|
||||
},
|
||||
getLabelsOnSidebar(_state) {
|
||||
return _state.records
|
||||
.filter(record => record.show_on_sidebar)
|
||||
.sort((a, b) => a.title.localeCompare(b.title));
|
||||
},
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
revalidate: async function revalidate({ commit }, { newKey }) {
|
||||
try {
|
||||
const isExistingKeyValid = await LabelsAPI.validateCacheKey(newKey);
|
||||
if (!isExistingKeyValid) {
|
||||
const response = await LabelsAPI.refetchAndCommit(newKey);
|
||||
commit(types.SET_LABELS, response.data.payload);
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
}
|
||||
},
|
||||
|
||||
get: async function getLabels({ commit }) {
|
||||
commit(types.SET_LABEL_UI_FLAG, { isFetching: true });
|
||||
try {
|
||||
const response = await LabelsAPI.get(true);
|
||||
const sortedLabels = response.data.payload.sort((a, b) =>
|
||||
a.title.localeCompare(b.title)
|
||||
);
|
||||
commit(types.SET_LABELS, sortedLabels);
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
} finally {
|
||||
commit(types.SET_LABEL_UI_FLAG, { isFetching: false });
|
||||
}
|
||||
},
|
||||
|
||||
create: async function createLabels({ commit }, cannedObj) {
|
||||
commit(types.SET_LABEL_UI_FLAG, { isCreating: true });
|
||||
try {
|
||||
const response = await LabelsAPI.create(cannedObj);
|
||||
AnalyticsHelper.track(LABEL_EVENTS.CREATE);
|
||||
commit(types.ADD_LABEL, response.data);
|
||||
} catch (error) {
|
||||
const errorMessage = error?.response?.data?.message;
|
||||
throw new Error(errorMessage);
|
||||
} finally {
|
||||
commit(types.SET_LABEL_UI_FLAG, { isCreating: false });
|
||||
}
|
||||
},
|
||||
|
||||
update: async function updateLabels({ commit }, { id, ...updateObj }) {
|
||||
commit(types.SET_LABEL_UI_FLAG, { isUpdating: true });
|
||||
try {
|
||||
const response = await LabelsAPI.update(id, updateObj);
|
||||
AnalyticsHelper.track(LABEL_EVENTS.UPDATE);
|
||||
commit(types.EDIT_LABEL, response.data);
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
commit(types.SET_LABEL_UI_FLAG, { isUpdating: false });
|
||||
}
|
||||
},
|
||||
|
||||
delete: async function deleteLabels({ commit }, id) {
|
||||
commit(types.SET_LABEL_UI_FLAG, { isDeleting: true });
|
||||
try {
|
||||
await LabelsAPI.delete(id);
|
||||
AnalyticsHelper.track(LABEL_EVENTS.DELETED);
|
||||
commit(types.DELETE_LABEL, id);
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
commit(types.SET_LABEL_UI_FLAG, { isDeleting: false });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const mutations = {
|
||||
[types.SET_LABEL_UI_FLAG](_state, data) {
|
||||
_state.uiFlags = {
|
||||
..._state.uiFlags,
|
||||
...data,
|
||||
};
|
||||
},
|
||||
|
||||
[types.SET_LABELS]: MutationHelpers.set,
|
||||
[types.ADD_LABEL]: MutationHelpers.create,
|
||||
[types.EDIT_LABEL]: MutationHelpers.update,
|
||||
[types.DELETE_LABEL]: MutationHelpers.destroy,
|
||||
};
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
};
|
||||
Reference in New Issue
Block a user