mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 19:17:48 +00:00 
			
		
		
		
	 23a804512a
			
		
	
	23a804512a
	
	
	
		
			
			Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
		
			
				
	
	
		
			539 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			539 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <script setup>
 | |
| import { h, computed, onMounted } from 'vue';
 | |
| import { provideSidebarContext } from './provider';
 | |
| import { useAccount } from 'dashboard/composables/useAccount';
 | |
| import { useKbd } from 'dashboard/composables/utils/useKbd';
 | |
| import { useMapGetter } from 'dashboard/composables/store';
 | |
| import { useStore } from 'vuex';
 | |
| import { useI18n } from 'vue-i18n';
 | |
| import { useStorage } from '@vueuse/core';
 | |
| import { useSidebarKeyboardShortcuts } from './useSidebarKeyboardShortcuts';
 | |
| 
 | |
| import Button from 'dashboard/components-next/button/Button.vue';
 | |
| import SidebarGroup from './SidebarGroup.vue';
 | |
| import SidebarProfileMenu from './SidebarProfileMenu.vue';
 | |
| import ChannelLeaf from './ChannelLeaf.vue';
 | |
| import SidebarAccountSwitcher from './SidebarAccountSwitcher.vue';
 | |
| import Logo from 'next/icon/Logo.vue';
 | |
| import ComposeConversation from 'dashboard/components-next/NewConversation/ComposeConversation.vue';
 | |
| 
 | |
| const emit = defineEmits([
 | |
|   'closeKeyShortcutModal',
 | |
|   'openKeyShortcutModal',
 | |
|   'showCreateAccountModal',
 | |
| ]);
 | |
| 
 | |
| const { accountScopedRoute } = useAccount();
 | |
| const store = useStore();
 | |
| const searchShortcut = useKbd([`$mod`, 'k']);
 | |
| const { t } = useI18n();
 | |
| 
 | |
| const toggleShortcutModalFn = show => {
 | |
|   if (show) {
 | |
|     emit('openKeyShortcutModal');
 | |
|   } else {
 | |
|     emit('closeKeyShortcutModal');
 | |
|   }
 | |
| };
 | |
| 
 | |
| useSidebarKeyboardShortcuts(toggleShortcutModalFn);
 | |
| 
 | |
| // We're using localStorage to store the expanded item in the sidebar
 | |
| // This helps preserve context when navigating between portal and dashboard layouts
 | |
| // and also when the user refreshes the page
 | |
| const expandedItem = useStorage(
 | |
|   'next-sidebar-expanded-item',
 | |
|   null,
 | |
|   sessionStorage
 | |
| );
 | |
| 
 | |
| const setExpandedItem = name => {
 | |
|   expandedItem.value = expandedItem.value === name ? null : name;
 | |
| };
 | |
| provideSidebarContext({
 | |
|   expandedItem,
 | |
|   setExpandedItem,
 | |
| });
 | |
| 
 | |
| const inboxes = useMapGetter('inboxes/getInboxes');
 | |
| const labels = useMapGetter('labels/getLabelsOnSidebar');
 | |
| const teams = useMapGetter('teams/getMyTeams');
 | |
| const contactCustomViews = useMapGetter('customViews/getContactCustomViews');
 | |
| const conversationCustomViews = useMapGetter(
 | |
|   'customViews/getConversationCustomViews'
 | |
| );
 | |
| 
 | |
| onMounted(() => {
 | |
|   store.dispatch('labels/get');
 | |
|   store.dispatch('inboxes/get');
 | |
|   store.dispatch('notifications/unReadCount');
 | |
|   store.dispatch('teams/get');
 | |
|   store.dispatch('attributes/get');
 | |
|   store.dispatch('customViews/get', 'conversation');
 | |
|   store.dispatch('customViews/get', 'contact');
 | |
| });
 | |
| 
 | |
