4722 - Menu refactor & added pinia
gitea/salix-front/pipeline/head There was a failure building this commit Details

This commit is contained in:
Joan Sanchez 2022-11-24 07:21:45 +01:00
parent 26d7640bc1
commit b6c240b279
12 changed files with 240 additions and 186 deletions

View File

@ -23,7 +23,7 @@ module.exports = configure(function (ctx) {
// app boot file (/src/boot) // app boot file (/src/boot)
// --> boot files are part of "main.js" // --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli-webpack/boot-files // https://v2.quasar.dev/quasar-cli-webpack/boot-files
boot: ['i18n', 'axios'], boot: ['i18n', 'axios', 'pinia'],
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css // https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css
css: ['app.scss'], css: ['app.scss'],

8
src/boot/pinia.js Normal file
View File

@ -0,0 +1,8 @@
import { boot } from 'quasar/wrappers';
import { createPinia } from 'pinia';
export default boot(({ app }) => {
const pinia = createPinia();
app.use(pinia);
});

View File

@ -1,43 +1,44 @@
<!--<script setup>--> <script setup>
<!--import { onMounted } from 'vue';--> import { onMounted } from 'vue';
<!--import { useI18n } from 'vue-i18n';--> // import { useI18n } from 'vue-i18n';
<!--import { useNavigation } from 'src/composables/useNavigation';--> import { useNavigation } from 'src/stores/useNavigation';
<!--const { t } = useI18n();--> // const { t } = useI18n();
<!--const navigation = useNavigation();--> const navigation = useNavigation();
<!--onMounted(() => {--> onMounted(() => {
<!-- navigation.fetchFavorites();--> navigation.fetchPinned();
<!--});--> });
<!--</script>--> </script>
<!--<template>--> <template>
<!-- <q-menu--> {{ navigation.pinned }}
<!-- anchor="bottom left"--> <!-- <q-menu-->
<!-- class="row q-pa-md q-col-gutter-lg"--> <!-- anchor="bottom left"-->
<!-- max-width="350px"--> <!-- class="row q-pa-md q-col-gutter-lg"-->
<!-- max-height="400px"--> <!-- max-width="350px"-->
<!-- v-if="navigation.favorites.value.length"--> <!-- max-height="400px"-->
<!-- >--> <!-- v-if="navigation.favorites.value.length"-->
<!-- <div v-for="module of navigation.favorites.value" :key="module.title" class="row no-wrap q-pa-xs flex-item">--> <!-- >-->
<!-- <q-btn--> <!-- <div v-for="module of navigation.favorites.value" :key="module.title" class="row no-wrap q-pa-xs flex-item">-->
<!-- align="evenly"--> <!-- <q-btn-->
<!-- padding="16px"--> <!-- align="evenly"-->
<!-- flat--> <!-- padding="16px"-->
<!-- stack--> <!-- flat-->
<!-- size="lg"--> <!-- stack-->
<!-- :icon="module.icon"--> <!-- size="lg"-->
<!-- color="primary"--> <!-- :icon="module.icon"-->
<!-- class="col-4 button"--> <!-- color="primary"-->
<!-- :to="{ name: module.stateName }"--> <!-- class="col-4 button"-->
<!-- >--> <!-- :to="{ name: module.stateName }"-->
<!-- <div class="text-center text-primary button-text">--> <!-- >-->
<!-- {{ t(`${module.name}.pageTitles.${module.title}`) }}--> <!-- <div class="text-center text-primary button-text">-->
<!-- </div>--> <!-- {{ t(`${module.name}.pageTitles.${module.title}`) }}-->
<!-- </q-btn>--> <!-- </div>-->
<!-- </div>--> <!-- </q-btn>-->
<!-- </q-menu>--> <!-- </div>-->
<!--</template>--> <!-- </q-menu>-->
</template>
<!--<style lang="scss" scoped>--> <!--<style lang="scss" scoped>-->
<!--.flex-item {--> <!--.flex-item {-->

View File

@ -1,53 +1,95 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
// import { useRole } from 'src/composables/useRole'; import { useRole } from 'src/composables/useRole';
// import { useQuasar } from 'quasar'; // import { useQuasar } from 'quasar';
import { useRoute } from 'vue-router';
import { useNavigation } from 'src/composables/useNavigation'; import { useNavigation } from 'src/composables/useNavigation';
import routes from 'src/router/modules'; import routes from 'src/router/modules';
const { t } = useI18n(); const { t } = useI18n();
// const { hasAny } = useRole(); const role = useRole();
const route = useRoute();
const navigation = useNavigation(); const navigation = useNavigation();
const $props = defineProps({ const props = defineProps({
source: { source: {
type: String, type: String,
default: 'main', default: 'main',
}, },
}); });
const items = ref([]); function toLowerCamel(value) {
for (const module of navigation.modules) { return value.charAt(0).toLowerCase() + value.slice(1);
const moduleDef = routes.find((route) => route.name.toLowerCase() === module); }
const moduleMeta = moduleDef.meta;
const item = { function findMatches(search, item) {
name: moduleDef.name, const matches = [];
title: moduleMeta.title, function findRoute(search, item) {
icon: moduleMeta.icon, for (const child of item.children) {
module: module, if (search.indexOf(child.name) > -1) {
children: [], matches.push(child);
}; } else if (child.children) {
findRoute(search, child);
if (moduleDef.menus) { }
const mainMenus = moduleDef.menus[$props.source];
for (const menu of mainMenus) {
const mainMenuItems = moduleDef.children[0].children;
const mainMenuItemDef = mainMenuItems.find((route) => route.name === menu.name);
const mainMenuItemMeta = mainMenuItemDef.meta;
item.children.push({
name: mainMenuItemDef.name,
title: mainMenuItemMeta.title,
icon: mainMenuItemMeta.icon,
});
} }
} }
items.value.push(item); findRoute(search, item);
return matches;
} }
function addChildren(module, route, parent) {
if (route.menus) {
const mainMenus = route.menus[props.source];
const matches = findMatches(mainMenus, route);
for (const child of matches) {
addMenuItem(module, child, parent);
}
}
}
function addMenuItem(module, route, parent) {
const { meta } = route;
if (meta && meta.roles && role.hasAny(meta.roles) === false) return;
const item = {
name: route.name,
};
if (meta) {
item.title = `${module}.pageTitles.${meta.title}`;
item.icon = meta.icon;
}
parent.push(item);
return item;
}
const items = ref([]);
if (props.source === 'main') {
for (const module of navigation.modules) {
const moduleDef = routes.find((route) => toLowerCamel(route.name) === module);
const item = addMenuItem(module, moduleDef, items.value);
item.children = [];
if (!item) continue;
addChildren(module, moduleDef, item.children);
}
}
if (props.source === 'card') {
const currentRoute = route.matched[1];
const currentModule = toLowerCamel(currentRoute.name);
const moduleDef = routes.find((route) => toLowerCamel(route.name) === currentModule);
addChildren(currentModule, moduleDef, items.value);
}
// const quasar = useQuasar(); // const quasar = useQuasar();
// async function onToggleFavoriteModule(moduleName, event) { // async function onToggleFavoriteModule(moduleName, event) {
@ -58,44 +100,69 @@ for (const module of navigation.modules) {
// type: 'positive', // type: 'positive',
// }); // });
// } // }
function isOpen(name) {
const { matched } = route;
return matched.some((item) => item.name === name);
}
</script> </script>
<template> <template>
<q-list padding> <q-list padding>
<q-item-label header>{{ t('globals.favoriteModules') }}</q-item-label> <q-item-label v-if="$props.source === 'main'" header>
{{ t('globals.favoriteModules') }}
</q-item-label>
<template v-for="item in items" :key="item.name"> <template v-for="item in items" :key="item.name">
<q-expansion-item <template v-if="item.children">
group="somegroup" <q-expansion-item
class="module" group="group"
active-class="text-primary" class="module"
:label="item.title" active-class="text-primary"
:to="{ name: item.name }" :label="item.title"
expand-separator :to="{ name: item.name }"
> expand-separator
<template #header> :default-opened="isOpen(item.name)"
<q-item-section avatar> >
<q-icon :name="item.icon"></q-icon> <template #header>
</q-item-section> <q-item-section avatar>
<q-item-section>{{ t(`${item.module}.pageTitles.${item.title}`) }}</q-item-section> <q-icon :name="item.icon"></q-icon>
<!-- <q-item-section side>-->
<!-- <div-->
<!-- @click="onToggleFavoriteModule(module.name, $event)"-->
<!-- class="row items-center"-->
<!-- v-if="module.name != 'dashboard'"-->
<!-- >-->
<!-- <q-icon name="vn:pin"></q-icon>-->
<!-- </div>-->
<!-- </q-item-section>-->
</template>
<template v-for="section in item.children" :key="section.name">
<q-item clickable v-ripple active-class="text-primary" :to="{ name: section.name }">
<q-item-section avatar :if="section.icon">
<q-icon :name="section.icon" />
</q-item-section> </q-item-section>
<q-item-section>{{ t(`${item.module}.pageTitles.${section.title}`) }}</q-item-section> <q-item-section>{{ t(item.title) }}</q-item-section>
</q-item> <!-- <q-item-section side>-->
</template> <!-- <div-->
</q-expansion-item> <!-- @click="onToggleFavoriteModule(module.name, $event)"-->
<!-- class="row items-center"-->
<!-- v-if="module.name != 'dashboard'"-->
<!-- >-->
<!-- <q-icon name="vn:pin"></q-icon>-->
<!-- </div>-->
<!-- </q-item-section>-->
</template>
<template v-for="section in item.children" :key="section.name">
<q-item clickable v-ripple active-class="text-primary" :to="{ name: section.name }">
<q-item-section avatar v-if="section.icon">
<q-icon :name="section.icon" />
</q-item-section>
<q-item-section avatar v-if="!item.icon">
<q-icon name="disabled_by_default" />
</q-item-section>
<q-item-section>{{ t(section.title) }}</q-item-section>
</q-item>
</template>
</q-expansion-item>
</template>
<template v-if="!item.children">
<q-item clickable v-ripple active-class="text-primary" :to="{ name: item.name }">
<q-item-section avatar v-if="item.icon">
<q-icon :name="item.icon" />
</q-item-section>
<q-item-section avatar v-if="!item.icon">
<q-icon name="disabled_by_default" />
</q-item-section>
<q-item-section>{{ t(item.title) }}</q-item-section>
</q-item>
</template>
</template> </template>
<!-- <template v-for="module in navigation.favorites.value" :key="module.title">--> <!-- <template v-for="module in navigation.favorites.value" :key="module.title">-->
<!-- <div class="module" v-if="!module.children">--> <!-- <div class="module" v-if="!module.children">-->
@ -157,8 +224,6 @@ for (const module of navigation.modules) {
<!-- </template>--> <!-- </template>-->
</q-list> </q-list>
<q-separator />
<!-- <q-expansion-item :label="t('moduleIndex.allModules')">--> <!-- <q-expansion-item :label="t('moduleIndex.allModules')">-->
<!-- <q-list padding>--> <!-- <q-list padding>-->
<!-- <template v-for="module in navigation.modules.value" :key="module.title">--> <!-- <template v-for="module in navigation.modules.value" :key="module.title">-->
@ -232,13 +297,13 @@ for (const module of navigation.modules) {
<!-- </q-expansion-item>--> <!-- </q-expansion-item>-->
</template> </template>
<style> <!--<style>-->
.module .icon-pin, <!--.module .icon-pin,-->
.module .icon-pin_off { <!--.module .icon-pin_off {-->
visibility: hidden; <!-- visibility: hidden;-->
} <!--}-->
.module:hover .icon-pin, <!--.module:hover .icon-pin,-->
.module:hover .icon-pin_off { <!--.module:hover .icon-pin_off {-->
visibility: visible; <!-- visibility: visible;-->
} <!--}-->
</style> <!--</style>-->

View File

@ -6,7 +6,7 @@ export function useRole() {
async function fetch() { async function fetch() {
const { data } = await axios.get('Accounts/acl'); const { data } = await axios.get('Accounts/acl');
const roles = data.roles.map(userRoles => userRoles.role.name); const roles = data.roles.map((userRoles) => userRoles.role.name);
const userData = { const userData = {
id: data.user.id, id: data.user.id,
@ -14,7 +14,7 @@ export function useRole() {
nickname: data.user.nickname, nickname: data.user.nickname,
lang: data.user.lang || 'es', lang: data.user.lang || 'es',
darkMode: data.user.userConfig.darkMode, darkMode: data.user.userConfig.darkMode,
} };
state.setUser(userData); state.setUser(userData);
state.setRoles(roles); state.setRoles(roles);
} }
@ -32,6 +32,6 @@ export function useRole() {
return { return {
fetch, fetch,
hasAny, hasAny,
state state,
}; };
} }

View File

@ -1,9 +1,8 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n';
import { useState } from 'composables/useState'; import { useState } from 'composables/useState';
import ClaimDescriptor from './ClaimDescriptor.vue'; import ClaimDescriptor from './ClaimDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue';
const { t } = useI18n();
const state = useState(); const state = useState();
</script> </script>
<template> <template>
@ -11,20 +10,7 @@ const state = useState();
<q-scroll-area class="fit"> <q-scroll-area class="fit">
<claim-descriptor /> <claim-descriptor />
<q-separator /> <q-separator />
<q-list> <left-menu source="card" />
<q-item :to="{ name: 'ClaimBasicData' }" clickable v-ripple>
<q-item-section avatar>
<q-icon name="vn:settings" />
</q-item-section>
<q-item-section>{{ t('claim.pageTitles.basicData') }}</q-item-section>
</q-item>
<q-item :to="{ name: 'ClaimRma' }" clickable v-ripple>
<q-item-section avatar>
<q-icon name="vn:barcode" />
</q-item-section>
<q-item-section>{{ t('claim.pageTitles.rma') }}</q-item-section>
</q-item>
</q-list>
</q-scroll-area> </q-scroll-area>
</q-drawer> </q-drawer>
<q-page-container> <q-page-container>

View File

@ -1,11 +1,9 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import CustomerDescriptor from './CustomerDescriptor.vue'; import CustomerDescriptor from './CustomerDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue'; import LeftMenu from 'components/LeftMenu.vue';
const state = useState(); const state = useState();
const { t } = useI18n();
</script> </script>
<template> <template>
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500"> <q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
@ -13,30 +11,6 @@ const { t } = useI18n();
<customer-descriptor /> <customer-descriptor />
<q-separator /> <q-separator />
<left-menu source="card" /> <left-menu source="card" />
<q-list>
<q-item :to="{ name: 'CustomerBasicData' }" clickable v-ripple>
<q-item-section avatar>
<q-icon name="vn:settings" />
</q-item-section>
<q-item-section>{{ t('customer.pageTitles.basicData') }}</q-item-section>
</q-item>
<!-- <q-item clickable v-ripple>
<q-item-section avatar>
<q-icon name="notes" />
</q-item-section>
<q-item-section>Notes</q-item-section>
</q-item>
<q-expansion-item icon="more" label="More options" expand-icon-toggle expand-separator>
<q-list>
<q-item clickable v-ripple>
<q-item-section avatar>
<q-icon name="person" />
</q-item-section>
<q-item-section>Option</q-item-section>
</q-item>
</q-list>
</q-expansion-item> -->
</q-list>
</q-scroll-area> </q-scroll-area>
</q-drawer> </q-drawer>
<q-page-container> <q-page-container>

View File

@ -1,24 +1,16 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import TicketDescriptor from './TicketDescriptor.vue'; import TicketDescriptor from './TicketDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue';
const state = useState(); const state = useState();
const { t } = useI18n();
</script> </script>
<template> <template>
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500"> <q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
<q-scroll-area class="fit"> <q-scroll-area class="fit">
<ticket-descriptor /> <ticket-descriptor />
<q-separator /> <q-separator />
<q-list> <left-menu source="card" />
<q-item :to="{ name: 'TicketBoxing' }" clickable v-ripple>
<q-item-section avatar>
<q-icon name="vn:package" />
</q-item-section>
<q-item-section>{{ t('ticket.pageTitles.boxing') }}</q-item-section>
</q-item>
</q-list>
</q-scroll-area> </q-scroll-area>
</q-drawer> </q-drawer>
<q-page-container> <q-page-container>

View File

@ -10,11 +10,8 @@ export default {
component: RouterView, component: RouterView,
redirect: { name: 'ClaimMain' }, redirect: { name: 'ClaimMain' },
menus: { menus: {
main: [ main: ['ClaimList', 'ClaimRmaList'],
{ name: 'ClaimList', icon: 'view_list' }, card: ['ClaimBasicData', 'ClaimRma'],
{ name: 'ClaimRmaList', icon: 'vn:barcode' },
],
card: [{ name: 'CustomerBasicData', icon: 'vn:settings' }],
}, },
children: [ children: [
{ {
@ -55,6 +52,7 @@ export default {
path: 'summary', path: 'summary',
meta: { meta: {
title: 'summary', title: 'summary',
icon: 'launch',
}, },
component: () => import('src/pages/Claim/Card/ClaimSummary.vue'), component: () => import('src/pages/Claim/Card/ClaimSummary.vue'),
}, },
@ -63,6 +61,7 @@ export default {
path: 'basic-data', path: 'basic-data',
meta: { meta: {
title: 'basicData', title: 'basicData',
icon: 'vn:settings',
roles: ['salesPerson'], roles: ['salesPerson'],
}, },
component: () => import('src/pages/Claim/Card/ClaimBasicData.vue'), component: () => import('src/pages/Claim/Card/ClaimBasicData.vue'),
@ -72,6 +71,7 @@ export default {
path: 'rma', path: 'rma',
meta: { meta: {
title: 'rma', title: 'rma',
icon: 'vn:barcode',
roles: ['claimManager'], roles: ['claimManager'],
}, },
component: () => import('src/pages/Claim/Card/ClaimRma.vue'), component: () => import('src/pages/Claim/Card/ClaimRma.vue'),

View File

@ -10,11 +10,8 @@ export default {
component: RouterView, component: RouterView,
redirect: { name: 'CustomerMain' }, redirect: { name: 'CustomerMain' },
menus: { menus: {
main: [ main: ['CustomerList', 'CustomerCreate'],
{ name: 'CustomerList', icon: 'view_list' }, card: ['CustomerBasicData'],
{ name: 'CustomerCreate', icon: 'vn:addperson' },
],
card: [{ name: 'CustomerBasicData', icon: 'vn:settings' }],
}, },
children: [ children: [
{ {
@ -55,6 +52,7 @@ export default {
path: 'summary', path: 'summary',
meta: { meta: {
title: 'summary', title: 'summary',
icon: 'launch',
}, },
component: () => import('src/pages/Customer/Card/CustomerSummary.vue'), component: () => import('src/pages/Customer/Card/CustomerSummary.vue'),
}, },
@ -63,6 +61,7 @@ export default {
name: 'CustomerBasicData', name: 'CustomerBasicData',
meta: { meta: {
title: 'basicData', title: 'basicData',
icon: 'vn:settings',
}, },
component: () => import('src/pages/Customer/Card/CustomerBasicData.vue'), component: () => import('src/pages/Customer/Card/CustomerBasicData.vue'),
}, },

View File

@ -5,10 +5,14 @@ export default {
path: '/ticket', path: '/ticket',
meta: { meta: {
title: 'tickets', title: 'tickets',
icon: 'vn:ticket' icon: 'vn:ticket',
}, },
component: RouterView, component: RouterView,
redirect: { name: 'TicketMain' }, redirect: { name: 'TicketMain' },
menus: {
main: ['TicketList'],
card: ['TicketBoxing'],
},
children: [ children: [
{ {
name: 'TicketMain', name: 'TicketMain',
@ -35,8 +39,7 @@ export default {
}, },
component: () => import('src/pages/Ticket/TicketList.vue'), component: () => import('src/pages/Ticket/TicketList.vue'),
}, },
],
]
}, },
{ {
name: 'TicketCard', name: 'TicketCard',
@ -48,7 +51,8 @@ export default {
name: 'TicketSummary', name: 'TicketSummary',
path: 'summary', path: 'summary',
meta: { meta: {
title: 'summary' title: 'summary',
icon: 'launch',
}, },
component: () => import('src/pages/Ticket/Card/TicketSummary.vue'), component: () => import('src/pages/Ticket/Card/TicketSummary.vue'),
}, },
@ -56,7 +60,8 @@ export default {
name: 'TicketBasicData', name: 'TicketBasicData',
path: 'basic-data', path: 'basic-data',
meta: { meta: {
title: 'basicData' title: 'basicData',
icon: 'vn:settings',
}, },
component: () => import('src/pages/Ticket/Card/TicketBasicData.vue'), component: () => import('src/pages/Ticket/Card/TicketBasicData.vue'),
}, },
@ -64,11 +69,12 @@ export default {
path: 'boxing', path: 'boxing',
name: 'TicketBoxing', name: 'TicketBoxing',
meta: { meta: {
title: 'boxing' title: 'boxing',
icon: 'vn:package',
}, },
component: () => import('src/pages/Ticket/Card/TicketBoxing.vue'), component: () => import('src/pages/Ticket/Card/TicketBoxing.vue'),
} },
] ],
}, },
] ],
}; };

View File

@ -0,0 +1,23 @@
import { ref } from 'vue';
import axios from 'axios';
import { defineStore } from 'pinia';
export const useNavigation = defineStore('navigation', () => {
const modules = ['customer', 'claim', 'ticket'];
const pinned = ref([]);
async function fetchPinned() {
const response = await axios.get('StarredModules/getStarredModules');
// const filteredModules = modules.value.filter((module) => {
// return response.data.find((element) => element.moduleFk == salixModules[module.name]);
// });
return (pinned.value = response.data);
}
return {
modules,
pinned,
fetchPinned,
};
});