0
0
Fork 0

feat(leftmenu): collapsable submodule routes #4044

This commit is contained in:
Joan Sanchez 2022-05-17 14:58:01 +02:00
parent 6c0688f1f0
commit d2e78c4b56
7 changed files with 195 additions and 26 deletions

View File

@ -7,21 +7,37 @@ import routes from 'src/router/routes';
const { t } = useI18n(); const { t } = useI18n();
const { hasAny } = useRole(); const { hasAny } = useRole();
const mainRoute = routes.find(route => route.path === '/'); const mainRoute = routes.find((route) => route.path === '/');
const moduleRoutes = mainRoute && mainRoute.children || [] const moduleRoutes = (mainRoute && mainRoute.children) || [];
const modules = ref([]); const modules = ref([]);
for (const route of moduleRoutes) { for (const route of moduleRoutes) {
const module = { const module = {
path: route.path, stateName: route.name,
name: route.name.toLowerCase(), name: route.name.toLowerCase(),
roles: [] roles: [],
}; };
if (route.meta) { if (route.meta) {
Object.assign(module, route.meta); Object.assign(module, route.meta);
} }
if (route.children && route.children.length) {
const [moduleMain] = route.children;
const routes = moduleMain.children;
module.children = routes.map((route) => {
const submodule = {
stateName: route.name,
name: route.name,
};
Object.assign(submodule, route.meta);
return submodule;
});
}
modules.value.push(module); modules.value.push(module);
} }
</script> </script>
@ -29,18 +45,47 @@ for (const route of moduleRoutes) {
<template> <template>
<q-list padding> <q-list padding>
<template v-for="module in modules" :key="module.title"> <template v-for="module in modules" :key="module.title">
<q-item <template v-if="!module.children">
clickable <q-item
v-ripple clickable
:to="{ path: module.path }" v-ripple
v-if="!module.roles.length || hasAny(module.roles)" active-class="text-orange"
active-class="text-orange" :key="module.title"
> :to="{ name: module.stateName }"
<q-item-section avatar :if="module.icon"> v-if="!module.roles || !module.roles.length || hasAny(module.roles)"
<q-icon :name="module.icon" /> >
</q-item-section> <q-item-section avatar :if="module.icon">
<q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section> <q-icon :name="module.icon" />
</q-item> </q-item-section>
<q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>
</q-item>
</template>
<template v-if="module.children">
<q-expansion-item
expand-separator
active-class="text-orange"
:icon="module.icon"
:label="t(`${module.name}.pageTitles.${module.title}`)"
v-if="!module.roles || !module.roles.length || hasAny(module.roles)"
:to="{ name: module.stateName }"
>
<template v-for="section in module.children" :key="section.title">
<q-item
clickable
v-ripple
active-class="text-orange"
:to="{ name: section.stateName }"
v-if="!section.roles || !section.roles.length || hasAny(section.roles)"
>
<q-item-section avatar :if="section.icon">
<q-icon :name="section.icon" />
</q-item-section>
<q-item-section>{{ t(`${module.name}.pageTitles.${section.title}`) }}</q-item-section>
</q-item>
</template>
</q-expansion-item>
</template>
</template> </template>
</q-list> </q-list>
</template> </template>

View File

@ -50,9 +50,17 @@ function onToggleDrawer() {
</q-menu> </q-menu>
</q-btn> </q-btn>
<q-btn v-if="$q.screen.gt.xs" dense flat round size="md" icon="notifications"> <q-btn v-if="$q.screen.gt.xs" dense flat round size="md" icon="notifications">
<q-badge color="red" text-color="white" floating> 2 </q-badge>
<q-tooltip bottom> <q-tooltip bottom>
{{ t('globals.notifications') }} {{ t('globals.notifications') }}
</q-tooltip> </q-tooltip>
<q-menu class="q-pa-md" style="min-width: 250px">
<strong>Notifications</strong>
<q-separator />
<div style="text-align: center; font-size: 2em">
<q-spinner-puff color="orange" />
</div>
</q-menu>
</q-btn> </q-btn>
<q-btn dense flat no-wrap> <q-btn dense flat no-wrap>
<q-avatar size="lg"> <q-avatar size="lg">

View File

@ -0,0 +1,98 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper } from 'app/tests/jest/jestHelpers';
import Leftmenu from '../LeftMenu.vue';
const mockPush = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: { value: 'myCurrentRoute' }
}),
}));
jest.mock('src/router/routes', () => ([
{
path: '/',
name: 'Main',
children: [
{
path: '/dashboard',
name: 'Dashboard',
meta: { title: 'dashboard', icon: 'dashboard' }
},
{
path: '/customer',
name: 'Customer',
meta: {
title: 'customers',
icon: 'vn:client'
},
children: [
{
path: '',
name: 'CustomerMain',
children: [
{
path: 'list',
name: 'CustomerList',
meta: {
title: 'list',
icon: 'view_list',
}
},
{
path: 'create',
name: 'CustomerCreate',
meta: {
title: 'createCustomer',
icon: 'vn:addperson',
}
},
]
}
]
}
],
},
]));
describe('Leftmenu', () => {
let vm;
beforeAll(() => {
vm = createWrapper(Leftmenu).vm;
});
it('should return a proper formated object without the children property', async () => {
const expectedMenuItem = {
stateName: 'Dashboard',
name: 'dashboard',
roles: [],
icon: 'dashboard',
title: 'dashboard'
}
const firstMenuItem = vm.modules[0];
expect(firstMenuItem.children).toBeUndefined();
expect(firstMenuItem).toEqual(expect.objectContaining(expectedMenuItem));
});
it('should return a proper formated object with two child items', async () => {
const expectedMenuItem = [{
name: 'CustomerList',
title: 'list',
icon: 'view_list',
stateName: 'CustomerList'
},
{
name: 'CustomerCreate',
title: 'createCustomer',
icon: 'vn:addperson',
stateName: 'CustomerCreate'
}];
const secondMenuItem = vm.modules[1];
expect(secondMenuItem.children).toEqual(expect.arrayContaining(expectedMenuItem));
expect(secondMenuItem.children.length).toEqual(2)
});
});

