mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 19:48:08 +00:00
feat(v4): Auto-navigate to first menu item on group menu open(#10350)
Ensures users are seamlessly directed to the first available menu item upon opening a group, improving UX by reducing unnecessary clicks. This change enhances navigation flow within groups. Co-authored-by: Pranav <pranavrajs@gmail.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { computed, watch, ref } from 'vue';
|
||||
import { useSidebarContext } from './provider';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import Policy from 'dashboard/components/policy.vue';
|
||||
import SidebarGroupHeader from './SidebarGroupHeader.vue';
|
||||
import SidebarGroupLeaf from './SidebarGroupLeaf.vue';
|
||||
@@ -41,7 +41,7 @@ const navigableChildren = computed(() => {
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const router = useRouter();
|
||||
const isExpanded = computed(() => expandedItem.value === props.name);
|
||||
const isExpandable = computed(() => props.children);
|
||||
const hasChildren = computed(
|
||||
@@ -53,10 +53,7 @@ const accessibleItems = computed(() => {
|
||||
return props.children.filter(child => isAllowed(child.to));
|
||||
});
|
||||
|
||||
const hasAccessibleItems = computed(() => {
|
||||
// default true so that rendering is not blocked
|
||||
if (!hasChildren.value) return true;
|
||||
|
||||
const hasAccessibleChildren = computed(() => {
|
||||
return accessibleItems.value.length > 0;
|
||||
});
|
||||
|
||||
@@ -93,6 +90,19 @@ const hasActiveChild = computed(() => {
|
||||
return activeChild.value !== undefined;
|
||||
});
|
||||
|
||||
const toggleTrigger = () => {
|
||||
if (
|
||||
hasAccessibleChildren.value &&
|
||||
!isExpanded.value &&
|
||||
!hasActiveChild.value
|
||||
) {
|
||||
// if not already expanded, navigate to the first child
|
||||
const firstItem = accessibleItems.value[0];
|
||||
router.push(firstItem.to);
|
||||
}
|
||||
setExpandedItem(props.name);
|
||||
};
|
||||
|
||||
watch(expandedItem, locateLastChild, {
|
||||
immediate: true,
|
||||
});
|
||||
@@ -101,7 +111,7 @@ watch(expandedItem, locateLastChild, {
|
||||
<!-- eslint-disable-next-line vue/no-root-v-if -->
|
||||
<template>
|
||||
<Policy
|
||||
v-if="hasAccessibleItems"
|
||||
v-if="!hasChildren || hasAccessibleChildren"
|
||||
:permissions="resolvePermissions(to)"
|
||||
:feature-flag="resolveFeatureFlag(to)"
|
||||
as="li"
|
||||
@@ -116,7 +126,7 @@ watch(expandedItem, locateLastChild, {
|
||||
:has-active-child="hasActiveChild"
|
||||
:expandable="hasChildren"
|
||||
:is-expanded="isExpanded"
|
||||
@toggle="setExpandedItem(name)"
|
||||
@toggle="toggleTrigger"
|
||||
/>
|
||||
<ul
|
||||
v-if="hasChildren"
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
CONTACT_PERMISSIONS,
|
||||
} from 'dashboard/constants/permissions.js';
|
||||
|
||||
const SearchView = () => import('./components/SearchView.vue');
|
||||
import SearchView from './components/SearchView.vue';
|
||||
|
||||
export const routes = [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint arrow-body-style: 0 */
|
||||
import { frontendURL } from '../../../helper/URLHelper';
|
||||
const ContactsView = () => import('./components/ContactsView.vue');
|
||||
const ContactManageView = () => import('./pages/ContactManageView.vue');
|
||||
import ContactsView from './components/ContactsView.vue';
|
||||
import ContactManageView from './pages/ContactManageView.vue';
|
||||
|
||||
export const routes = [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint arrow-body-style: 0 */
|
||||
import { frontendURL } from '../../../helper/URLHelper';
|
||||
const ConversationView = () => import('./ConversationView.vue');
|
||||
import ConversationView from './ConversationView.vue';
|
||||
|
||||
const CONVERSATION_PERMISSIONS = [
|
||||
'administrator',
|
||||
|
||||
@@ -9,9 +9,9 @@ import helpcenterRoutes from './helpcenter/helpcenter.routes';
|
||||
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
|
||||
const AppContainer = () => import('./Dashboard.vue');
|
||||
const Captain = () => import('./Captain.vue');
|
||||
const Suspended = () => import('./suspended/Index.vue');
|
||||
import AppContainer from './Dashboard.vue';
|
||||
import Captain from './Captain.vue';
|
||||
import Suspended from './suspended/Index.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -2,8 +2,8 @@ import { getPortalRoute } from './helpers/routeHelper';
|
||||
|
||||
import HelpCenterPageRouteView from './pages/HelpCenterPageRouteView.vue';
|
||||
|
||||
const PortalsIndex = () => import('./pages/PortalsIndexPage.vue');
|
||||
const PortalsNew = () => import('./pages/PortalsNewPage.vue');
|
||||
import PortalsIndex from './pages/PortalsIndexPage.vue';
|
||||
import PortalsNew from './pages/PortalsNewPage.vue';
|
||||
|
||||
const PortalsArticlesIndexPage = () =>
|
||||
import('./pages/PortalsArticlesIndexPage.vue');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||
const InboxListView = () => import('./InboxList.vue');
|
||||
const InboxDetailView = () => import('./InboxView.vue');
|
||||
const InboxEmptyStateView = () => import('./InboxEmptyState.vue');
|
||||
import InboxListView from './InboxList.vue';
|
||||
import InboxDetailView from './InboxView.vue';
|
||||
import InboxEmptyStateView from './InboxEmptyState.vue';
|
||||
import {
|
||||
ROLES,
|
||||
CONVERSATION_PERMISSIONS,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint arrow-body-style: 0 */
|
||||
import { frontendURL } from '../../../helper/URLHelper';
|
||||
const SettingsWrapper = () => import('../settings/Wrapper.vue');
|
||||
const NotificationsView = () => import('./components/NotificationsView.vue');
|
||||
import SettingsWrapper from '../settings/Wrapper.vue';
|
||||
import NotificationsView from './components/NotificationsView.vue';
|
||||
|
||||
export const routes = [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
const SettingsContent = () => import('../Wrapper.vue');
|
||||
const Index = () => import('./Index.vue');
|
||||
import SettingsContent from '../Wrapper.vue';
|
||||
import Index from './Index.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const Bot = () => import('./Index.vue');
|
||||
const CsmlEditBot = () => import('./csml/Edit.vue');
|
||||
const CsmlNewBot = () => import('./csml/New.vue');
|
||||
import Bot from './Index.vue';
|
||||
import CsmlEditBot from './csml/Edit.vue';
|
||||
import CsmlNewBot from './csml/New.vue';
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
const SettingsContent = () => import('../Wrapper.vue');
|
||||
import SettingsContent from '../Wrapper.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
const SettingsWrapper = () => import('../SettingsWrapper.vue');
|
||||
const AgentHome = () => import('./Index.vue');
|
||||
import SettingsWrapper from '../SettingsWrapper.vue';
|
||||
import AgentHome from './Index.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
const SettingsWrapper = () => import('../SettingsWrapper.vue');
|
||||
const AttributesHome = () => import('./Index.vue');
|
||||
import SettingsWrapper from '../SettingsWrapper.vue';
|
||||
import AttributesHome from './Index.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
|
||||
const SettingsWrapper = () => import('../SettingsWrapper.vue');
|
||||
const AuditLogsHome = () => import('./Index.vue');
|
||||
import SettingsWrapper from '../SettingsWrapper.vue';
|
||||
import AuditLogsHome from './Index.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
const SettingsWrapper = () => import('../SettingsWrapper.vue');
|
||||
const Automation = () => import('./Index.vue');
|
||||
import SettingsWrapper from '../SettingsWrapper.vue';
|
||||
import Automation from './Index.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
const SettingsContent = () => import('../Wrapper.vue');
|
||||
const Index = () => import('./Index.vue');
|
||||
import SettingsContent from '../Wrapper.vue';
|
||||
import Index from './Index.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
const SettingsContent = () => import('../Wrapper.vue');
|
||||
const Index = () => import('./Index.vue');
|
||||
import SettingsContent from '../Wrapper.vue';
|
||||
import Index from './Index.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
ROLES,
|
||||
CONVERSATION_PERMISSIONS,
|
||||
} from 'dashboard/constants/permissions.js';
|
||||
const SettingsWrapper = () => import('../SettingsWrapper.vue');
|
||||
const CannedHome = () => import('./Index.vue');
|
||||
import SettingsWrapper from '../SettingsWrapper.vue';
|
||||
import CannedHome from './Index.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||
|
||||
const SettingsWrapper = () => import('../SettingsWrapper.vue');
|
||||
const CustomRolesHome = () => import('./Index.vue');
|
||||
import SettingsWrapper from '../SettingsWrapper.vue';
|
||||
import CustomRolesHome from './Index.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
import ChannelFactory from './ChannelFactory.vue';
|
||||
|
||||
const SettingsContent = () => import('../Wrapper.vue');
|
||||
const SettingWrapper = () => import('../SettingsWrapper.vue');
|
||||
const InboxHome = () => import('./Index.vue');
|
||||
const Settings = () => import('./Settings.vue');
|
||||
const InboxChannel = () => import('./InboxChannels.vue');
|
||||
const ChannelList = () => import('./ChannelList.vue');
|
||||
const AddAgents = () => import('./AddAgents.vue');
|
||||
const FinishSetup = () => import('./FinishSetup.vue');
|
||||
import SettingsContent from '../Wrapper.vue';
|
||||
import SettingWrapper from '../SettingsWrapper.vue';
|
||||
import InboxHome from './Index.vue';
|
||||
import Settings from './Settings.vue';
|
||||
import InboxChannel from './InboxChannels.vue';
|
||||
import ChannelList from './ChannelList.vue';
|
||||
import AddAgents from './AddAgents.vue';
|
||||
import FinishSetup from './FinishSetup.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
const SettingsWrapper = () => import('../SettingsWrapper.vue');
|
||||
const IntegrationHooks = () => import('./IntegrationHooks.vue');
|
||||
const Index = () => import('./Index.vue');
|
||||
const Webhook = () => import('./Webhooks/Index.vue');
|
||||
const DashboardApps = () => import('./DashboardApps/Index.vue');
|
||||
const Slack = () => import('./Slack.vue');
|
||||
const SettingsContent = () => import('../Wrapper.vue');
|
||||
import SettingsWrapper from '../SettingsWrapper.vue';
|
||||
import IntegrationHooks from './IntegrationHooks.vue';
|
||||
import Index from './Index.vue';
|
||||
import Webhook from './Webhooks/Index.vue';
|
||||
import DashboardApps from './DashboardApps/Index.vue';
|
||||
import Slack from './Slack.vue';
|
||||
import SettingsContent from '../Wrapper.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
|
||||
const SettingsWrapper = () => import('../SettingsWrapper.vue');
|
||||
const Index = () => import('./Index.vue');
|
||||
import SettingsWrapper from '../SettingsWrapper.vue';
|
||||
import Index from './Index.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -4,10 +4,10 @@ import {
|
||||
ROLES,
|
||||
CONVERSATION_PERMISSIONS,
|
||||
} from 'dashboard/constants/permissions.js';
|
||||
const SettingsContent = () => import('../Wrapper.vue');
|
||||
const SettingsWrapper = () => import('../SettingsWrapper.vue');
|
||||
const Macros = () => import('./Index.vue');
|
||||
const MacroEditor = () => import('./MacroEditor.vue');
|
||||
import SettingsContent from '../Wrapper.vue';
|
||||
import SettingsWrapper from '../SettingsWrapper.vue';
|
||||
import Macros from './Index.vue';
|
||||
import MacroEditor from './MacroEditor.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
|
||||
const SettingsContent = () => import('./Wrapper.vue');
|
||||
const Index = () => import('./Index.vue');
|
||||
import SettingsContent from './Wrapper.vue';
|
||||
import Index from './Index.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
|
||||
const SettingsContent = () => import('../Wrapper.vue');
|
||||
const Index = () => import('./Index.vue');
|
||||
const AgentReports = () => import('./AgentReports.vue');
|
||||
const LabelReports = () => import('./LabelReports.vue');
|
||||
const InboxReports = () => import('./InboxReports.vue');
|
||||
const TeamReports = () => import('./TeamReports.vue');
|
||||
const CsatResponses = () => import('./CsatResponses.vue');
|
||||
const BotReports = () => import('./BotReports.vue');
|
||||
const LiveReports = () => import('./LiveReports.vue');
|
||||
const SLAReports = () => import('./SLAReports.vue');
|
||||
import SettingsContent from '../Wrapper.vue';
|
||||
import Index from './Index.vue';
|
||||
import AgentReports from './AgentReports.vue';
|
||||
import LabelReports from './LabelReports.vue';
|
||||
import InboxReports from './InboxReports.vue';
|
||||
import TeamReports from './TeamReports.vue';
|
||||
import CsatResponses from './CsatResponses.vue';
|
||||
import BotReports from './BotReports.vue';
|
||||
import LiveReports from './LiveReports.vue';
|
||||
import SLAReports from './SLAReports.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
|
||||
const SettingsWrapper = () => import('../SettingsWrapper.vue');
|
||||
const Index = () => import('./Index.vue');
|
||||
import SettingsWrapper from '../SettingsWrapper.vue';
|
||||
import Index from './Index.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
|
||||
const TeamsIndex = () => import('./Index.vue');
|
||||
const CreateStepWrap = () => import('./Create/Index.vue');
|
||||
const EditStepWrap = () => import('./Edit/Index.vue');
|
||||
const CreateTeam = () => import('./Create/CreateTeam.vue');
|
||||
const EditTeam = () => import('./Edit/EditTeam.vue');
|
||||
const AddAgents = () => import('./Create/AddAgents.vue');
|
||||
const EditAgents = () => import('./Edit/EditAgents.vue');
|
||||
const FinishSetup = () => import('./FinishSetup.vue');
|
||||
const SettingsContent = () => import('../Wrapper.vue');
|
||||
const SettingsWrapper = () => import('../SettingsWrapper.vue');
|
||||
import TeamsIndex from './Index.vue';
|
||||
import CreateStepWrap from './Create/Index.vue';
|
||||
import EditStepWrap from './Edit/Index.vue';
|
||||
import CreateTeam from './Create/CreateTeam.vue';
|
||||
import EditTeam from './Edit/EditTeam.vue';
|
||||
import AddAgents from './Create/AddAgents.vue';
|
||||
import EditAgents from './Edit/EditAgents.vue';
|
||||
import FinishSetup from './FinishSetup.vue';
|
||||
import SettingsContent from '../Wrapper.vue';
|
||||
import SettingsWrapper from '../SettingsWrapper.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
|
||||
@@ -11,16 +11,10 @@ import { directive as onClickaway } from 'vue3-click-away';
|
||||
import { domPurifyConfig } from '../shared/helpers/HTMLSanitizer';
|
||||
import { plugin, defaultConfig } from '@formkit/vue';
|
||||
|
||||
// import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
// https://github.com/wearebraid/vue-formulate/issues/198
|
||||
// [VITE] [TODO] Re-enable this later
|
||||
// import VueFormulate from '@braid/vue-formulate';
|
||||
import {
|
||||
startsWithPlus,
|
||||
isPhoneNumberValidWithDialCode,
|
||||
} from 'shared/helpers/Validators';
|
||||
// const PhoneInput = () => import('../widget/components/Form/PhoneInput.vue');
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false, // https://github.com/intlify/vue-i18n/issues/1902
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||
|
||||
const Login = () => import('./login/Index.vue');
|
||||
const Signup = () => import('./auth/signup/Index.vue');
|
||||
const ResetPassword = () => import('./auth/reset/password/Index.vue');
|
||||
const Confirmation = () => import('./auth/confirmation/Index.vue');
|
||||
const PasswordEdit = () => import('./auth/password/Edit.vue');
|
||||
import Login from './login/Index.vue';
|
||||
import Signup from './auth/signup/Index.vue';
|
||||
import ResetPassword from './auth/reset/password/Index.vue';
|
||||
import Confirmation from './auth/confirmation/Index.vue';
|
||||
import PasswordEdit from './auth/password/Edit.vue';
|
||||
|
||||
export default [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user