# Pull Request Template ## Description Adds custom tool support to v1 ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. <img width="1816" height="958" alt="CleanShot 2026-03-24 at 11 37 33@2x" src="https://github.com/user-attachments/assets/2777a953-8b65-4a2d-88ec-39f395b3fb47" /> <img width="378" height="488" alt="CleanShot 2026-03-24 at 11 38 18@2x" src="https://github.com/user-attachments/assets/f6973c99-efd0-40e4-90fe-4472a2f63cea" /> <img width="1884" height="1452" alt="CleanShot 2026-03-24 at 11 38 32@2x" src="https://github.com/user-attachments/assets/9fba4fc4-0c33-46da-888a-52ec6bad6130" /> ## Checklist: - [x] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
891 lines
27 KiB
Vue
891 lines
27 KiB
Vue
<script setup>
|
|
import { h, ref, computed, onMounted } from 'vue';
|
|
import { provideSidebarContext, useSidebarResize } 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 { useSidebarKeyboardShortcuts } from './useSidebarKeyboardShortcuts';
|
|
import { vOnClickOutside } from '@vueuse/components';
|
|
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
|
import { useWindowSize, useEventListener } from '@vueuse/core';
|
|
import { emitter } from 'shared/helpers/mitt';
|
|
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
|
|
|
import Button from 'dashboard/components-next/button/Button.vue';
|
|
import SidebarGroup from './SidebarGroup.vue';
|
|
import SidebarProfileMenu from './SidebarProfileMenu.vue';
|
|
import SidebarChangelogCard from './SidebarChangelogCard.vue';
|
|
import SidebarChangelogButton from './SidebarChangelogButton.vue';
|
|
import ChannelLeaf from './ChannelLeaf.vue';
|
|
import ChannelIcon from 'next/icon/ChannelIcon.vue';
|
|
import SidebarAccountSwitcher from './SidebarAccountSwitcher.vue';
|
|
import Logo from 'next/icon/Logo.vue';
|
|
import ComposeConversation from 'dashboard/components-next/NewConversation/ComposeConversation.vue';
|
|
|
|
const props = defineProps({
|
|
isMobileSidebarOpen: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits([
|
|
'closeKeyShortcutModal',
|
|
'openKeyShortcutModal',
|
|
'showCreateAccountModal',
|
|
'closeMobileSidebar',
|
|
]);
|
|
|
|
const { accountScopedRoute, isOnChatwootCloud } = useAccount();
|
|
const store = useStore();
|
|
const searchShortcut = useKbd([`$mod`, 'k']);
|
|
const { t } = useI18n();
|
|
|
|
const isACustomBrandedInstance = useMapGetter(
|
|
'globalConfig/isACustomBrandedInstance'
|
|
);
|
|
const isRTL = useMapGetter('accounts/isRTL');
|
|
|
|
const { width: windowWidth } = useWindowSize();
|
|
const isMobile = computed(() => windowWidth.value < 768);
|
|
|
|
const accountId = useMapGetter('getCurrentAccountId');
|
|
const isFeatureEnabledonAccount = useMapGetter(
|
|
'accounts/isFeatureEnabledonAccount'
|
|
);
|
|
|
|
const hasAdvancedAssignment = computed(() => {
|
|
return isFeatureEnabledonAccount.value(
|
|
accountId.value,
|
|
FEATURE_FLAGS.ADVANCED_ASSIGNMENT
|
|
);
|
|
});
|
|
|
|
const hasCustomTools = computed(() => {
|
|
return (
|
|
isFeatureEnabledonAccount.value(
|
|
accountId.value,
|
|
FEATURE_FLAGS.CAPTAIN_CUSTOM_TOOLS
|
|
) ||
|
|
isFeatureEnabledonAccount.value(accountId.value, FEATURE_FLAGS.CAPTAIN_V2)
|
|
);
|
|
});
|
|
|
|
const toggleShortcutModalFn = show => {
|
|
if (show) {
|
|
emit('openKeyShortcutModal');
|
|
} else {
|
|
emit('closeKeyShortcutModal');
|
|
}
|
|
};
|
|
|
|
useSidebarKeyboardShortcuts(toggleShortcutModalFn);
|
|
|
|
const expandedItem = ref(null);
|
|
|
|
const setExpandedItem = name => {
|
|
expandedItem.value = expandedItem.value === name ? null : name;
|
|
};
|
|
|
|
const {
|
|
sidebarWidth,
|
|
isCollapsed,
|
|
setSidebarWidth,
|
|
saveWidth,
|
|
snapToCollapsed,
|
|
snapToExpanded,
|
|
COLLAPSED_THRESHOLD,
|
|
} = useSidebarResize();
|
|
|
|
// On mobile, sidebar is always expanded (flyout mode)
|
|
const isEffectivelyCollapsed = computed(
|
|
() => !isMobile.value && isCollapsed.value
|
|
);
|
|
|
|
// Resize handle logic
|
|
const isResizing = ref(false);
|
|
const startX = ref(0);
|
|
const startWidth = ref(0);
|
|
|
|
provideSidebarContext({
|
|
expandedItem,
|
|
setExpandedItem,
|
|
isCollapsed: isEffectivelyCollapsed,
|
|
sidebarWidth,
|
|
isResizing,
|
|
});
|
|
|
|
// Get clientX from mouse or touch event
|
|
const getClientX = event =>
|
|
event.touches ? event.touches[0].clientX : event.clientX;
|
|
|
|
const onResizeStart = event => {
|
|
isResizing.value = true;
|
|
startX.value = getClientX(event);
|
|
startWidth.value = sidebarWidth.value;
|
|
Object.assign(document.body.style, {
|
|
cursor: 'col-resize',
|
|
userSelect: 'none',
|
|
});
|
|
// Prevent default to avoid scrolling on touch
|
|
event.preventDefault();
|
|
};
|
|
|
|
const onResizeMove = event => {
|
|
if (!isResizing.value) return;
|
|
|
|
const delta = isRTL.value
|
|
? startX.value - getClientX(event)
|
|
: getClientX(event) - startX.value;
|
|
setSidebarWidth(startWidth.value + delta);
|
|
};
|
|
|
|
const onResizeEnd = () => {
|
|
if (!isResizing.value) return;
|
|
|
|
isResizing.value = false;
|
|
Object.assign(document.body.style, { cursor: '', userSelect: '' });
|
|
|
|
// Snap to collapsed state if below threshold
|
|
if (sidebarWidth.value < COLLAPSED_THRESHOLD) {
|
|
snapToCollapsed();
|
|
} else {
|
|
saveWidth();
|
|
}
|
|
};
|
|
|
|
const onResizeHandleDoubleClick = () => {
|
|
if (isCollapsed.value) snapToExpanded();
|
|
else snapToCollapsed();
|
|
};
|
|
|
|
// Support both mouse and touch events
|
|
useEventListener(document, 'mousemove', onResizeMove);
|
|
useEventListener(document, 'mouseup', onResizeEnd);
|
|
useEventListener(document, 'touchmove', onResizeMove, { passive: false });
|
|
useEventListener(document, 'touchend', onResizeEnd);
|
|
|
|
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 closeMobileSidebar = () => {
|
|
if (!props.isMobileSidebarOpen) return;
|
|
emit('closeMobileSidebar');
|
|
};
|
|
|
|
const onComposeOpen = toggleFn => {
|
|
toggleFn();
|
|
emitter.emit(BUS_EVENTS.NEW_CONVERSATION_MODAL, true);
|
|
};
|
|
|
|
const onComposeClose = () => {
|
|
emitter.emit(BUS_EVENTS.NEW_CONVERSATION_MODAL, false);
|
|
};
|
|
|
|
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_index'),
|
|
},
|
|
{
|
|
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: {
|
|
count: 'notifications/getUnreadCount',
|
|
},
|
|
},
|
|
{
|
|
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,
|
|
icon: h(ChannelIcon, { inbox, class: 'size-[16px]' }),
|
|
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-[8px] rounded-sm`,
|
|
style: { backgroundColor: label.color },
|
|
}),
|
|
to: accountScopedRoute('label_conversations', {
|
|
label: label.title,
|
|
}),
|
|
})),
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: 'Captain',
|
|
icon: 'i-woot-captain',
|
|
label: t('SIDEBAR.CAPTAIN'),
|
|
activeOn: ['captain_assistants_create_index'],
|
|
children: [
|
|
{
|
|
name: 'FAQs',
|
|
label: t('SIDEBAR.CAPTAIN_RESPONSES'),
|
|
activeOn: [
|
|
'captain_assistants_responses_index',
|
|
'captain_assistants_responses_pending',
|
|
],
|
|
to: accountScopedRoute('captain_assistants_index', {
|
|
navigationPath: 'captain_assistants_responses_index',
|
|
}),
|
|
},
|
|
{
|
|
name: 'Documents',
|
|
label: t('SIDEBAR.CAPTAIN_DOCUMENTS'),
|
|
activeOn: ['captain_assistants_documents_index'],
|
|
to: accountScopedRoute('captain_assistants_index', {
|
|
navigationPath: 'captain_assistants_documents_index',
|
|
}),
|
|
},
|
|
{
|
|
name: 'Scenarios',
|
|
label: t('SIDEBAR.CAPTAIN_SCENARIOS'),
|
|
activeOn: ['captain_assistants_scenarios_index'],
|
|
to: accountScopedRoute('captain_assistants_index', {
|
|
navigationPath: 'captain_assistants_scenarios_index',
|
|
}),
|
|
},
|
|
{
|
|
name: 'Playground',
|
|
label: t('SIDEBAR.CAPTAIN_PLAYGROUND'),
|
|
activeOn: ['captain_assistants_playground_index'],
|
|
to: accountScopedRoute('captain_assistants_index', {
|
|
navigationPath: 'captain_assistants_playground_index',
|
|
}),
|
|
},
|
|
{
|
|
name: 'Inboxes',
|
|
label: t('SIDEBAR.CAPTAIN_INBOXES'),
|
|
activeOn: ['captain_assistants_inboxes_index'],
|
|
to: accountScopedRoute('captain_assistants_index', {
|
|
navigationPath: 'captain_assistants_inboxes_index',
|
|
}),
|
|
},
|
|
...(hasCustomTools.value
|
|
? [
|
|
{
|
|
name: 'Tools',
|
|
label: t('SIDEBAR.CAPTAIN_TOOLS'),
|
|
activeOn: ['captain_tools_index'],
|
|
to: accountScopedRoute('captain_assistants_index', {
|
|
navigationPath: 'captain_tools_index',
|
|
}),
|
|
},
|
|
]
|
|
: []),
|
|
{
|
|
name: 'Settings',
|
|
label: t('SIDEBAR.CAPTAIN_SETTINGS'),
|
|
activeOn: [
|
|
'captain_assistants_settings_index',
|
|
'captain_assistants_guidelines_index',
|
|
'captain_assistants_guardrails_index',
|
|
],
|
|
to: accountScopedRoute('captain_assistants_index', {
|
|
navigationPath: 'captain_assistants_settings_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: 'Active',
|
|
label: t('SIDEBAR.ACTIVE'),
|
|
to: accountScopedRoute('contacts_dashboard_active'),
|
|
activeOn: ['contacts_dashboard_active'],
|
|
},
|
|
{
|
|
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-[8px] 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: 'Companies',
|
|
label: t('SIDEBAR.COMPANIES'),
|
|
icon: 'i-lucide-building-2',
|
|
children: [
|
|
{
|
|
name: 'All Companies',
|
|
label: t('SIDEBAR.ALL_COMPANIES'),
|
|
to: accountScopedRoute(
|
|
'companies_dashboard_index',
|
|
{},
|
|
{ page: 1, search: undefined }
|
|
),
|
|
activeOn: ['companies_dashboard_index'],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
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: 'WhatsApp',
|
|
label: t('SIDEBAR.WHATSAPP'),
|
|
to: accountScopedRoute('campaigns_whatsapp_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 Captain',
|
|
// label: t('SIDEBAR.CAPTAIN_AI'),
|
|
// icon: 'i-woot-captain',
|
|
// to: accountScopedRoute('captain_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',
|
|
activeOn: [
|
|
'settings_teams_list',
|
|
'settings_teams_new',
|
|
'settings_teams_finish',
|
|
'settings_teams_add_agents',
|
|
'settings_teams_show',
|
|
'settings_teams_edit',
|
|
'settings_teams_edit_members',
|
|
'settings_teams_edit_finish',
|
|
],
|
|
to: accountScopedRoute('settings_teams_list'),
|
|
},
|
|
...(hasAdvancedAssignment.value
|
|
? [
|
|
{
|
|
name: 'Settings Agent Assignment',
|
|
label: t('SIDEBAR.AGENT_ASSIGNMENT'),
|
|
icon: 'i-lucide-user-cog',
|
|
activeOn: [
|
|
'assignment_policy_index',
|
|
'agent_assignment_policy_index',
|
|
'agent_assignment_policy_create',
|
|
'agent_assignment_policy_edit',
|
|
'agent_capacity_policy_index',
|
|
'agent_capacity_policy_create',
|
|
'agent_capacity_policy_edit',
|
|
],
|
|
to: accountScopedRoute('assignment_policy_index'),
|
|
},
|
|
]
|
|
: []),
|
|
{
|
|
name: 'Settings Inboxes',
|
|
label: t('SIDEBAR.INBOXES'),
|
|
icon: 'i-lucide-inbox',
|
|
activeOn: [
|
|
'settings_inbox_list',
|
|
'settings_inbox_show',
|
|
'settings_inbox_new',
|
|
'settings_inbox_finish',
|
|
'settings_inboxes_page_channel',
|
|
'settings_inboxes_add_agents',
|
|
],
|
|
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-repeat',
|
|
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: 'Conversation Workflow',
|
|
label: t('SIDEBAR.CONVERSATION_WORKFLOW'),
|
|
icon: 'i-lucide-workflow',
|
|
to: accountScopedRoute('conversation_workflow_index'),
|
|
},
|
|
{
|
|
name: 'Settings Security',
|
|
label: t('SIDEBAR.SECURITY'),
|
|
icon: 'i-lucide-shield',
|
|
to: accountScopedRoute('security_settings_index'),
|
|
},
|
|
{
|
|
name: 'Settings Billing',
|
|
label: t('SIDEBAR.BILLING'),
|
|
icon: 'i-lucide-credit-card',
|
|
to: accountScopedRoute('billing_settings_index'),
|
|
},
|
|
],
|
|
},
|
|
];
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<aside
|
|
v-on-click-outside="[
|
|
closeMobileSidebar,
|
|
{ ignore: ['#mobile-sidebar-launcher'] },
|
|
]"
|
|
class="bg-n-background flex flex-col text-sm pb-px fixed top-0 ltr:left-0 rtl:right-0 h-full z-40 w-[200px] md:w-auto md:relative md:flex-shrink-0 md:ltr:translate-x-0 md:rtl:translate-x-0 ltr:border-r rtl:border-l border-n-weak"
|
|
:class="[
|
|
{
|
|
'shadow-lg md:shadow-none': isMobileSidebarOpen,
|
|
'ltr:-translate-x-full rtl:translate-x-full': !isMobileSidebarOpen,
|
|
'transition-transform duration-200 ease-out md:transition-[width]':
|
|
!isResizing,
|
|
},
|
|
]"
|
|
:style="isMobile ? undefined : { width: `${sidebarWidth}px` }"
|
|
>
|
|
<section
|
|
class="grid"
|
|
:class="isEffectivelyCollapsed ? 'mt-3 mb-6 gap-4' : 'mt-1 mb-4 gap-2'"
|
|
>
|
|
<div
|
|
class="flex gap-2 items-center min-w-0"
|
|
:class="{
|
|
'justify-center px-1': isEffectivelyCollapsed,
|
|
'px-2': !isEffectivelyCollapsed,
|
|
}"
|
|
>
|
|
<template v-if="isEffectivelyCollapsed">
|
|
<SidebarAccountSwitcher
|
|
is-collapsed
|
|
@show-create-account-modal="emit('showCreateAccountModal')"
|
|
/>
|
|
</template>
|
|
<template v-else>
|
|
<div class="grid flex-shrink-0 place-content-center size-6">
|
|
<Logo class="size-4" />
|
|
</div>
|
|
<div class="flex-shrink-0 w-px h-3 bg-n-strong" />
|
|
<SidebarAccountSwitcher
|
|
class="flex-grow -mx-1 min-w-0"
|
|
@show-create-account-modal="emit('showCreateAccountModal')"
|
|
/>
|
|
</template>
|
|
</div>
|
|
<div
|
|
class="flex gap-2"
|
|
:class="isEffectivelyCollapsed ? 'flex-col items-center' : 'px-2'"
|
|
>
|
|
<RouterLink
|
|
v-if="!isEffectivelyCollapsed"
|
|
:to="{ name: 'search' }"
|
|
class="flex gap-2 items-center px-2 py-1 w-full h-7 rounded-lg outline outline-1 outline-n-weak bg-n-button-color transition-all duration-100 ease-out"
|
|
>
|
|
<span class="flex-shrink-0 i-lucide-search size-4 text-n-slate-10" />
|
|
<span class="flex-grow text-start text-n-slate-10">
|
|
{{ t('COMBOBOX.SEARCH_PLACEHOLDER') }}
|
|
</span>
|
|
<span
|
|
class="hidden tracking-wide pointer-events-none select-none text-n-slate-10"
|
|
>
|
|
{{ searchShortcut }}
|
|
</span>
|
|
</RouterLink>
|
|
<RouterLink
|
|
v-else
|
|
:to="{ name: 'search' }"
|
|
class="flex items-center justify-center size-8 rounded-lg outline outline-1 outline-n-weak bg-n-button-color transition-all duration-100 ease-out hover:bg-n-alpha-2 dark:hover:bg-n-slate-9/30"
|
|
:title="t('COMBOBOX.SEARCH_PLACEHOLDER')"
|
|
>
|
|
<span class="i-lucide-search size-4 text-n-slate-11" />
|
|
</RouterLink>
|
|
<ComposeConversation align-position="right" @close="onComposeClose">
|
|
<template #trigger="{ toggle, isOpen }">
|
|
<Button
|
|
icon="i-lucide-pen-line"
|
|
color="slate"
|
|
size="sm"
|
|
class="dark:hover:!bg-n-slate-9/30"
|
|
:class="[
|
|
isEffectivelyCollapsed
|
|
? '!size-8 !outline-n-weak !text-n-slate-11'
|
|
: '!h-7 !outline-n-weak !text-n-slate-11',
|
|
{ '!bg-n-alpha-2 dark:!bg-n-slate-9/30': isOpen },
|
|
]"
|
|
@click="onComposeOpen(toggle)"
|
|
/>
|
|
</template>
|
|
</ComposeConversation>
|
|
</div>
|
|
</section>
|
|
<nav
|
|
class="grid overflow-y-scroll flex-grow gap-2 pb-5 no-scrollbar min-w-0"
|
|
:class="isEffectivelyCollapsed ? 'px-1' : 'px-2'"
|
|
>
|
|
<ul
|
|
class="flex flex-col gap-1 m-0 list-none min-w-0"
|
|
:class="{ 'items-center': isEffectivelyCollapsed }"
|
|
>
|
|
<SidebarGroup
|
|
v-for="item in menuItems"
|
|
:key="item.name"
|
|
v-bind="item"
|
|
/>
|
|
</ul>
|
|
</nav>
|
|
<section
|
|
class="flex relative flex-col flex-shrink-0 gap-1 justify-between items-center"
|
|
>
|
|
<div
|
|
class="pointer-events-none absolute inset-x-0 -top-[1.938rem] h-8 bg-gradient-to-t from-n-background to-transparent"
|
|
/>
|
|
<SidebarChangelogCard
|
|
v-if="
|
|
isOnChatwootCloud &&
|
|
!isACustomBrandedInstance &&
|
|
!isEffectivelyCollapsed
|
|
"
|
|
/>
|
|
<SidebarChangelogButton
|
|
v-if="
|
|
isOnChatwootCloud &&
|
|
!isACustomBrandedInstance &&
|
|
isEffectivelyCollapsed
|
|
"
|
|
/>
|
|
<div
|
|
class="px-1 py-1.5 flex-shrink-0 flex w-full z-50 gap-2 items-center border-t border-n-weak shadow-[0px_-2px_4px_0px_rgba(27,28,29,0.02)]"
|
|
:class="isEffectivelyCollapsed ? 'justify-center' : 'justify-between'"
|
|
>
|
|
<SidebarProfileMenu
|
|
:is-collapsed="isEffectivelyCollapsed"
|
|
@open-key-shortcut-modal="emit('openKeyShortcutModal')"
|
|
/>
|
|
</div>
|
|
</section>
|
|
<!-- Resize Handle (desktop only) -->
|
|
<div
|
|
class="hidden md:block absolute top-0 h-full w-1 cursor-col-resize z-40 ltr:right-0 rtl:left-0 group"
|
|
@mousedown="onResizeStart"
|
|
@touchstart="onResizeStart"
|
|
@dblclick="onResizeHandleDoubleClick"
|
|
>
|
|
<div
|
|
class="absolute top-0 h-full w-px ltr:right-0 rtl:left-0 bg-transparent group-hover:bg-n-brand transition-colors"
|
|
:class="{ 'bg-n-brand': isResizing }"
|
|
/>
|
|
</div>
|
|
</aside>
|
|
</template>
|