Chatwoot/app/javascript/dashboard/components/layout/Sidebar.vue
Pranav Raj S 5c3de5e095
fix: Enhance CRM UI (#1397)
* feat: Sort by name
* feat: Fetch labels from sidebar
* Remove unused language file
* Add beta tag to contacts
* Add timeMixin, reduce font-size
* Remove unused methods
* Remove unused prop
* Disabled footer if no contacts or invalid page
* Add keyup for input
* Fix conversation not loading if there are no active conversations
* return last_seen_at as unix time
* Fix contact edit modal
* Add loader for edit contact button
* Fix review comments
2020-11-11 16:02:14 +05:30

412 lines
11 KiB
Vue

<template>
<aside class="sidebar animated shrink columns">
<div class="logo">
<router-link :to="dashboardPath" replace>
<img :src="globalConfig.logo" :alt="globalConfig.installationName" />
</router-link>
</div>
<div class="main-nav">
<transition-group name="menu-list" tag="ul" class="menu vertical">
<sidebar-item
v-for="item in accessibleMenuItems"
:key="item.toState"
:menu-item="item"
/>
<sidebar-item
v-if="shouldShowInboxes"
:key="inboxSection.toState"
:menu-item="inboxSection"
/>
<sidebar-item
v-if="shouldShowInboxes"
:key="labelSection.toState"
:menu-item="labelSection"
/>
</transition-group>
</div>
<div class="bottom-nav">
<availability-status />
</div>
<div class="bottom-nav app-context-menu">
<transition name="menu-slide">
<div
v-if="showOptionsMenu"
v-on-clickaway="showOptions"
class="dropdown-pane top"
>
<ul class="vertical dropdown menu">
<li v-if="showChangeAccountOption">
<button
class="button clear change-accounts--button"
@click="changeAccount"
>
{{ $t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS') }}
</button>
</li>
<li>
<router-link :to="`/app/accounts/${accountId}/profile/settings`">
{{ $t('SIDEBAR_ITEMS.PROFILE_SETTINGS') }}
</router-link>
</li>
<li>
<a href="#" @click.prevent="logout()">
{{ $t('SIDEBAR_ITEMS.LOGOUT') }}
</a>
</li>
</ul>
</div>
</transition>
<div class="current-user" @click.prevent="showOptions()">
<thumbnail
:src="currentUser.avatar_url"
:username="currentUserAvailableName"
/>
<div class="current-user--data">
<h3 class="current-user--name">
{{ currentUserAvailableName }}
</h3>
<h5 v-if="currentRole" class="current-user--role">
{{ $t(`AGENT_MGMT.AGENT_TYPES.${currentRole.toUpperCase()}`) }}
</h5>
</div>
<span class="current-user--options icon ion-android-more-vertical" />
</div>
</div>
<woot-modal
:show="showAccountModal"
:on-close="onClose"
class="account-selector--modal"
>
<woot-modal-header
:header-title="$t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS')"
:header-content="$t('SIDEBAR_ITEMS.SELECTOR_SUBTITLE')"
/>
<div
v-for="account in currentUser.accounts"
:key="account.id"
class="account-selector"
>
<a :href="`/app/accounts/${account.id}/dashboard`">
<i v-if="account.id === accountId" class="ion ion-ios-checkmark" />
<label :for="account.name" class="account--details">
<div class="account--name">{{ account.name }}</div>
<div class="account--role">{{ account.role }}</div>
</label>
</a>
</div>
<div
v-if="globalConfig.createNewAccountFromDashboard"
class="modal-footer delete-item"
>
<button
class="button success large expanded nice"
@click="createAccount"
>
{{ $t('CREATE_ACCOUNT.NEW_ACCOUNT') }}
</button>
</div>
</woot-modal>
<woot-modal
:show="showCreateAccountModal"
:on-close="onCloseCreate"
class="account-selector--modal"
>
<div class="column content-box">
<woot-modal-header
:header-title="$t('CREATE_ACCOUNT.NEW_ACCOUNT')"
:header-content="$t('CREATE_ACCOUNT.SELECTOR_SUBTITLE')"
/>
<form class="row" @submit.prevent="addAccount()">
<div class="medium-12 columns">
<label :class="{ error: $v.accountName.$error }">
{{ $t('CREATE_ACCOUNT.FORM.NAME.LABEL') }}
<input
v-model.trim="accountName"
type="text"
:placeholder="$t('CREATE_ACCOUNT.FORM.NAME.PLACEHOLDER')"
@input="$v.accountName.$touch"
/>
</label>
</div>
<div class="modal-footer medium-12 columns">
<div class="medium-12 columns">
<woot-submit-button
:disabled="
$v.accountName.$invalid ||
$v.accountName.$invalid ||
uiFlags.isCreating
"
:button-text="$t('CREATE_ACCOUNT.FORM.SUBMIT')"
:loading="uiFlags.isCreating"
button-class="large expanded"
/>
</div>
</div>
</form>
</div>
</woot-modal>
</aside>
</template>
<script>
import { mapGetters } from 'vuex';
import { mixin as clickaway } from 'vue-clickaway';
import adminMixin from '../../mixins/isAdmin';
import Auth from '../../api/auth';
import SidebarItem from './SidebarItem';
import AvailabilityStatus from './AvailabilityStatus';
import { frontendURL } from '../../helper/URLHelper';
import Thumbnail from '../widgets/Thumbnail';
import { getSidebarItems } from '../../i18n/default-sidebar';
import { required, minLength } from 'vuelidate/lib/validators';
import alertMixin from 'shared/mixins/alertMixin';
export default {
components: {
SidebarItem,
Thumbnail,
AvailabilityStatus,
},
mixins: [clickaway, adminMixin, alertMixin],
props: {
route: {
type: String,
default: '',
},
},
data() {
return {
showOptionsMenu: false,
showAccountModal: false,
showCreateAccountModal: false,
accountName: '',
vertical: 'bottom',
horizontal: 'center',
};
},
validations: {
accountName: {
required,
minLength: minLength(1),
},
},
computed: {
...mapGetters({
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
inboxes: 'inboxes/getInboxes',
accountId: 'getCurrentAccountId',
currentRole: 'getCurrentRole',
uiFlags: 'agents/getUIFlags',
accountLabels: 'labels/getLabelsOnSidebar',
}),
currentUserAvailableName() {
return this.currentUser.name;
},
showChangeAccountOption() {
if (this.globalConfig.createNewAccountFromDashboard) {
return true;
}
return this.currentUser.accounts.length > 1;
},
sidemenuItems() {
return getSidebarItems(this.accountId);
},
accessibleMenuItems() {
// get all keys in menuGroup
const groupKey = Object.keys(this.sidemenuItems);
let menuItems = [];
// Iterate over menuGroup to find the correct group
for (let i = 0; i < groupKey.length; i += 1) {
const groupItem = this.sidemenuItems[groupKey[i]];
// Check if current route is included
const isRouteIncluded = groupItem.routes.includes(this.currentRoute);
if (isRouteIncluded) {
menuItems = Object.values(groupItem.menuItems);
}
}
return this.filterMenuItemsByRole(menuItems);
},
currentRoute() {
return this.$store.state.route.name;
},
shouldShowInboxes() {
return this.sidemenuItems.common.routes.includes(this.currentRoute);
},
inboxSection() {
return {
icon: 'ion-folder',
label: 'INBOXES',
hasSubMenu: true,
newLink: true,
key: 'inbox',
cssClass: 'menu-title align-justify',
toState: frontendURL(`accounts/${this.accountId}/settings/inboxes`),
toStateName: 'settings_inbox_list',
children: this.inboxes.map(inbox => ({
id: inbox.id,
label: inbox.name,
toState: frontendURL(`accounts/${this.accountId}/inbox/${inbox.id}`),
type: inbox.channel_type,
phoneNumber: inbox.phone_number,
})),
};
},
labelSection() {
return {
icon: 'ion-pound',
label: 'LABELS',
hasSubMenu: true,
key: 'label',
cssClass: 'menu-title align-justify',
toState: frontendURL(`accounts/${this.accountId}/settings/labels`),
toStateName: 'labels_list',
children: this.accountLabels.map(label => ({
id: label.id,
label: label.title,
color: label.color,
truncateLabel: true,
toState: frontendURL(
`accounts/${this.accountId}/label/${label.title}`
),
})),
};
},
dashboardPath() {
return frontendURL(`accounts/${this.accountId}/dashboard`);
},
},
mounted() {
this.$store.dispatch('labels/get');
this.$store.dispatch('inboxes/get');
},
methods: {
filterMenuItemsByRole(menuItems) {
if (!this.currentRole) {
return [];
}
return menuItems.filter(
menuItem =>
window.roleWiseRoutes[this.currentRole].indexOf(
menuItem.toStateName
) > -1
);
},
logout() {
Auth.logout();
},
showOptions() {
this.showOptionsMenu = !this.showOptionsMenu;
},
changeAccount() {
this.showAccountModal = true;
},
onClose() {
this.showAccountModal = false;
},
createAccount() {
this.showAccountModal = false;
this.showCreateAccountModal = true;
},
onCloseCreate() {
this.showCreateAccountModal = false;
},
async addAccount() {
try {
const account_id = await this.$store.dispatch('accounts/create', {
account_name: this.accountName,
});
this.onClose();
this.showAlert(this.$t('CREATE_ACCOUNT.API.SUCCESS_MESSAGE'));
window.location = `/app/accounts/${account_id}/dashboard`;
} catch (error) {
if (error.response.status === 422) {
this.showAlert(this.$t('CREATE_ACCOUNT.API.EXIST_MESSAGE'));
} else {
this.showAlert(this.$t('CREATE_ACCOUNT.API.ERROR_MESSAGE'));
}
}
},
},
};
</script>
<style lang="scss">
@import '~dashboard/assets/scss/variables';
.account-selector--modal {
.modal-container {
width: 40rem;
}
.page-top-bar {
padding-bottom: $space-two;
}
}
.change-accounts--button.button {
font-weight: $font-weight-normal;
font-size: $font-size-small;
padding: $space-small $space-one;
}
.dropdown-pane {
li {
a {
padding: $space-small $space-one !important;
}
}
}
.account-selector {
cursor: pointer;
padding: $space-small $space-large;
.ion-ios-checkmark {
font-size: $font-size-big;
& + .account--details {
padding-left: $space-normal;
}
}
.account--details {
padding-left: $space-large + $space-smaller;
}
&:last-child {
margin-bottom: $space-large;
}
a {
align-items: center;
cursor: pointer;
display: flex;
.account--name {
cursor: pointer;
font-size: $font-size-medium;
font-weight: $font-weight-medium;
line-height: 1;
}
.account--role {
cursor: pointer;
font-size: $font-size-mini;
text-transform: capitalize;
}
}
}
.app-context-menu {
height: 6rem;
}
</style>