| const sortedInboxes = computed(() =>
 | |
|   inboxes.value.slice().sort((a, b) => a.name.localeCompare(b.name))
 | |
| );
 | |
| 
 | |
| const newReportRoutes = () => [
 | |
|   {
 | |
|     name: 'Reports Agent',
 | |
|     label: t('SIDEBAR.REPORTS_AGENT'),
 | |
|     to: accountScopedRoute('agent_reports_index'),
 | |
|     activeOn: ['agent_reports_show'],
 | |
|   },
 | |
|   {
 | |
|     name: 'Reports Label',
 | |
|     label: t('SIDEBAR.REPORTS_LABEL'),
 | |
|     to: accountScopedRoute('label_reports'),
 | |
|   },
 | |
|   {
 | |
|     name: 'Reports Inbox',
 | |
|     label: t('SIDEBAR.REPORTS_INBOX'),
 | |
|     to: accountScopedRoute('inbox_reports_index'),
 | |
|     activeOn: ['inbox_reports_show'],
 | |
|   },
 | |
|   {
 | |
|     name: 'Reports Team',
 | |
|     label: t('SIDEBAR.REPORTS_TEAM'),
 | |
|     to: accountScopedRoute('team_reports_index'),
 | |
|     activeOn: ['team_reports_show'],
 | |
|   },
 | |
| ];
 | |
| 
 | |
| const reportRoutes = computed(() => newReportRoutes());
 | |
| 
 | |