View File

@ -39,7 +39,10 @@ export default {
}, },
ticket: { ticket: {
pageTitles: { pageTitles: {
tickets: 'Tickets' tickets: 'Tickets',
list: 'List',
createTicket: 'Create ticket',
basicData: 'Basic Data'
} }
}, },
components: { components: {

View File

@ -39,7 +39,10 @@ export default {
}, },
ticket: { ticket: {
pageTitles: { pageTitles: {
tickets: 'Tickets' tickets: 'Tickets',
list: 'Listado',
createTicket: 'Crear ticket',
basicData: 'Datos básicos'
} }
}, },
components: { components: {

View File

@ -5,8 +5,7 @@ export default {
name: 'Customer', name: 'Customer',
meta: { meta: {
title: 'customers', title: 'customers',
icon: 'vn:client', icon: 'vn:client'
roles: ['salesPerson'],
}, },
component: RouterView, component: RouterView,
redirect: { name: 'CustomerMain' }, redirect: { name: 'CustomerMain' },
@ -21,7 +20,8 @@ export default {
path: 'list', path: 'list',
name: 'CustomerList', name: 'CustomerList',
meta: { meta: {
title: 'list' title: 'list',
icon: 'view_list',
}, },
component: () => import('src/pages/Customer/CustomerList.vue'), component: () => import('src/pages/Customer/CustomerList.vue'),
}, },
@ -29,7 +29,8 @@ export default {
path: 'create', path: 'create',
name: 'CustomerCreate', name: 'CustomerCreate',
meta: { meta: {
title: 'createCustomer' title: 'createCustomer',
icon: 'vn:addperson',
}, },
component: () => import('src/pages/Customer/CustomerCreate.vue'), component: () => import('src/pages/Customer/CustomerCreate.vue'),
}, },

View File

@ -5,8 +5,7 @@ export default {
name: 'Ticket', name: 'Ticket',
meta: { meta: {
title: 'tickets', title: 'tickets',
icon: 'vn:ticket', icon: 'vn:ticket'
roles: ['salesPerson'],
}, },
component: RouterView, component: RouterView,
redirect: { name: 'TicketMain' }, redirect: { name: 'TicketMain' },
@ -21,10 +20,22 @@ export default {
path: 'list', path: 'list',
name: 'TicketList', name: 'TicketList',
meta: { meta: {
title: 'list' title: 'list',
icon: 'view_list',
}, },
component: () => import('src/pages/Ticket/TicketList.vue'), component: () => import('src/pages/Ticket/TicketList.vue'),
}, },
{
path: 'create',
name: 'TicketCreate',
meta: {
title: 'createTicket',
icon: 'vn:ticketAdd',
roles: ['salesPerson'],
},
component: () => import('src/pages/Ticket/TicketList.vue'),
},
] ]
}, },
{ {