mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 19:48:08 +00:00
fix: Update route permissions in the new primary menu (#3499)
* fix: Display rolewise primary sidebar * Fix issues with roles * Fix active style * Fix accessible menu * Fix key missing * Changes menu icon size Co-authored-by: Nithin David <1277421+nithindavid@users.noreply.github.com>
This commit is contained in:
@@ -1,41 +1,37 @@
|
||||
<template>
|
||||
<aside class="woot-sidebar" :class="{ 'only-primary': !showSecondaryMenu }">
|
||||
<aside class="woot-sidebar">
|
||||
<primary-sidebar
|
||||
:logo-source="globalConfig.logo"
|
||||
:installation-name="globalConfig.installationName"
|
||||
:account-id="accountId"
|
||||
:menu-items="primaryMenuItems"
|
||||
:active-menu-item="activePrimaryMenu.key"
|
||||
@toggle-accounts="toggleAccountModal"
|
||||
@key-shortcut-modal="toggleKeyShortcutModal"
|
||||
/>
|
||||
|
||||
<secondary-sidebar
|
||||
v-if="showSecondaryMenu"
|
||||
:account-id="accountId"
|
||||
:inboxes="inboxes"
|
||||
:account-labels="accountLabels"
|
||||
:labels="labels"
|
||||
:teams="teams"
|
||||
:menu-items="primaryMenuItems"
|
||||
:menu-config="activeSecondaryMenu"
|
||||
:current-role="currentRole"
|
||||
@add-label="showAddLabelPopup"
|
||||
/>
|
||||
|
||||
<woot-key-shortcut-modal
|
||||
v-if="showShortcutModal"
|
||||
@close="closeKeyShortcutModal"
|
||||
@clickaway="closeKeyShortcutModal"
|
||||
/>
|
||||
|
||||
<account-selector
|
||||
:show-account-modal="showAccountModal"
|
||||
@close-account-modal="toggleAccountModal"
|
||||
@show-create-account-modal="openCreateAccountModal"
|
||||
/>
|
||||
|
||||
<add-account-modal
|
||||
:show="showCreateAccountModal"
|
||||
@close-account-create-modal="closeCreateAccountModal"
|
||||
/>
|
||||
|
||||
<woot-modal :show.sync="showAddLabelModal" :on-close="hideAddLabelPopup">
|
||||
<add-label-modal @close="hideAddLabelPopup" />
|
||||
</woot-modal>
|
||||
@@ -46,14 +42,14 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import adminMixin from '../../mixins/isAdmin';
|
||||
import { getSidebarItems } from '../../i18n/default-sidebar';
|
||||
import { getSidebarItems } from './config/default-sidebar';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
|
||||
import AccountSelector from './sidebarComponents/AccountSelector.vue';
|
||||
import AddAccountModal from './sidebarComponents/AddAccountModal.vue';
|
||||
import AddLabelModal from '../../routes/dashboard/settings/labels/AddLabel';
|
||||
import PrimarySidebar from 'dashboard/modules/sidebar/components/Primary';
|
||||
import SecondarySidebar from 'dashboard/modules/sidebar/components/Secondary';
|
||||
import PrimarySidebar from './sidebarComponents/Primary';
|
||||
import SecondarySidebar from './sidebarComponents/Secondary';
|
||||
import WootKeyShortcutModal from 'components/widgets/modal/WootKeyShortcutModal';
|
||||
import {
|
||||
hasPressedAltAndCKey,
|
||||
@@ -93,38 +89,34 @@ export default {
|
||||
inboxes: 'inboxes/getInboxes',
|
||||
accountId: 'getCurrentAccountId',
|
||||
currentRole: 'getCurrentRole',
|
||||
accountLabels: 'labels/getLabelsOnSidebar',
|
||||
labels: 'labels/getLabelsOnSidebar',
|
||||
teams: 'teams/getMyTeams',
|
||||
}),
|
||||
|
||||
sideMenuItems() {
|
||||
sideMenuConfig() {
|
||||
return getSidebarItems(this.accountId);
|
||||
},
|
||||
primaryMenuItems() {
|
||||
const menuItems = Object.values(
|
||||
getSidebarItems(this.accountId).common.menuItems
|
||||
const menuItems = this.sideMenuConfig.primaryMenu;
|
||||
return menuItems.filter(menuItem =>
|
||||
menuItem.roles.includes(this.currentRole)
|
||||
);
|
||||
},
|
||||
activeSecondaryMenu() {
|
||||
const { secondaryMenu } = this.sideMenuConfig;
|
||||
const { name: currentRoute } = this.$route;
|
||||
|
||||
return menuItems;
|
||||
const activeSecondaryMenu =
|
||||
secondaryMenu.find(menuItem =>
|
||||
menuItem.routes.includes(currentRoute)
|
||||
) || {};
|
||||
return activeSecondaryMenu;
|
||||
},
|
||||
currentRoute() {
|
||||
return this.$store.state.route.name;
|
||||
},
|
||||
shouldShowNotificationsSideMenu() {
|
||||
return this.sideMenuItems.notifications.routes.includes(
|
||||
this.currentRoute
|
||||
);
|
||||
},
|
||||
shouldShowProfileSideMenu() {
|
||||
return (
|
||||
this.currentRoute === 'profile_settings_index' ||
|
||||
this.currentRoute === 'profile_settings'
|
||||
);
|
||||
},
|
||||
showSecondaryMenu() {
|
||||
if (this.shouldShowNotificationsSideMenu) return false;
|
||||
if (this.shouldShowProfileSideMenu) return false;
|
||||
return true;
|
||||
activePrimaryMenu() {
|
||||
const activePrimaryMenu =
|
||||
this.primaryMenuItems.find(
|
||||
menuItem => menuItem.key === this.activeSecondaryMenu.parentNav
|
||||
) || {};
|
||||
return activePrimaryMenu;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
@@ -169,23 +161,11 @@ export default {
|
||||
}
|
||||
},
|
||||
isCurrentRouteSameAsNavigation(routeName) {
|
||||
return router.currentRoute && router.currentRoute.name === routeName;
|
||||
return this.$route.name === routeName;
|
||||
},
|
||||
toggleSupportChatWindow() {
|
||||
window.$chatwoot.toggle();
|
||||
},
|
||||
filterMenuItemsByRole(menuItems) {
|
||||
if (!this.currentRole) {
|
||||
return [];
|
||||
}
|
||||
return menuItems.filter(
|
||||
menuItem =>
|
||||
window.roleWiseRoutes[this.currentRole].indexOf(
|
||||
menuItem.toStateName
|
||||
) > -1
|
||||
);
|
||||
},
|
||||
|
||||
toggleAccountModal() {
|
||||
this.showAccountModal = !this.showAccountModal;
|
||||
},
|
||||
@@ -208,12 +188,8 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.woot-sidebar {
|
||||
background: white;
|
||||
background: var(--white);
|
||||
display: flex;
|
||||
|
||||
&.only-primary {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.secondary-menu {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import conversations from './sidebarItems/conversations';
|
||||
import contacts from './sidebarItems/contacts';
|
||||
import reports from './sidebarItems/reports';
|
||||
import campaigns from './sidebarItems/campaigns';
|
||||
import settings from './sidebarItems/settings';
|
||||
import notifications from './sidebarItems/notifications';
|
||||
import primaryMenu from './sidebarItems/primaryMenu';
|
||||
|
||||
export const getSidebarItems = accountId => ({
|
||||
primaryMenu: primaryMenu(accountId),
|
||||
secondaryMenu: [
|
||||
conversations(accountId),
|
||||
contacts(accountId),
|
||||
reports(accountId),
|
||||
campaigns(accountId),
|
||||
settings(accountId),
|
||||
notifications(accountId),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
|
||||
const campaigns = accountId => ({
|
||||
parentNav: 'campaigns',
|
||||
routes: ['settings_account_campaigns', 'one_off'],
|
||||
menuItems: [
|
||||
{
|
||||
icon: 'arrow-swap',
|
||||
label: 'ONGOING',
|
||||
key: 'ongoingCampaigns',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/campaigns/ongoing`),
|
||||
toStateName: 'settings_account_campaigns',
|
||||
},
|
||||
{
|
||||
key: 'oneOffCampaigns',
|
||||
icon: 'sound-source',
|
||||
label: 'ONE_OFF',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/campaigns/one_off`),
|
||||
toStateName: 'one_off',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default campaigns;
|
||||
@@ -0,0 +1,21 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
|
||||
const contacts = accountId => ({
|
||||
parentNav: 'contacts',
|
||||
routes: [
|
||||
'contacts_dashboard',
|
||||
'contact_profile_dashboard',
|
||||
'contacts_labels_dashboard',
|
||||
],
|
||||
menuItems: [
|
||||
{
|
||||
icon: 'contact-card-group',
|
||||
label: 'ALL_CONTACTS',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/contacts`),
|
||||
toStateName: 'contacts_dashboard',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default contacts;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
|
||||
const conversations = accountId => ({
|
||||
parentNav: 'conversations',
|
||||
routes: [
|
||||
'home',
|
||||
'inbox_dashboard',
|
||||
'inbox_conversation',
|
||||
'conversation_through_inbox',
|
||||
'notifications_dashboard',
|
||||
'label_conversations',
|
||||
'conversations_through_label',
|
||||
'team_conversations',
|
||||
'conversations_through_team',
|
||||
],
|
||||
menuItems: [
|
||||
{
|
||||
icon: 'chat',
|
||||
label: 'ALL_CONVERSATIONS',
|
||||
key: 'conversations',
|
||||
toState: frontendURL(`accounts/${accountId}/dashboard`),
|
||||
toolTip: 'Conversation from all subscribed inboxes',
|
||||
toStateName: 'home',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default conversations;
|
||||
@@ -0,0 +1,7 @@
|
||||
const notifications = () => ({
|
||||
parentNav: 'notifications',
|
||||
routes: ['notifications_index'],
|
||||
menuItems: [],
|
||||
});
|
||||
|
||||
export default notifications;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
|
||||
const primaryMenuItems = accountId => [
|
||||
{
|
||||
icon: 'chat',
|
||||
key: 'conversations',
|
||||
label: 'CONVERSATIONS',
|
||||
toState: frontendURL(`accounts/${accountId}/dashboard`),
|
||||
toStateName: 'home',
|
||||
roles: ['administrator', 'agent'],
|
||||
},
|
||||
{
|
||||
icon: 'book-contacts',
|
||||
key: 'contacts',
|
||||
label: 'CONTACTS',
|
||||
toState: frontendURL(`accounts/${accountId}/contacts`),
|
||||
toStateName: 'contacts_dashboard',
|
||||
roles: ['administrator', 'agent'],
|
||||
},
|
||||
{
|
||||
icon: 'arrow-trending-lines',
|
||||
key: 'reports',
|
||||
label: 'REPORTS',
|
||||
toState: frontendURL(`accounts/${accountId}/reports`),
|
||||
toStateName: 'settings_account_reports',
|
||||
roles: ['administrator'],
|
||||
},
|
||||
{
|
||||
icon: 'megaphone',
|
||||
key: 'campaigns',
|
||||
label: 'CAMPAIGNS',
|
||||
toState: frontendURL(`accounts/${accountId}/campaigns`),
|
||||
toStateName: 'settings_account_campaigns',
|
||||
roles: ['administrator'],
|
||||
},
|
||||
{
|
||||
icon: 'settings',
|
||||
key: 'settings',
|
||||
label: 'SETTINGS',
|
||||
toState: frontendURL(`accounts/${accountId}/settings`),
|
||||
toStateName: 'settings_home',
|
||||
roles: ['administrator', 'agent'],
|
||||
},
|
||||
];
|
||||
|
||||
export default primaryMenuItems;
|
||||
@@ -0,0 +1,7 @@
|
||||
const profileSettings = () => ({
|
||||
parentNav: 'profileSettings',
|
||||
routes: ['profile_settings_index'],
|
||||
menuItems: [],
|
||||
});
|
||||
|
||||
export default profileSettings;
|
||||
@@ -0,0 +1,59 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
|
||||
const reports = accountId => ({
|
||||
parentNav: 'reports',
|
||||
routes: [
|
||||
'settings_account_reports',
|
||||
'csat_reports',
|
||||
'agent_reports',
|
||||
'label_reports',
|
||||
'inbox_reports',
|
||||
'team_reports',
|
||||
],
|
||||
menuItems: [
|
||||
{
|
||||
icon: 'arrow-trending-lines',
|
||||
label: 'REPORTS_OVERVIEW',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/reports/overview`),
|
||||
toStateName: 'settings_account_reports',
|
||||
},
|
||||
{
|
||||
icon: 'emoji',
|
||||
label: 'CSAT',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/reports/csat`),
|
||||
toStateName: 'csat_reports',
|
||||
},
|
||||
{
|
||||
icon: 'people',
|
||||
label: 'REPORTS_AGENT',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/reports/agent`),
|
||||
toStateName: 'agent_reports',
|
||||
},
|
||||
{
|
||||
icon: 'tag',
|
||||
label: 'REPORTS_LABEL',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/reports/label`),
|
||||
toStateName: 'label_reports',
|
||||
},
|
||||
{
|
||||
icon: 'mail-inbox-all',
|
||||
label: 'REPORTS_INBOX',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/reports/inboxes`),
|
||||
toStateName: 'inbox_reports',
|
||||
},
|
||||
{
|
||||
icon: 'people-team',
|
||||
label: 'REPORTS_TEAM',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/reports/teams`),
|
||||
toStateName: 'team_reports',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default reports;
|
||||
@@ -0,0 +1,112 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
|
||||
const settings = accountId => ({
|
||||
parentNav: 'settings',
|
||||
routes: [
|
||||
'agent_list',
|
||||
'canned_list',
|
||||
'labels_list',
|
||||
'settings_inbox',
|
||||
'attributes_list',
|
||||
'settings_inbox_new',
|
||||
'settings_inbox_list',
|
||||
'settings_inbox_show',
|
||||
'settings_inboxes_page_channel',
|
||||
'settings_inboxes_add_agents',
|
||||
'settings_inbox_finish',
|
||||
'settings_integrations',
|
||||
'settings_integrations_webhook',
|
||||
'settings_integrations_integration',
|
||||
'settings_applications',
|
||||
'settings_applications_webhook',
|
||||
'settings_applications_integration',
|
||||
'general_settings',
|
||||
'general_settings_index',
|
||||
'settings_teams_list',
|
||||
'settings_teams_new',
|
||||
'settings_teams_add_agents',
|
||||
'settings_teams_finish',
|
||||
'settings_teams_edit',
|
||||
'settings_teams_edit_members',
|
||||
'settings_teams_edit_finish',
|
||||
'automation_list',
|
||||
],
|
||||
menuItems: [
|
||||
{
|
||||
icon: 'people',
|
||||
label: 'AGENTS',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/settings/agents/list`),
|
||||
toStateName: 'agent_list',
|
||||
},
|
||||
{
|
||||
icon: 'people-team',
|
||||
label: 'TEAMS',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/settings/teams/list`),
|
||||
toStateName: 'settings_teams_list',
|
||||
},
|
||||
{
|
||||
icon: 'mail-inbox-all',
|
||||
label: 'INBOXES',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/settings/inboxes/list`),
|
||||
toStateName: 'settings_inbox_list',
|
||||
},
|
||||
{
|
||||
icon: 'tag',
|
||||
label: 'LABELS',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/settings/labels/list`),
|
||||
toStateName: 'labels_list',
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
label: 'CUSTOM_ATTRIBUTES',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(
|
||||
`accounts/${accountId}/settings/custom-attributes/list`
|
||||
),
|
||||
toStateName: 'attributes_list',
|
||||
},
|
||||
{
|
||||
icon: 'autocorrect',
|
||||
label: 'AUTOMATION',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/settings/automation/list`),
|
||||
toStateName: 'automation_list',
|
||||
},
|
||||
{
|
||||
icon: 'chat-multiple',
|
||||
label: 'CANNED_RESPONSES',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(
|
||||
`accounts/${accountId}/settings/canned-response/list`
|
||||
),
|
||||
toStateName: 'canned_list',
|
||||
},
|
||||
{
|
||||
icon: 'flash-on',
|
||||
label: 'INTEGRATIONS',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/settings/integrations`),
|
||||
toStateName: 'settings_integrations',
|
||||
},
|
||||
{
|
||||
icon: 'star-emphasis',
|
||||
label: 'APPLICATIONS',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/settings/applications`),
|
||||
toStateName: 'settings_applications',
|
||||
},
|
||||
{
|
||||
icon: 'settings',
|
||||
label: 'ACCOUNT_SETTINGS',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/settings/general`),
|
||||
toStateName: 'general_settings_index',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default settings;
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="logo">
|
||||
<router-link :to="dashboardPath" replace>
|
||||
<img :src="source" :alt="name" />
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
source: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
accountId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dashboardPath() {
|
||||
return frontendURL(`accounts/${this.accountId}/dashboard`);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
$logo-size: 32px;
|
||||
|
||||
.logo {
|
||||
padding: var(--space-normal);
|
||||
|
||||
img {
|
||||
width: $logo-size;
|
||||
height: $logo-size;
|
||||
object-fit: cover;
|
||||
object-position: left center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -10,7 +10,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import PrimaryNavItem from 'dashboard/modules/sidebar/components/PrimaryNavItem';
|
||||
import PrimaryNavItem from './PrimaryNavItem';
|
||||
|
||||
export default {
|
||||
components: { PrimaryNavItem },
|
||||
@@ -21,7 +21,7 @@ export default {
|
||||
}),
|
||||
unreadCount() {
|
||||
if (!this.notificationMetadata.unreadCount) {
|
||||
return '0';
|
||||
return '';
|
||||
}
|
||||
|
||||
return this.notificationMetadata.unreadCount < 100
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
size="small"
|
||||
icon="ion-help-buoy"
|
||||
icon="chat-help"
|
||||
@click="$emit('show-support-chat-window')"
|
||||
>
|
||||
{{ $t('SIDEBAR_ITEMS.CONTACT_SUPPORT') }}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="primary--sidebar">
|
||||
<logo
|
||||
:source="logoSource"
|
||||
:name="installationName"
|
||||
:account-id="accountId"
|
||||
/>
|
||||
<nav class="menu vertical">
|
||||
<primary-nav-item
|
||||
v-for="menuItem in menuItems"
|
||||
:key="menuItem.toState"
|
||||
:icon="menuItem.icon"
|
||||
:name="menuItem.label"
|
||||
:to="menuItem.toState"
|
||||
:is-child-menu-active="menuItem.key === activeMenuItem"
|
||||
/>
|
||||
</nav>
|
||||
<div class="menu vertical user-menu">
|
||||
<notification-bell />
|
||||
<agent-details @toggle-menu="toggleOptions" />
|
||||
<options-menu
|
||||
:show="showOptionsMenu"
|
||||
@toggle-accounts="toggleAccountModal"
|
||||
@show-support-chat-window="toggleSupportChatWindow"
|
||||
@key-shortcut-modal="$emit('key-shortcut-modal')"
|
||||
@close="toggleOptions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Logo from './Logo';
|
||||
import PrimaryNavItem from './PrimaryNavItem';
|
||||
import OptionsMenu from './OptionsMenu';
|
||||
import AgentDetails from './AgentDetails';
|
||||
import NotificationBell from './NotificationBell';
|
||||
|
||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Logo,
|
||||
PrimaryNavItem,
|
||||
OptionsMenu,
|
||||
AgentDetails,
|
||||
NotificationBell,
|
||||
},
|
||||
props: {
|
||||
logoSource: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
installationName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
accountId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
menuItems: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
activeMenuItem: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showOptionsMenu: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
frontendURL,
|
||||
toggleOptions() {
|
||||
this.showOptionsMenu = !this.showOptionsMenu;
|
||||
},
|
||||
toggleAccountModal() {
|
||||
this.$emit('toggle-accounts');
|
||||
},
|
||||
toggleSupportChatWindow() {
|
||||
window.$chatwoot.toggle();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.primary--sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: var(--space-jumbo);
|
||||
border-right: 1px solid var(--s-50);
|
||||
box-sizing: content-box;
|
||||
height: 100vh;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.menu {
|
||||
align-items: center;
|
||||
margin-top: var(--space-medium);
|
||||
}
|
||||
|
||||
.user-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: var(--space-normal);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<router-link v-slot="{ href, isActive, navigate }" :to="to" custom>
|
||||
<a
|
||||
v-tooltip.right="$t(`SIDEBAR.${name}`)"
|
||||
:href="href"
|
||||
class="button clear button--only-icon menu-item"
|
||||
:class="{ 'is-active': isActive || isChildMenuActive }"
|
||||
@click="navigate"
|
||||
>
|
||||
<fluent-icon :icon="icon" />
|
||||
<span class="show-for-sr">{{ name }}</span>
|
||||
<span v-if="count" class="badge warning">{{ count }}</span>
|
||||
</a>
|
||||
</router-link>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
to: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
count: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isChildMenuActive: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.button {
|
||||
margin: var(--space-small) 0;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
border-radius: var(--border-radius-large);
|
||||
border: 1px solid transparent;
|
||||
color: var(--s-600);
|
||||
|
||||
&:hover {
|
||||
background: var(--w-25);
|
||||
color: var(--s-600);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--w-500);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background: var(--w-50);
|
||||
color: var(--w-500);
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: var(--font-size-default);
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
right: var(--space-minus-smaller);
|
||||
top: var(--space-minus-smaller);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<div v-if="menuConfig.menuItems.length" class="main-nav secondary-menu">
|
||||
<transition-group name="menu-list" tag="ul" class="menu vertical">
|
||||
<secondary-nav-item
|
||||
v-for="menuItem in accessibleMenuItems"
|
||||
:key="menuItem.toState"
|
||||
:menu-item="menuItem"
|
||||
/>
|
||||
<secondary-nav-item
|
||||
v-for="menuItem in additionalSecondaryMenuItems[menuConfig.parentNav]"
|
||||
:key="menuItem.key"
|
||||
:menu-item="menuItem"
|
||||
@add-label="showAddLabelPopup"
|
||||
/>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { frontendURL } from '../../../helper/URLHelper';
|
||||
import SecondaryNavItem from './SecondaryNavItem.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SecondaryNavItem,
|
||||
},
|
||||
props: {
|
||||
accountId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
labels: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
inboxes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
teams: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
menuConfig: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
currentRole: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
accessibleMenuItems() {
|
||||
if (!this.currentRole) {
|
||||
return [];
|
||||
}
|
||||
return this.menuConfig.menuItems.filter(
|
||||
menuItem =>
|
||||
window.roleWiseRoutes[this.currentRole].indexOf(
|
||||
menuItem.toStateName
|
||||
) > -1
|
||||
);
|
||||
},
|
||||
inboxSection() {
|
||||
return {
|
||||
icon: 'folder',
|
||||
label: 'INBOXES',
|
||||
hasSubMenu: true,
|
||||
newLink: true,
|
||||
newLinkTag: 'NEW_INBOX',
|
||||
key: 'inbox',
|
||||
toState: frontendURL(`accounts/${this.accountId}/settings/inboxes/new`),
|
||||
toStateName: 'settings_inbox_new',
|
||||
newLinkRouteName: 'settings_inbox_new',
|
||||
children: this.inboxes.map(inbox => ({
|
||||
id: inbox.id,
|
||||
label: inbox.name,
|
||||
truncateLabel: true,
|
||||
toState: frontendURL(`accounts/${this.accountId}/inbox/${inbox.id}`),
|
||||
type: inbox.channel_type,
|
||||
phoneNumber: inbox.phone_number,
|
||||
})),
|
||||
};
|
||||
},
|
||||
labelSection() {
|
||||
return {
|
||||
icon: 'number-symbol',
|
||||
label: 'LABELS',
|
||||
hasSubMenu: true,
|
||||
newLink: true,
|
||||
newLinkTag: 'NEW_LABEL',
|
||||
key: 'label',
|
||||
toState: frontendURL(`accounts/${this.accountId}/settings/labels`),
|
||||
toStateName: 'labels_list',
|
||||
showModalForNewItem: true,
|
||||
modalName: 'AddLabel',
|
||||
children: this.labels.map(label => ({
|
||||
id: label.id,
|
||||
label: label.title,
|
||||
color: label.color,
|
||||
truncateLabel: true,
|
||||
toState: frontendURL(
|
||||
`accounts/${this.accountId}/label/${label.title}`
|
||||
),
|
||||
})),
|
||||
};
|
||||
},
|
||||
contactLabelSection() {
|
||||
return {
|
||||
icon: 'number-symbol',
|
||||
label: 'TAGGED_WITH',
|
||||
hasSubMenu: true,
|
||||
key: 'label',
|
||||
newLink: true,
|
||||
newLinkTag: 'NEW_LABEL',
|
||||
toState: frontendURL(`accounts/${this.accountId}/settings/labels`),
|
||||
toStateName: 'labels_list',
|
||||
showModalForNewItem: true,
|
||||
modalName: 'AddLabel',
|
||||
children: this.labels.map(label => ({
|
||||
id: label.id,
|
||||
label: label.title,
|
||||
color: label.color,
|
||||
truncateLabel: true,
|
||||
toState: frontendURL(
|
||||
`accounts/${this.accountId}/labels/${label.title}/contacts`
|
||||
),
|
||||
})),
|
||||
};
|
||||
},
|
||||
teamSection() {
|
||||
return {
|
||||
icon: 'people-team',
|
||||
label: 'TEAMS',
|
||||
hasSubMenu: true,
|
||||
newLink: true,
|
||||
newLinkTag: 'NEW_TEAM',
|
||||
key: 'team',
|
||||
toState: frontendURL(`accounts/${this.accountId}/settings/teams/new`),
|
||||
toStateName: 'settings_teams_new',
|
||||
newLinkRouteName: 'settings_teams_new',
|
||||
children: this.teams.map(team => ({
|
||||
id: team.id,
|
||||
label: team.name,
|
||||
truncateLabel: true,
|
||||
toState: frontendURL(`accounts/${this.accountId}/team/${team.id}`),
|
||||
})),
|
||||
};
|
||||
},
|
||||
additionalSecondaryMenuItems() {
|
||||
let conversationMenuItems = [this.inboxSection, this.labelSection];
|
||||
if (this.teams.length) {
|
||||
conversationMenuItems = [this.teamSection, ...conversationMenuItems];
|
||||
}
|
||||
return {
|
||||
conversations: conversationMenuItems,
|
||||
contacts: [this.contactLabelSection],
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showAddLabelPopup() {
|
||||
this.$emit('add-label');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<router-link
|
||||
v-slot="{ href, isActive, navigate }"
|
||||
:to="to"
|
||||
custom
|
||||
active-class="active"
|
||||
>
|
||||
<li :class="{ active: isActive }">
|
||||
<a
|
||||
:href="href"
|
||||
class="button clear menu-item text-truncate"
|
||||
:class="{ 'is-active': isActive, 'text-truncate': shouldTruncate }"
|
||||
@click="navigate"
|
||||
>
|
||||
<span v-if="icon" class="badge--icon">
|
||||
<fluent-icon class="inbox-icon" :icon="icon" size="12" />
|
||||
</span>
|
||||
<span
|
||||
v-if="labelColor"
|
||||
class="badge--label"
|
||||
:style="{ backgroundColor: labelColor }"
|
||||
/>
|
||||
<span
|
||||
:title="menuTitle"
|
||||
class="menu-label button__content"
|
||||
:class="{ 'text-truncate': shouldTruncate }"
|
||||
>
|
||||
{{ label }}
|
||||
</span>
|
||||
<span v-if="count" class="badge" :class="{ secondary: !isActive }">
|
||||
{{ count }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
to: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
labelColor: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
shouldTruncate: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
count: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showIcon() {
|
||||
return { 'text-truncate': this.shouldTruncate };
|
||||
},
|
||||
menuTitle() {
|
||||
return this.shouldTruncate ? this.label : '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
$badge-size: var(--space-normal);
|
||||
$label-badge-size: var(--space-slab);
|
||||
|
||||
.button {
|
||||
margin: var(--space-small) 0;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: inline-flex;
|
||||
color: var(--s-600);
|
||||
font-weight: var(--font-weight-medium);
|
||||
width: 100%;
|
||||
height: var(--space-medium);
|
||||
padding: var(--space-smaller) var(--space-smaller);
|
||||
margin: var(--space-smaller) 0;
|
||||
text-align: left;
|
||||
|
||||
&:hover {
|
||||
background: var(--s-25);
|
||||
color: var(--s-600);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--w-300);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background: var(--w-25);
|
||||
color: var(--w-500);
|
||||
border-color: var(--w-25);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
flex-grow: 1;
|
||||
line-height: var(--space-two);
|
||||
}
|
||||
|
||||
.inbox-icon {
|
||||
font-size: var(--font-size-nano);
|
||||
}
|
||||
|
||||
.badge--label,
|
||||
.badge--icon {
|
||||
display: inline-flex;
|
||||
border-radius: var(--border-radius-small);
|
||||
margin-right: var(--space-smaller);
|
||||
background: var(--s-100);
|
||||
}
|
||||
|
||||
.badge--icon {
|
||||
align-items: center;
|
||||
height: $badge-size;
|
||||
justify-content: center;
|
||||
min-width: $badge-size;
|
||||
}
|
||||
|
||||
.badge--label {
|
||||
height: $label-badge-size;
|
||||
min-width: $label-badge-size;
|
||||
margin-left: var(--space-smaller);
|
||||
}
|
||||
|
||||
.badge.secondary {
|
||||
min-width: unset;
|
||||
background: var(--s-75);
|
||||
color: var(--s-600);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
</style>
|
||||
@@ -1,16 +1,24 @@
|
||||
<template>
|
||||
<li :class="computedClass" class="sidebar-item">
|
||||
<a
|
||||
class="sub-menu-title"
|
||||
:class="getMenuItemClass"
|
||||
data-tooltip
|
||||
aria-haspopup="true"
|
||||
:title="menuItem.toolTip"
|
||||
>
|
||||
<li class="sidebar-item">
|
||||
<span v-if="hasSubMenu" class="secondary-menu--title fs-small">
|
||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||
</a>
|
||||
<ul v-if="menuItem.hasSubMenu" class="nested vertical menu">
|
||||
<secondary-nav-item
|
||||
</span>
|
||||
<router-link
|
||||
v-else
|
||||
class="secondary-menu--title secondary-menu--link fs-small"
|
||||
:class="computedClass"
|
||||
:to="menuItem.toState"
|
||||
>
|
||||
<fluent-icon
|
||||
:icon="menuItem.icon"
|
||||
class="secondary-menu--icon"
|
||||
size="14"
|
||||
/>
|
||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||
</router-link>
|
||||
|
||||
<ul v-if="hasSubMenu" class="nested vertical menu">
|
||||
<secondary-child-nav-item
|
||||
v-for="child in menuItem.children"
|
||||
:key="child.id"
|
||||
:to="child.toState"
|
||||
@@ -20,7 +28,7 @@
|
||||
:icon="computedInboxClass(child)"
|
||||
/>
|
||||
<router-link
|
||||
v-if="menuItem.newLink"
|
||||
v-if="showItem(menuItem)"
|
||||
v-slot="{ href, isActive, navigate }"
|
||||
:to="menuItem.toState"
|
||||
custom
|
||||
@@ -46,13 +54,13 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import adminMixin from '../../mixins/isAdmin';
|
||||
import adminMixin from '../../../mixins/isAdmin';
|
||||
import { getInboxClassByType } from 'dashboard/helper/inbox';
|
||||
|
||||
import SecondaryNavItem from 'dashboard/modules/sidebar/components/SecondaryNavItem';
|
||||
import SecondaryChildNavItem from './SecondaryChildNavItem';
|
||||
|
||||
export default {
|
||||
components: { SecondaryNavItem },
|
||||
components: { SecondaryChildNavItem },
|
||||
mixins: [adminMixin],
|
||||
props: {
|
||||
menuItem: {
|
||||
@@ -62,10 +70,8 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ activeInbox: 'getSelectedInbox' }),
|
||||
getMenuItemClass() {
|
||||
return this.menuItem.cssClass
|
||||
? `side-menu ${this.menuItem.cssClass}`
|
||||
: 'side-menu';
|
||||
hasSubMenu() {
|
||||
return !!this.menuItem.children;
|
||||
},
|
||||
computedClass() {
|
||||
// If active Inbox is present
|
||||
@@ -76,7 +82,7 @@ export default {
|
||||
this.$store.state.route.name === 'inbox_conversation' &&
|
||||
this.menuItem.toStateName === 'home'
|
||||
) {
|
||||
return 'active';
|
||||
return 'is-active';
|
||||
}
|
||||
return ' ';
|
||||
},
|
||||
@@ -88,14 +94,6 @@ export default {
|
||||
const classByType = getInboxClassByType(type, phoneNumber);
|
||||
return classByType;
|
||||
},
|
||||
computedChildClass(child) {
|
||||
if (!child.truncateLabel) return '';
|
||||
return 'text-truncate';
|
||||
},
|
||||
computedChildTitle(child) {
|
||||
if (!child.truncateLabel) return false;
|
||||
return child.label;
|
||||
},
|
||||
newLinkClick(e, navigate) {
|
||||
if (this.menuItem.newLinkRouteName) {
|
||||
navigate(e);
|
||||
@@ -114,17 +112,46 @@ export default {
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.sidebar-item {
|
||||
margin: var(--space-small) 0;
|
||||
margin: var(--space-smaller) 0 0;
|
||||
}
|
||||
.sub-menu-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--space-small);
|
||||
margin-bottom: var(--space-smaller);
|
||||
|
||||
.secondary-menu--title {
|
||||
color: var(--s-600);
|
||||
display: flex;
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: var(--space-two);
|
||||
text-transform: uppercase;
|
||||
margin: var(--space-small) 0;
|
||||
padding: 0 var(--space-small);
|
||||
}
|
||||
|
||||
.secondary-menu--link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: var(--space-small);
|
||||
font-weight: var(--font-weight-medium);
|
||||
border-radius: var(--border-radius-normal);
|
||||
|
||||
&:hover {
|
||||
background: var(--s-25);
|
||||
color: var(--s-600);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--w-300);
|
||||
}
|
||||
|
||||
&.router-link-exact-active,
|
||||
&.is-active {
|
||||
background: var(--w-25);
|
||||
color: var(--w-500);
|
||||
border-color: var(--w-25);
|
||||
}
|
||||
}
|
||||
|
||||
.secondary-menu--icon {
|
||||
margin-right: var(--space-smaller);
|
||||
min-width: var(--space-normal);
|
||||
}
|
||||
|
||||
.sub-menu-link {
|
||||
Reference in New Issue
Block a user