| const menuItems = computed(() => {
 | |
|   return [
 | |
|     {
 | |
|       name: 'Inbox',
 | |
|       label: t('SIDEBAR.INBOX'),
 | |
|       icon: 'i-lucide-inbox',
 | |
|       to: accountScopedRoute('inbox_view'),
 | |
|       activeOn: ['inbox_view', 'inbox_view_conversation'],
 | |
|       getterKeys: {
 | |
|         badge: 'notifications/getHasUnreadNotifications',
 | |
|       },
 | |
|     },
 | |
|     {
 | |
|       name: 'Conversation',
 | |
|       label: t('SIDEBAR.CONVERSATIONS'),
 | |
|       icon: 'i-lucide-message-circle',
 | |
|       children: [
 | |
|         {
 | |
|           name: 'All',
 | |
|           label: t('SIDEBAR.ALL_CONVERSATIONS'),
 | |
|           activeOn: ['inbox_conversation'],
 | |
|           to: accountScopedRoute('home'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Mentions',
 | |
|           label: t('SIDEBAR.MENTIONED_CONVERSATIONS'),
 | |
|           activeOn: ['conversation_through_mentions'],
 | |
|           to: accountScopedRoute('conversation_mentions'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Unattended',
 | |
|           activeOn: ['conversation_through_unattended'],
 | |
|           label: t('SIDEBAR.UNATTENDED_CONVERSATIONS'),
 | |
|           to: accountScopedRoute('conversation_unattended'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Folders',
 | |
|           label: t('SIDEBAR.CUSTOM_VIEWS_FOLDER'),
 | |
|           icon: 'i-lucide-folder',
 | |
|           activeOn: ['conversations_through_folders'],
 | |
|           children: conversationCustomViews.value.map(view => ({
 | |
|             name: `${view.name}-${view.id}`,
 | |
|             label: view.name,
 | |
|             to: accountScopedRoute('folder_conversations', { id: view.id }),
 | |
|           })),
 | |
|         },
 | |
|         {
 | |
|           name: 'Teams',
 | |
|           label: t('SIDEBAR.TEAMS'),
 | |
|           icon: 'i-lucide-users',
 | |
|           activeOn: ['conversations_through_team'],
 | |
|           children: teams.value.map(team => ({
 | |
|             name: `${team.name}-${team.id}`,
 | |
|             label: team.name,
 | |
|             to: accountScopedRoute('team_conversations', { teamId: team.id }),
 | |
|           })),
 | |
|         },
 | |
|         {
 | |
|           name: 'Channels',
 | |
|           label: t('SIDEBAR.CHANNELS'),
 | |
|           icon: 'i-lucide-mailbox',
 | |
|           activeOn: ['conversation_through_inbox'],
 | |
|           children: sortedInboxes.value.map(inbox => ({
 | |
|             name: `${inbox.name}-${inbox.id}`,
 | |
|             label: inbox.name,
 | |
|             to: accountScopedRoute('inbox_dashboard', { inbox_id: inbox.id }),
 | |
|             component: leafProps =>
 | |
|               h(ChannelLeaf, {
 | |
|                 label: leafProps.label,
 | |
|                 active: leafProps.active,
 | |
|                 inbox,
 | |
|               }),
 | |
|           })),
 | |
|         },
 | |
|         {
 | |
|           name: 'Labels',
 | |
|           label: t('SIDEBAR.LABELS'),
 | |
|           icon: 'i-lucide-tag',
 | |
|           activeOn: ['conversations_through_label'],
 | |
|           children: labels.value.map(label => ({
 | |
|             name: `${label.title}-${label.id}`,
 | |
|             label: label.title,
 | |
|             icon: h('span', {
 | |
|               class: `size-[12px] ring-1 ring-n-alpha-1 dark:ring-white/20 ring-inset rounded-sm`,
 | |
|               style: { backgroundColor: label.color },
 | |
|             }),
 | |
|             to: accountScopedRoute('label_conversations', {
 | |
|               label: label.title,
 | |
|             }),
 | |
|           })),
 | |
|         },
 | |
|       ],
 | |
|     },
 | |
|     {
 | |
|       name: 'Captain',
 | |
|       icon: 'i-woot-captain',
 | |
|       label: t('SIDEBAR.CAPTAIN'),
 | |
|       children: [
 | |
|         {
 | |
|           name: 'Assistants',
 | |
|           label: t('SIDEBAR.CAPTAIN_ASSISTANTS'),
 | |
|           to: accountScopedRoute('captain_assistants_index'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Documents',
 | |
|           label: t('SIDEBAR.CAPTAIN_DOCUMENTS'),
 | |
|           to: accountScopedRoute('captain_documents_index'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Responses',
 | |
|           label: t('SIDEBAR.CAPTAIN_RESPONSES'),
 | |
|           to: accountScopedRoute('captain_responses_index'),
 | |
|         },
 | |
|       ],
 | |
|     },
 | |
|     {
 | |
|       name: 'Contacts',
 | |
|       label: t('SIDEBAR.CONTACTS'),
 | |
|       icon: 'i-lucide-contact',
 | |
|       children: [
 | |
|         {
 | |
|           name: 'All Contacts',
 | |
|           label: t('SIDEBAR.ALL_CONTACTS'),
 | |
|           to: accountScopedRoute(
 | |
|             'contacts_dashboard_index',
 | |
|             {},
 | |
|             { page: 1, search: undefined }
 | |
|           ),
 | |
|           activeOn: ['contacts_dashboard_index', 'contacts_edit'],
 | |
|         },
 | |
|         {
 | |
|           name: 'Segments',
 | |
|           icon: 'i-lucide-group',
 | |
|           label: t('SIDEBAR.CUSTOM_VIEWS_SEGMENTS'),
 | |
|           children: contactCustomViews.value.map(view => ({
 | |
|             name: `${view.name}-${view.id}`,
 | |
|             label: view.name,
 | |
|             to: accountScopedRoute(
 | |
|               'contacts_dashboard_segments_index',
 | |
|               { segmentId: view.id },
 | |
|               { page: 1 }
 | |
|             ),
 | |
|             activeOn: [
 | |
|               'contacts_dashboard_segments_index',
 | |
|               'contacts_edit_segment',
 | |
|             ],
 | |
|           })),
 | |
|         },
 | |
|         {
 | |
|           name: 'Tagged With',
 | |
|           icon: 'i-lucide-tag',
 | |
|           label: t('SIDEBAR.TAGGED_WITH'),
 | |
|           children: labels.value.map(label => ({
 | |
|             name: `${label.title}-${label.id}`,
 | |
|             label: label.title,
 | |
|             icon: h('span', {
 | |
|               class: `size-[12px] ring-1 ring-n-alpha-1 dark:ring-white/20 ring-inset rounded-sm`,
 | |
|               style: { backgroundColor: label.color },
 | |
|             }),
 | |
|             to: accountScopedRoute(
 | |
|               'contacts_dashboard_labels_index',
 | |
|               { label: label.title },
 | |
|               { page: 1, search: undefined }
 | |
|             ),
 | |
|             activeOn: [
 | |
|               'contacts_dashboard_labels_index',
 | |
|               'contacts_edit_label',
 | |
|             ],
 | |
|           })),
 | |
|         },
 | |
|       ],
 | |
|     },
 | |
|     {
 | |
|       name: 'Reports',
 | |
|       label: t('SIDEBAR.REPORTS'),
 | |
|       icon: 'i-lucide-chart-spline',
 | |
|       children: [
 | |
|         {
 | |
|           name: 'Report Overview',
 | |
|           label: t('SIDEBAR.REPORTS_OVERVIEW'),
 | |
|           to: accountScopedRoute('account_overview_reports'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Report Conversation',
 | |
|           label: t('SIDEBAR.REPORTS_CONVERSATION'),
 | |
|           to: accountScopedRoute('conversation_reports'),
 | |
|         },
 | |
|         ...reportRoutes.value,
 | |
|         {
 | |
|           name: 'Reports CSAT',
 | |
|           label: t('SIDEBAR.CSAT'),
 | |
|           to: accountScopedRoute('csat_reports'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Reports SLA',
 | |
|           label: t('SIDEBAR.REPORTS_SLA'),
 | |
|           to: accountScopedRoute('sla_reports'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Reports Bot',
 | |
|           label: t('SIDEBAR.REPORTS_BOT'),
 | |
|           to: accountScopedRoute('bot_reports'),
 | |
|         },
 | |
|       ],
 | |
|     },
 | |
|     {
 | |
|       name: 'Campaigns',
 | |
|       label: t('SIDEBAR.CAMPAIGNS'),
 | |
|       icon: 'i-lucide-megaphone',
 | |
|       children: [
 | |
|         {
 | |
|           name: 'Live chat',
 | |
|           label: t('SIDEBAR.LIVE_CHAT'),
 | |
|           to: accountScopedRoute('campaigns_livechat_index'),
 | |
|         },
 | |
|         {
 | |
|           name: 'SMS',
 | |
|           label: t('SIDEBAR.SMS'),
 | |
|           to: accountScopedRoute('campaigns_sms_index'),
 | |
|         },
 | |
|       ],
 | |
|     },
 | |
|     {
 | |
|       name: 'Portals',
 | |
|       label: t('SIDEBAR.HELP_CENTER.TITLE'),
 | |
|       icon: 'i-lucide-library-big',
 | |
|       children: [
 | |
|         {
 | |
|           name: 'Articles',
 | |
|           label: t('SIDEBAR.HELP_CENTER.ARTICLES'),
 | |
|           activeOn: [
 | |
|             'portals_articles_index',
 | |
|             'portals_articles_new',
 | |
|             'portals_articles_edit',
 | |
|           ],
 | |
|           to: accountScopedRoute('portals_index', {
 | |
|             navigationPath: 'portals_articles_index',
 | |
|           }),
 | |
|         },
 | |
|         {
 | |
|           name: 'Categories',
 | |
|           label: t('SIDEBAR.HELP_CENTER.CATEGORIES'),
 | |
|           activeOn: [
 | |
|             'portals_categories_index',
 | |
|             'portals_categories_articles_index',
 | |
|             'portals_categories_articles_edit',
 | |
|           ],
 | |
|           to: accountScopedRoute('portals_index', {
 | |
|             navigationPath: 'portals_categories_index',
 | |
|           }),
 | |
|         },
 | |
|         {
 | |
|           name: 'Locales',
 | |
|           label: t('SIDEBAR.HELP_CENTER.LOCALES'),
 | |
|           activeOn: ['portals_locales_index'],
 | |
|           to: accountScopedRoute('portals_index', {
 | |
|             navigationPath: 'portals_locales_index',
 | |
|           }),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings',
 | |
|           label: t('SIDEBAR.HELP_CENTER.SETTINGS'),
 | |
|           activeOn: ['portals_settings_index'],
 | |
|           to: accountScopedRoute('portals_index', {
 | |
|             navigationPath: 'portals_settings_index',
 | |
|           }),
 | |
|         },
 | |
|       ],
 | |
|     },
 | |
|     {
 | |
|       name: 'Settings',
 | |
|       label: t('SIDEBAR.SETTINGS'),
 | |
|       icon: 'i-lucide-bolt',
 | |
|       children: [
 | |
|         {
 | |
|           name: 'Settings Account Settings',
 | |
|           label: t('SIDEBAR.ACCOUNT_SETTINGS'),
 | |
|           icon: 'i-lucide-briefcase',
 | |
|           to: accountScopedRoute('general_settings_index'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings Agents',
 | |
|           label: t('SIDEBAR.AGENTS'),
 | |
|           icon: 'i-lucide-square-user',
 | |
|           to: accountScopedRoute('agent_list'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings Teams',
 | |
|           label: t('SIDEBAR.TEAMS'),
 | |
|           icon: 'i-lucide-users',
 | |
|           to: accountScopedRoute('settings_teams_list'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings Inboxes',
 | |
|           label: t('SIDEBAR.INBOXES'),
 | |
|           icon: 'i-lucide-inbox',
 | |
|           to: accountScopedRoute('settings_inbox_list'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings Labels',
 | |
|           label: t('SIDEBAR.LABELS'),
 | |
|           icon: 'i-lucide-tags',
 | |
|           to: accountScopedRoute('labels_list'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings Custom Attributes',
 | |
|           label: t('SIDEBAR.CUSTOM_ATTRIBUTES'),
 | |
|           icon: 'i-lucide-code',
 | |
|           to: accountScopedRoute('attributes_list'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings Automation',
 | |
|           label: t('SIDEBAR.AUTOMATION'),
 | |
|           icon: 'i-lucide-workflow',
 | |
|           to: accountScopedRoute('automation_list'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings Agent Bots',
 | |
|           label: t('SIDEBAR.AGENT_BOTS'),
 | |
|           icon: 'i-lucide-bot',
 | |
|           to: accountScopedRoute('agent_bots'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings Macros',
 | |
|           label: t('SIDEBAR.MACROS'),
 | |
|           icon: 'i-lucide-toy-brick',
 | |
|           to: accountScopedRoute('macros_wrapper'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings Canned Responses',
 | |
|           label: t('SIDEBAR.CANNED_RESPONSES'),
 | |
|           icon: 'i-lucide-message-square-quote',
 | |
|           to: accountScopedRoute('canned_list'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings Integrations',
 | |
|           label: t('SIDEBAR.INTEGRATIONS'),
 | |
|           icon: 'i-lucide-blocks',
 | |
|           to: accountScopedRoute('settings_applications'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings Audit Logs',
 | |
|           label: t('SIDEBAR.AUDIT_LOGS'),
 | |
|           icon: 'i-lucide-briefcase',
 | |
|           to: accountScopedRoute('auditlogs_list'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings Custom Roles',
 | |
|           label: t('SIDEBAR.CUSTOM_ROLES'),
 | |
|           icon: 'i-lucide-shield-plus',
 | |
|           to: accountScopedRoute('custom_roles_list'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings Sla',
 | |
|           label: t('SIDEBAR.SLA'),
 | |
|           icon: 'i-lucide-clock-alert',
 | |
|           to: accountScopedRoute('sla_list'),
 | |
|         },
 | |
|         {
 | |
|           name: 'Settings Billing',
 | |
|           label: t('SIDEBAR.BILLING'),
 | |
|           icon: 'i-lucide-credit-card',
 | |
|           to: accountScopedRoute('billing_settings_index'),
 | |
|         },
 | |
|       ],
 | |
|     },
 | |
|   ];
 | |
| });
 | |
| </script>
 | |
| 
 | |
| <template>
 | |
|   <aside
 | |
|     class="w-[12.5rem] bg-n-solid-2 rtl:border-l ltr:border-r border-n-weak h-screen flex flex-col text-sm pb-1"
 | |
|   >
 | |
|     <section class="grid gap-2 mt-2 mb-4">
 | |
|       <div class="flex items-center min-w-0 gap-2 px-2">
 | |
|         <div class="grid flex-shrink-0 size-6 place-content-center">
 | |
|           <Logo class="size-4" />
 | |
|         </div>
 | |
|         <div class="flex-shrink-0 w-px h-3 bg-n-strong" />
 | |
|         <SidebarAccountSwitcher
 | |
|           class="flex-grow min-w-0 -mx-1"
 | |
|           @show-create-account-modal="emit('showCreateAccountModal')"
 | |
|         />
 | |
|       </div>
 | |
|       <div class="flex gap-2 px-2">
 | |
|         <RouterLink
 | |
|           :to="{ name: 'search' }"
 | |
|           class="flex items-center w-full gap-2 px-2 py-1 rounded-lg h-7 outline outline-1 outline-n-weak bg-n-solid-3 dark:bg-n-black/30"
 | |
|         >
 | |
|           <span class="flex-shrink-0 i-lucide-search size-4 text-n-slate-11" />
 | |
|           <span class="flex-grow text-left">
 | |
|             {{ t('COMBOBOX.SEARCH_PLACEHOLDER') }}
 | |
|           </span>
 | |
|           <span
 | |
|             class="hidden tracking-wide pointer-events-none select-none text-n-slate-10"
 | |
|           >
 | |
|             {{ searchShortcut }}
 | |
|           </span>
 | |
|         </RouterLink>
 | |
|         <ComposeConversation align-position="right">
 | |
|           <template #trigger="{ toggle }">
 | |
|             <Button
 | |
|               icon="i-lucide-pen-line"
 | |
|               color="slate"
 | |
|               size="sm"
 | |
|               class="!h-7 !bg-n-solid-3 dark:!bg-n-black/30 !outline-n-weak !text-n-slate-11"
 | |
|               @click="toggle"
 | |
|             />
 | |
|           </template>
 | |
|         </ComposeConversation>
 | |
|       </div>
 | |
|     </section>
 | |
|     <nav class="grid flex-grow gap-2 px-2 pb-5 overflow-y-scroll no-scrollbar">
 | |
|       <ul class="flex flex-col gap-1.5 m-0 list-none">
 | |
|         <SidebarGroup
 | |
|           v-for="item in menuItems"
 | |
|           :key="item.name"
 | |
|           v-bind="item"
 | |
|         />
 | |
|       </ul>
 | |
|     </nav>
 | |
|     <section
 | |
|       class="p-1 border-t border-n-weak shadow-[0px_-2px_4px_0px_rgba(27,28,29,0.02)] flex-shrink-0 flex justify-between gap-2 items-center"
 | |
|     >
 | |
|       <SidebarProfileMenu
 | |
|         @open-key-shortcut-modal="emit('openKeyShortcutModal')"
 | |
|       />
 | |
|     </section>
 | |
|   </aside>
 | |
| </template>
 |