Merge branch 'develop' into live-agent-reports-controller

This commit is contained in:
Pranav
2025-02-26 18:39:40 -08:00
834 changed files with 25762 additions and 9567 deletions
@@ -3,6 +3,12 @@ import { frontendURL } from 'dashboard/helper/URLHelper.js';
import CampaignsPageRouteView from './pages/CampaignsPageRouteView.vue';
import LiveChatCampaignsPage from './pages/LiveChatCampaignsPage.vue';
import SMSCampaignsPage from './pages/SMSCampaignsPage.vue';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
const meta = {
featureFlag: FEATURE_FLAGS.CAMPAIGNS,
permissions: ['administrator'],
};
const campaignsRoutes = {
routes: [
@@ -19,9 +25,7 @@ const campaignsRoutes = {
{
path: 'ongoing',
name: 'campaigns_ongoing_index',
meta: {
permissions: ['administrator'],
},
meta,
redirect: to => {
return { name: 'campaigns_livechat_index', params: to.params };
},
@@ -29,9 +33,7 @@ const campaignsRoutes = {
{
path: 'one_off',
name: 'campaigns_one_off_index',
meta: {
permissions: ['administrator'],
},
meta,
redirect: to => {
return { name: 'campaigns_sms_index', params: to.params };
},
@@ -39,17 +41,13 @@ const campaignsRoutes = {
{
path: 'live_chat',
name: 'campaigns_livechat_index',
meta: {
permissions: ['administrator'],
},
meta,
component: LiveChatCampaignsPage,
},
{
path: 'sms',
name: 'campaigns_sms_index',
meta: {
permissions: ['administrator'],
},
meta,
component: SMSCampaignsPage,
},
],
@@ -78,8 +78,8 @@ onMounted(() => store.dispatch('captainAssistants/get'));
:button-policy="['administrator']"
:show-pagination-footer="false"
:is-fetching="isFetching"
:feature-flag="FEATURE_FLAGS.CAPTAIN"
:is-empty="!assistants.length"
:feature-flag="FEATURE_FLAGS.CAPTAIN"
@click="handleCreate"
>
<template #emptyState>
@@ -72,8 +72,8 @@ onMounted(() =>
:button-policy="['administrator']"
:is-fetching="isFetchingAssistant || isFetching"
:is-empty="!captainInboxes.length"
:feature-flag="FEATURE_FLAGS.CAPTAIN"
:show-pagination-footer="false"
:feature-flag="FEATURE_FLAGS.CAPTAIN"
@click="handleCreate"
>
<template v-if="!isFetchingAssistant" #headerTitle>
@@ -1,4 +1,5 @@
// import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import { INSTALLATION_TYPES } from 'dashboard/constants/installationTypes';
import { frontendURL } from '../../../helper/URLHelper';
import AssistantIndex from './assistants/Index.vue';
import AssistantInboxesIndex from './assistants/inboxes/Index.vue';
@@ -12,6 +13,11 @@ export const routes = [
name: 'captain_assistants_index',
meta: {
permissions: ['administrator', 'agent'],
featureFlag: FEATURE_FLAGS.CAPTAIN,
installationTypes: [
INSTALLATION_TYPES.CLOUD,
INSTALLATION_TYPES.ENTERPRISE,
],
},
},
{
@@ -22,6 +28,11 @@ export const routes = [
name: 'captain_assistants_inboxes_index',
meta: {
permissions: ['administrator', 'agent'],
featureFlag: FEATURE_FLAGS.CAPTAIN,
installationTypes: [
INSTALLATION_TYPES.CLOUD,
INSTALLATION_TYPES.ENTERPRISE,
],
},
},
{
@@ -30,6 +41,11 @@ export const routes = [
name: 'captain_documents_index',
meta: {
permissions: ['administrator', 'agent'],
featureFlag: FEATURE_FLAGS.CAPTAIN,
installationTypes: [
INSTALLATION_TYPES.CLOUD,
INSTALLATION_TYPES.ENTERPRISE,
],
},
},
{
@@ -38,6 +54,11 @@ export const routes = [
name: 'captain_responses_index',
meta: {
permissions: ['administrator', 'agent'],
featureFlag: FEATURE_FLAGS.CAPTAIN,
installationTypes: [
INSTALLATION_TYPES.CLOUD,
INSTALLATION_TYPES.ENTERPRISE,
],
},
},
];
@@ -163,8 +163,8 @@ onMounted(() => {
:button-label="$t('CAPTAIN.RESPONSES.ADD_NEW')"
:is-fetching="isFetching"
:is-empty="!responses.length"
:feature-flag="FEATURE_FLAGS.CAPTAIN"
:show-pagination-footer="!isFetching && !!responses.length"
:feature-flag="FEATURE_FLAGS.CAPTAIN"
@update:current-page="onPageChange"
@click="handleCreate"
>
@@ -1,6 +1,7 @@
<script setup>
import { onMounted, computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useAlert } from 'dashboard/composables';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useRoute, useRouter } from 'vue-router';
@@ -25,6 +26,7 @@ const contactMergeRef = ref(null);
const isFetchingItem = computed(() => uiFlags.value.isFetchingItem);
const isMergingContact = computed(() => uiFlags.value.isMerging);
const isUpdatingContact = computed(() => uiFlags.value.isUpdating);
const selectedContact = computed(() => contact.value(route.params.contactId));
@@ -88,6 +90,33 @@ const fetchAttributes = () => {
store.dispatch('attributes/get');
};
const toggleContactBlock = async isBlocked => {
const ALERT_MESSAGES = {
success: {
block: t('CONTACTS_LAYOUT.HEADER.ACTIONS.BLOCK_SUCCESS_MESSAGE'),
unblock: t('CONTACTS_LAYOUT.HEADER.ACTIONS.UNBLOCK_SUCCESS_MESSAGE'),
},
error: {
block: t('CONTACTS_LAYOUT.HEADER.ACTIONS.BLOCK_ERROR_MESSAGE'),
unblock: t('CONTACTS_LAYOUT.HEADER.ACTIONS.UNBLOCK_ERROR_MESSAGE'),
},
};
try {
await store.dispatch(`contacts/update`, {
...selectedContact.value,
blocked: !isBlocked,
});
useAlert(
isBlocked ? ALERT_MESSAGES.success.unblock : ALERT_MESSAGES.success.block
);
} catch (error) {
useAlert(
isBlocked ? ALERT_MESSAGES.error.unblock : ALERT_MESSAGES.error.block
);
}
};
onMounted(() => {
fetchActiveContact();
fetchContactNotes();
@@ -105,7 +134,9 @@ onMounted(() => {
:selected-contact="selectedContact"
is-detail-view
:show-pagination-footer="false"
:is-updating="isUpdatingContact"
@go-to-contacts-list="goToContactsList"
@toggle-block="toggleContactBlock"
>
<div
v-if="showSpinner"
@@ -1,8 +1,10 @@
import { frontendURL } from '../../../helper/URLHelper';
import ContactsIndex from './pages/ContactsIndex.vue';
import ContactManageView from './pages/ContactManageView.vue';
import { FEATURE_FLAGS } from '../../../featureFlags';
const commonMeta = {
featureFlag: FEATURE_FLAGS.CRM,
permissions: ['administrator', 'agent', 'contact_manage'],
};
@@ -1,83 +1,113 @@
<script>
import { mapGetters } from 'vuex';
<script setup>
import { computed, onMounted, ref } from 'vue';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useAccount } from 'dashboard/composables/useAccount';
import { useUISettings } from 'dashboard/composables/useUISettings';
import Draggable from 'vuedraggable';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
import MacroItem from './MacroItem.vue';
export default {
components: {
MacroItem,
defineProps({
conversationId: {
type: [Number, String],
required: true,
},
props: {
conversationId: {
type: [Number, String],
required: true,
},
},
setup() {
const { accountScopedUrl } = useAccount();
});
return {
accountScopedUrl,
};
const store = useStore();
const { accountScopedUrl } = useAccount();
const { uiSettings, updateUISettings } = useUISettings();
const dragging = ref(false);
const macros = useMapGetter('macros/getMacros');
const uiFlags = useMapGetter('macros/getUIFlags');
const MACROS_ORDER_KEY = 'macros_display_order';
const orderedMacros = computed({
get: () => {
// Get saved order array and current macros
const savedOrder = uiSettings.value?.[MACROS_ORDER_KEY] ?? [];
const currentMacros = macros.value ?? [];
// Return unmodified macros if not present or macro is not available
if (!savedOrder.length || !currentMacros.length) {
return currentMacros;
}
// Create a Map of id -> position for faster lookups
const orderMap = new Map(savedOrder.map((id, index) => [id, index]));
return [...currentMacros].sort((a, b) => {
// Use Infinity for items not in saved order (pushes them to end)
const aPos = orderMap.get(a.id) ?? Infinity;
const bPos = orderMap.get(b.id) ?? Infinity;
return aPos - bPos;
});
},
computed: {
...mapGetters({
macros: ['macros/getMacros'],
uiFlags: 'macros/getUIFlags',
}),
},
mounted() {
this.$store.dispatch('macros/get');
set: newOrder => {
// Update settings with array of ids from new order
updateUISettings({
[MACROS_ORDER_KEY]: newOrder.map(({ id }) => id),
});
},
});
const onDragEnd = () => {
dragging.value = false;
};
onMounted(() => {
store.dispatch('macros/get');
});
</script>
<template>
<div>
<div
v-if="!uiFlags.isFetching && !macros.length"
class="macros_list--empty-state"
>
<div v-if="!uiFlags.isFetching && !macros.length" class="p-3">
<p class="flex flex-col items-center justify-center h-full">
{{ $t('MACROS.LIST.404') }}
</p>
<router-link :to="accountScopedUrl('settings/macros')">
<woot-button
variant="smooth"
icon="add"
size="tiny"
class="macros_add-button"
>
<woot-button variant="smooth" icon="add" size="tiny" class="mt-1">
{{ $t('MACROS.HEADER_BTN_TXT') }}
</woot-button>
</router-link>
</div>
<woot-loading-state
<div
v-if="uiFlags.isFetching"
:message="$t('MACROS.LOADING')"
/>
<div v-if="!uiFlags.isFetching && macros.length" class="macros-list">
<MacroItem
v-for="macro in macros"
:key="macro.id"
:macro="macro"
:conversation-id="conversationId"
/>
class="flex items-center gap-2 justify-center p-6 text-n-slate-12"
>
<span class="text-sm">{{ $t('MACROS.LOADING') }}</span>
<Spinner class="size-5" />
</div>
<Draggable
v-if="!uiFlags.isFetching && macros.length"
v-model="orderedMacros"
class="p-1"
animation="200"
ghost-class="ghost"
handle=".drag-handle"
item-key="id"
@start="dragging = true"
@end="onDragEnd"
>
<template #item="{ element }">
<MacroItem
:key="element.id"
:macro="element"
:conversation-id="conversationId"
class="drag-handle cursor-grab"
/>
</template>
</Draggable>
</div>
</template>
<style scoped lang="scss">
.macros-list {
padding: var(--space-smaller);
}
.macros_list--empty-state {
padding: var(--space-slab);
p {
margin: 0;
}
}
.macros_add-button {
margin: var(--space-small) auto 0;
.ghost {
@apply opacity-50 bg-n-slate-3 dark:bg-n-slate-9;
}
</style>
@@ -1,75 +1,78 @@
<script>
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useAlert } from 'dashboard/composables';
import MacroPreview from './MacroPreview.vue';
import { useStore } from 'dashboard/composables/store';
import { CONVERSATION_EVENTS } from '../../../../helper/AnalyticsHelper/events';
import { useTrack } from 'dashboard/composables';
export default {
components: {
MacroPreview,
import NextButton from 'dashboard/components-next/button/Button.vue';
import MacroPreview from './MacroPreview.vue';
const props = defineProps({
macro: {
type: Object,
required: true,
},
props: {
macro: {
type: Object,
required: true,
},
conversationId: {
type: [Number, String],
required: true,
},
},
data() {
return {
isExecuting: false,
showPreview: false,
};
},
methods: {
async executeMacro(macro) {
try {
this.isExecuting = true;
await this.$store.dispatch('macros/execute', {
macroId: macro.id,
conversationIds: [this.conversationId],
});
useTrack(CONVERSATION_EVENTS.EXECUTED_A_MACRO);
useAlert(this.$t('MACROS.EXECUTE.EXECUTED_SUCCESSFULLY'));
} catch (error) {
useAlert(this.$t('MACROS.ERROR'));
} finally {
this.isExecuting = false;
}
},
toggleMacroPreview() {
this.showPreview = !this.showPreview;
},
closeMacroPreview() {
this.showPreview = false;
},
conversationId: {
type: [Number, String],
required: true,
},
});
const store = useStore();
const { t } = useI18n();
const isExecuting = ref(false);
const showPreview = ref(false);
const executeMacro = async macro => {
try {
isExecuting.value = true;
await store.dispatch('macros/execute', {
macroId: macro.id,
conversationIds: [props.conversationId],
});
useTrack(CONVERSATION_EVENTS.EXECUTED_A_MACRO);
useAlert(t('MACROS.EXECUTE.EXECUTED_SUCCESSFULLY'));
} catch (error) {
useAlert(t('MACROS.ERROR'));
} finally {
isExecuting.value = false;
}
};
const toggleMacroPreview = () => {
showPreview.value = !showPreview.value;
};
const closeMacroPreview = () => {
showPreview.value = false;
};
</script>
<template>
<div class="macro button secondary clear">
<span class="overflow-hidden whitespace-nowrap text-ellipsis">{{
macro.name
}}</span>
<div class="flex items-center gap-1 macros-actions">
<woot-button
<div
class="relative flex items-center justify-between leading-4 rounded-md button secondary clear"
>
<span class="overflow-hidden whitespace-nowrap text-ellipsis">
{{ macro.name }}
</span>
<div class="flex items-center gap-1 justify-end">
<NextButton
v-tooltip.left-start="$t('MACROS.EXECUTE.PREVIEW')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="info"
@click="toggleMacroPreview(macro)"
icon="i-lucide-info"
slate
faded
xs
@click="toggleMacroPreview"
/>
<woot-button
<NextButton
v-tooltip.left-start="$t('MACROS.EXECUTE.BUTTON_TOOLTIP')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="play-circle"
icon="i-lucide-play"
slate
faded
xs
:is-loading="isExecuting"
@click="executeMacro(macro)"
/>
@@ -83,13 +86,3 @@ export default {
</transition>
</div>
</template>
<style scoped lang="scss">
.macro {
@apply relative flex items-center justify-between leading-4 rounded-md;
.macros-actions {
@apply flex items-center justify-end;
}
}
</style>
@@ -1,57 +1,48 @@
<script>
<script setup>
import { computed } from 'vue';
import { useMapGetter } from 'dashboard/composables/store.js';
import {
resolveActionName,
resolveTeamIds,
resolveLabels,
resolveAgents,
} from 'dashboard/routes/dashboard/settings/macros/macroHelper';
import { mapGetters } from 'vuex';
export default {
props: {
macro: {
type: Object,
required: true,
},
},
computed: {
resolvedMacro() {
return this.macro.actions.map(action => {
return {
actionName: resolveActionName(action.action_name),
actionValue: this.getActionValue(
action.action_name,
action.action_params
),
};
});
},
...mapGetters({
labels: 'labels/getLabels',
teams: 'teams/getTeams',
agents: 'agents/getAgents',
}),
},
methods: {
getActionValue(key, params) {
const actionsMap = {
assign_team: resolveTeamIds(this.teams, params),
add_label: resolveLabels(this.labels, params),
remove_label: resolveLabels(this.labels, params),
assign_agent: resolveAgents(this.agents, params),
mute_conversation: null,
snooze_conversation: null,
resolve_conversation: null,
remove_assigned_team: null,
send_webhook_event: params[0],
send_message: params[0],
send_email_transcript: params[0],
add_private_note: params[0],
};
return actionsMap[key] || '';
},
const props = defineProps({
macro: {
type: Object,
required: true,
},
});
const labels = useMapGetter('labels/getLabels');
const teams = useMapGetter('teams/getTeams');
const agents = useMapGetter('agents/getAgents');
const getActionValue = (key, params) => {
const actionsMap = {
assign_team: resolveTeamIds(teams.value, params),
add_label: resolveLabels(labels.value, params),
remove_label: resolveLabels(labels.value, params),
assign_agent: resolveAgents(agents.value, params),
mute_conversation: null,
snooze_conversation: null,
resolve_conversation: null,
remove_assigned_team: null,
send_webhook_event: params[0],
send_message: params[0],
send_email_transcript: params[0],
add_private_note: params[0],
};
return actionsMap[key] || '';
};
const resolvedMacro = computed(() => {
return props.macro.actions.map(action => ({
actionName: resolveActionName(action.action_name),
actionValue: getActionValue(action.action_name, action.action_params),
}));
});
</script>
<template>
@@ -1,3 +1,4 @@
import { FEATURE_FLAGS } from '../../../featureFlags';
import { getPortalRoute } from './helpers/routeHelper';
import HelpCenterPageRouteView from './pages/HelpCenterPageRouteView.vue';
@@ -21,21 +22,21 @@ const PortalsLocalesIndexPage = () =>
const PortalsSettingsIndexPage = () =>
import('./pages/PortalsSettingsIndexPage.vue');
const meta = {
featureFlag: FEATURE_FLAGS.HELP_CENTER,
permissions: ['administrator', 'agent', 'knowledge_base_manage'],
};
const portalRoutes = [
{
path: getPortalRoute(':portalSlug/:locale/:categorySlug?/articles/:tab?'),
name: 'portals_articles_index',
meta: {
permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
meta,
component: PortalsArticlesIndexPage,
},
{
path: getPortalRoute(':portalSlug/:locale/:categorySlug?/articles/new'),
name: 'portals_articles_new',
meta: {
permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
meta,
component: PortalsArticlesNewPage,
},
{
@@ -43,18 +44,14 @@ const portalRoutes = [
':portalSlug/:locale/:categorySlug?/articles/:tab?/edit/:articleSlug'
),
name: 'portals_articles_edit',
meta: {
permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
meta,
component: PortalsArticlesEditPage,
},
{
path: getPortalRoute(':portalSlug/:locale/categories'),
name: 'portals_categories_index',
meta: {
permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
meta,
component: PortalsCategoriesIndexPage,
},
{
@@ -62,9 +59,7 @@ const portalRoutes = [
':portalSlug/:locale/categories/:categorySlug/articles'
),
name: 'portals_categories_articles_index',
meta: {
permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
meta,
component: PortalsArticlesIndexPage,
},
{
@@ -72,31 +67,26 @@ const portalRoutes = [
':portalSlug/:locale/categories/:categorySlug/articles/:articleSlug'
),
name: 'portals_categories_articles_edit',
meta: {
permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
meta,
component: PortalsArticlesEditPage,
},
{
path: getPortalRoute(':portalSlug/locales'),
name: 'portals_locales_index',
meta: {
permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
meta,
component: PortalsLocalesIndexPage,
},
{
path: getPortalRoute(':portalSlug/settings'),
name: 'portals_settings_index',
meta: {
permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
meta,
component: PortalsSettingsIndexPage,
},
{
path: getPortalRoute('new'),
name: 'portals_new',
meta: {
featureFlag: FEATURE_FLAGS.HELP_CENTER,
permissions: ['administrator', 'knowledge_base_manage'],
},
component: PortalsNew,
@@ -105,6 +95,7 @@ const portalRoutes = [
path: getPortalRoute(':navigationPath'),
name: 'portals_index',
meta: {
featureFlag: FEATURE_FLAGS.HELP_CENTER,
permissions: ['administrator', 'knowledge_base_manage'],
},
component: PortalsIndex,
@@ -115,12 +115,12 @@ export default {
<template>
<div
class="flex flex-col bg-n-alpha-3 backdrop-blur-[100px] border-0 outline outline-1 outline-n-container shadow-lg z-50 w-[170px] rounded-xl divide-y divide-n-weak dark:divide-n-strong"
class="flex flex-col bg-n-alpha-3 backdrop-blur-[100px] border-0 outline outline-1 outline-n-container shadow-lg z-50 max-w-64 min-w-[170px] w-fit rounded-xl divide-y divide-n-weak dark:divide-n-strong"
>
<div class="flex items-center justify-between p-3 rounded-t-lg h-11">
<div class="flex gap-1.5">
<div class="flex items-center gap-2 justify-between p-3 rounded-t-lg h-11">
<div class="flex gap-1.5 min-w-0">
<span class="i-lucide-arrow-down-up size-3.5 text-n-slate-12" />
<span class="text-xs font-medium text-n-slate-12">
<span class="text-xs font-medium text-n-slate-12 truncate min-w-0">
{{ $t('INBOX.DISPLAY_MENU.SORT') }}
</span>
</div>
@@ -132,25 +132,25 @@ export default {
trailing-icon
xs
outline
class="w-20"
class="w-fit min-w-20 max-w-32"
@click="openSortMenu"
/>
<div
v-if="showSortMenu"
class="absolute flex flex-col gap-0.5 bg-n-alpha-3 backdrop-blur-[100px] z-60 rounded-lg p-0.5 w-20 top-px outline outline-1 outline-n-container dark:outline-n-strong"
class="absolute flex flex-col gap-0.5 bg-n-alpha-3 backdrop-blur-[100px] z-60 rounded-lg p-0.5 w-fit min-w-20 max-w-32 top-px outline outline-1 outline-n-container dark:outline-n-strong"
>
<div
v-for="option in sortOptions"
:key="option.key"
role="button"
class="flex rounded-md h-5 w-full items-center justify-between px-1.5 py-0.5 gap-1"
class="flex rounded-md h-5 w-full items-center justify-between px-1.5 py-0.5 gap-2 whitespace-nowrap"
:class="{
'bg-n-brand/10 dark:bg-n-brand/10': activeSort === option.key,
}"
@click.stop="onSortOptionClick(option)"
>
<span
class="text-xs font-medium hover:text-n-brand dark:hover:text-n-brand"
class="text-xs font-medium hover:text-n-brand truncate min-w-0 dark:hover:text-n-brand"
:class="{
'text-n-blue-text dark:text-n-blue-text':
activeSort === option.key,
@@ -161,7 +161,7 @@ export default {
</span>
<span
v-if="activeSort === option.key"
class="i-lucide-check size-2.5 text-n-blue-text"
class="i-lucide-check size-2.5 flex-shrink-0 text-n-blue-text"
/>
</div>
</div>
@@ -113,7 +113,7 @@ export default {
<InboxOptionMenu
v-if="showInboxOptionMenu"
v-on-clickaway="openInboxOptionsMenu"
class="absolute top-full mt-1 ltr:right-0 ltr:lg:right-[unset] rtl:left-0 rtl:md:left-[unset]"
class="absolute top-full mt-1 ltr:right-0 ltr:lg:right-[unset] rtl:left-0 rtl:lg:left-[unset]"
@option-click="onInboxOptionMenuClick"
/>
</div>
@@ -33,7 +33,7 @@ export default {
<template>
<div
class="z-50 flex flex-col w-40 gap-1 bg-n-alpha-3 backdrop-blur-[100px] divide-y py-2 px-2 outline outline-1 outline-n-container shadow-lg rounded-xl divide-n-weak dark:divide-n-strong"
class="z-50 flex flex-col max-w-64 min-w-40 gap-1 bg-n-alpha-3 backdrop-blur-[100px] divide-y py-2 px-2 outline outline-1 outline-n-container shadow-lg rounded-xl divide-n-weak dark:divide-n-strong"
>
<div class="flex flex-col">
<MenuItem
@@ -10,8 +10,10 @@ defineProps({
<template>
<div
role="button"
class="flex items-center w-full h-8 px-2 py-1 overflow-hidden text-xs font-medium rounded-md cursor-pointer text-n-slate-12 whitespace-nowrap text-ellipsis hover:text-n-blue-text"
class="flex items-center w-full h-8 px-2 py-1 rounded-md cursor-pointer hover:text-n-blue-text min-w-0"
>
{{ label }}
<span class="text-xs font-medium truncate text-n-slate-12">
{{ label }}
</span>
</div>
</template>
@@ -1,3 +1,4 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import Bot from './Index.vue';
import CsmlEditBot from './csml/Edit.vue';
import CsmlNewBot from './csml/New.vue';
@@ -23,6 +24,7 @@ export default {
name: 'agent_bots',
component: Bot,
meta: {
featureFlag: FEATURE_FLAGS.AGENT_BOTS,
permissions: ['administrator'],
},
},
@@ -31,6 +33,7 @@ export default {
name: 'agent_bots_csml_new',
component: CsmlNewBot,
meta: {
featureFlag: FEATURE_FLAGS.AGENT_BOTS,
permissions: ['administrator'],
},
},
@@ -39,6 +42,7 @@ export default {
name: 'agent_bots_csml_edit',
component: CsmlEditBot,
meta: {
featureFlag: FEATURE_FLAGS.AGENT_BOTS,
permissions: ['administrator'],
},
},
@@ -1,3 +1,4 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { frontendURL } from '../../../../helper/URLHelper';
import SettingsWrapper from '../SettingsWrapper.vue';
import AgentHome from './Index.vue';
@@ -19,6 +20,7 @@ export default {
name: 'agent_list',
component: AgentHome,
meta: {
featureFlag: FEATURE_FLAGS.AGENT_MANAGEMENT,
permissions: ['administrator'],
},
},
@@ -1,3 +1,4 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { frontendURL } from '../../../../helper/URLHelper';
import SettingsWrapper from '../SettingsWrapper.vue';
import AttributesHome from './Index.vue';
@@ -19,6 +20,7 @@ export default {
name: 'attributes_list',
component: AttributesHome,
meta: {
featureFlag: FEATURE_FLAGS.CUSTOM_ATTRIBUTES,
permissions: ['administrator'],
},
},
@@ -1,3 +1,5 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { INSTALLATION_TYPES } from 'dashboard/constants/installationTypes';
import { frontendURL } from '../../../../helper/URLHelper';
import SettingsWrapper from '../SettingsWrapper.vue';
@@ -19,6 +21,11 @@ export default {
path: 'list',
name: 'auditlogs_list',
meta: {
featureFlag: FEATURE_FLAGS.AUDIT_LOGS,
installationTypes: [
INSTALLATION_TYPES.CLOUD,
INSTALLATION_TYPES.ENTERPRISE,
],
permissions: ['administrator'],
},
component: AuditLogsHome,
@@ -1,3 +1,4 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { frontendURL } from '../../../../helper/URLHelper';
import SettingsWrapper from '../SettingsWrapper.vue';
import Automation from './Index.vue';
@@ -19,6 +20,7 @@ export default {
name: 'automation_list',
component: Automation,
meta: {
featureFlag: FEATURE_FLAGS.AUTOMATIONS,
permissions: ['administrator'],
},
},
@@ -1,4 +1,5 @@
import { frontendURL } from '../../../../helper/URLHelper';
import { INSTALLATION_TYPES } from 'dashboard/constants/installationTypes';
import SettingsWrapper from '../SettingsWrapper.vue';
import Index from './Index.vue';
@@ -8,6 +9,7 @@ export default {
path: frontendURL('accounts/:accountId/settings/billing'),
meta: {
permissions: ['administrator'],
installationTypes: [INSTALLATION_TYPES.CLOUD],
},
component: SettingsWrapper,
props: {
@@ -21,6 +23,7 @@ export default {
name: 'billing_settings_index',
component: Index,
meta: {
installationTypes: [INSTALLATION_TYPES.CLOUD],
permissions: ['administrator'],
},
},
@@ -1,3 +1,4 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { frontendURL } from '../../../../helper/URLHelper';
import {
ROLES,
@@ -22,6 +23,7 @@ export default {
path: 'list',
name: 'canned_list',
meta: {
featureFlag: FEATURE_FLAGS.CANNED_RESPONSES,
permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
},
component: CannedHome,
@@ -1,3 +1,5 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { INSTALLATION_TYPES } from 'dashboard/constants/installationTypes';
import { frontendURL } from 'dashboard/helper/URLHelper';
import SettingsWrapper from '../SettingsWrapper.vue';
@@ -17,6 +19,11 @@ export default {
path: 'list',
name: 'custom_roles_list',
meta: {
featureFlag: FEATURE_FLAGS.CUSTOM_ROLES,
installationTypes: [
INSTALLATION_TYPES.CLOUD,
INSTALLATION_TYPES.ENTERPRISE,
],
permissions: ['administrator'],
},
component: CustomRolesHome,
@@ -1,3 +1,4 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { frontendURL } from '../../../../helper/URLHelper';
import ChannelFactory from './ChannelFactory.vue';
@@ -27,6 +28,7 @@ export default {
name: 'settings_inbox_list',
component: InboxHome,
meta: {
featureFlag: FEATURE_FLAGS.INBOX_MANAGEMENT,
permissions: ['administrator'],
},
},
@@ -55,6 +57,7 @@ export default {
name: 'settings_inbox_new',
component: ChannelList,
meta: {
featureFlag: FEATURE_FLAGS.INBOX_MANAGEMENT,
permissions: ['administrator'],
},
},
@@ -63,6 +66,7 @@ export default {
name: 'settings_inbox_finish',
component: FinishSetup,
meta: {
featureFlag: FEATURE_FLAGS.INBOX_MANAGEMENT,
permissions: ['administrator'],
},
},
@@ -71,6 +75,7 @@ export default {
name: 'settings_inboxes_page_channel',
component: ChannelFactory,
meta: {
featureFlag: FEATURE_FLAGS.INBOX_MANAGEMENT,
permissions: ['administrator'],
},
props: route => {
@@ -81,6 +86,7 @@ export default {
path: ':inbox_id/agents',
name: 'settings_inboxes_add_agents',
meta: {
featureFlag: FEATURE_FLAGS.INBOX_MANAGEMENT,
permissions: ['administrator'],
},
component: AddAgents,
@@ -92,6 +98,7 @@ export default {
name: 'settings_inbox_show',
component: Settings,
meta: {
featureFlag: FEATURE_FLAGS.INBOX_MANAGEMENT,
permissions: ['administrator'],
},
},
@@ -1,3 +1,4 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { frontendURL } from '../../../../helper/URLHelper';
import SettingsWrapper from '../SettingsWrapper.vue';
import IntegrationHooks from './IntegrationHooks.vue';
@@ -19,6 +20,7 @@ export default {
name: 'settings_applications',
component: Index,
meta: {
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
permissions: ['administrator'],
},
},
@@ -27,6 +29,7 @@ export default {
component: DashboardApps,
name: 'settings_integrations_dashboard_apps',
meta: {
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
permissions: ['administrator'],
},
},
@@ -35,6 +38,7 @@ export default {
component: Webhook,
name: 'settings_integrations_webhook',
meta: {
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
permissions: ['administrator'],
},
},
@@ -62,6 +66,7 @@ export default {
name: 'settings_integrations_slack',
component: Slack,
meta: {
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
permissions: ['administrator'],
},
props: route => ({ code: route.query.code }),
@@ -71,6 +76,7 @@ export default {
name: 'settings_applications_integration',
component: IntegrationHooks,
meta: {
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
permissions: ['administrator'],
},
props: route => ({
@@ -1,3 +1,4 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { frontendURL } from '../../../../helper/URLHelper';
import SettingsWrapper from '../SettingsWrapper.vue';
@@ -23,6 +24,7 @@ export default {
path: 'list',
name: 'labels_list',
meta: {
featureFlag: FEATURE_FLAGS.LABELS,
permissions: ['administrator'],
},
component: Index,
@@ -1,3 +1,4 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { frontendURL } from 'dashboard/helper/URLHelper';
import {
@@ -20,6 +21,7 @@ export default {
name: 'macros_wrapper',
component: Macros,
meta: {
featureFlag: FEATURE_FLAGS.MACROS,
permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
},
},
@@ -41,6 +43,7 @@ export default {
name: 'macros_edit',
component: MacroEditor,
meta: {
featureFlag: FEATURE_FLAGS.MACROS,
permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
},
},
@@ -49,6 +52,7 @@ export default {
name: 'macros_new',
component: MacroEditor,
meta: {
featureFlag: FEATURE_FLAGS.MACROS,
permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
},
},
@@ -112,22 +112,28 @@ export default {
};
},
getChartOptions(metric) {
let tooltips = {};
const options = {
scales: METRIC_CHART[metric.KEY].scales,
};
// Only add tooltip configuration for time-based metrics
if (this.isAverageMetricType(metric.KEY)) {
tooltips.callbacks = {
label: tooltipItem => {
return this.$t(metric.TOOLTIP_TEXT, {
metricValue: formatTime(tooltipItem.yLabel),
conversationCount:
this.accountReport.data[metric.KEY][tooltipItem.index].count,
});
options.plugins = {
tooltip: {
callbacks: {
label: ({ raw, dataIndex }) => {
return this.$t(metric.TOOLTIP_TEXT, {
metricValue: formatTime(raw || 0),
conversationCount:
this.accountReport.data[metric.KEY][dataIndex]?.count || 0,
});
},
},
},
};
}
return {
scales: METRIC_CHART[metric.KEY].scales,
tooltips: tooltips,
};
return options;
},
},
};
@@ -22,37 +22,34 @@ import BotReports from './BotReports.vue';
import LiveReports from './LiveReports.vue';
import SLAReports from './SLAReports.vue';
const meta = {
featureFlag: FEATURE_FLAGS.REPORTS,
permissions: ['administrator', 'report_manage'],
};
const oldReportRoutes = [
{
path: 'agent',
name: 'agent_reports',
meta: {
permissions: ['administrator', 'report_manage'],
},
meta,
component: AgentReports,
},
{
path: 'inboxes',
name: 'inbox_reports',
meta: {
permissions: ['administrator', 'report_manage'],
},
meta,
component: InboxReports,
},
{
path: 'label',
name: 'label_reports',
meta: {
permissions: ['administrator', 'report_manage'],
},
meta,
component: LabelReports,
},
{
path: 'teams',
name: 'team_reports',
meta: {
permissions: ['administrator', 'report_manage'],
},
meta,
component: TeamReports,
},
];
@@ -124,17 +121,13 @@ export default {
{
path: 'overview',
name: 'account_overview_reports',
meta: {
permissions: ['administrator', 'report_manage'],
},
meta,
component: LiveReports,
},
{
path: 'conversation',
name: 'conversation_reports',
meta: {
permissions: ['administrator', 'report_manage'],
},
meta,
component: Index,
},
...oldReportRoutes,
@@ -142,26 +135,19 @@ export default {
{
path: 'sla',
name: 'sla_reports',
meta: {
permissions: ['administrator', 'report_manage'],
featureFlag: FEATURE_FLAGS.SLA,
},
meta,
component: SLAReports,
},
{
path: 'csat',
name: 'csat_reports',
meta: {
permissions: ['administrator', 'report_manage'],
},
meta,
component: CsatResponses,
},
{
path: 'bot',
name: 'bot_reports',
meta: {
permissions: ['administrator', 'report_manage'],
},
meta,
component: BotReports,
},
],
@@ -1,8 +1,16 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { INSTALLATION_TYPES } from 'dashboard/constants/installationTypes';
import { frontendURL } from '../../../../helper/URLHelper';
import SettingsWrapper from '../SettingsWrapper.vue';
import Index from './Index.vue';
const meta = {
featureFlag: FEATURE_FLAGS.SLA,
permissions: ['administrator'],
installationTypes: [INSTALLATION_TYPES.CLOUD, INSTALLATION_TYPES.ENTERPRISE],
};
export default {
routes: [
{
@@ -13,9 +21,7 @@ export default {
{
path: '',
name: 'sla_wrapper',
meta: {
permissions: ['administrator'],
},
meta,
redirect: to => {
return { name: 'sla_list', params: to.params };
},
@@ -23,9 +29,7 @@ export default {
{
path: 'list',
name: 'sla_list',
meta: {
permissions: ['administrator'],
},
meta,
component: Index,
},
],
@@ -1,5 +1,5 @@
import { frontendURL } from '../../../../helper/URLHelper';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import { FEATURE_FLAGS } from '../../../../featureFlags';
import TeamsIndex from './Index.vue';
import CreateStepWrap from './Create/Index.vue';