0
0
Fork 0

Pinned refactor

This commit is contained in:
joan 2022-11-29 14:45:48 +01:00
parent 3fa5eb1726
commit edf05bb7a9
17 changed files with 381 additions and 516 deletions

View File

@ -2,7 +2,7 @@
"editor.bracketPairColorization.enabled": true, "editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true, "editor.guides.bracketPairs": true,
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.defaultFormatter": "johnsoncodehk.volar", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": ["source.fixAll.eslint"], "editor.codeActionsOnSave": ["source.fixAll.eslint"],
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"], "eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
"json.schemas": [ "json.schemas": [
@ -11,9 +11,6 @@
"url": "https://on.cypress.io/cypress.schema.json" "url": "https://on.cypress.io/cypress.schema.json"
} }
], ],
"[javascript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[vue]": { "[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
} }

View File

@ -38,8 +38,9 @@ module.exports = configure(function (ctx) {
// 'line-awesome', // 'line-awesome',
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
'roboto-font', // optional, you are not bound to it 'roboto-font',
'material-icons', // optional, you are not bound to it 'material-icons-outlined',
'material-symbols-outlined',
], ],
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-build // Full list of options: https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-build

View File

@ -19,6 +19,11 @@ quasar.iconMapFn = (iconName) => {
cls: `icon-${name}`, cls: `icon-${name}`,
}; };
} }
return {
cls: 'material-symbols-outlined',
content: iconName,
};
}; };
function responseError(error) { function responseError(error) {

View File

@ -1,57 +0,0 @@
<script setup>
// import { onMounted } from 'vue';
// import { useI18n } from 'vue-i18n';
// import { useNavigation } from 'src/stores/useNavigationStoreStore';
// const { t } = useI18n();
// const navigation = useNavigation();
// onMounted(() => {
// navigation.fetchPinned();
// });
</script>
<template>
Test
<!-- {{ navigation.pinned }} -->
<!-- <q-menu-->
<!-- anchor="bottom left"-->
<!-- class="row q-pa-md q-col-gutter-lg"-->
<!-- max-width="350px"-->
<!-- 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-->
<!-- align="evenly"-->
<!-- padding="16px"-->
<!-- flat-->
<!-- stack-->
<!-- size="lg"-->
<!-- :icon="module.icon"-->
<!-- color="primary"-->
<!-- class="col-4 button"-->
<!-- :to="{ name: module.stateName }"-->
<!-- >-->
<!-- <div class="text-center text-primary button-text">-->
<!-- {{ t(`${module.name}.pageTitles.${module.title}`) }}-->
<!-- </div>-->
<!-- </q-btn>-->
<!-- </div>-->
<!-- </q-menu>-->
</template>
<!--<style lang="scss" scoped>-->
<!--.flex-item {-->
<!-- width: 100px;-->
<!--}-->
<!--.button {-->
<!-- width: 100%;-->
<!-- line-height: normal;-->
<!-- align-items: center;-->
<!--}-->
<!--.button-text {-->
<!-- font-size: 10px;-->
<!-- margin-top: 5px;-->
<!--}-->
<!--</style>-->

View File

@ -1,15 +1,16 @@
<script setup> <script setup>
import { ref } from 'vue'; import axios from 'axios';
import { onMounted, ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRole } from 'src/composables/useRole';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useNavigationStore } from 'src/stores/useNavigationStore'; import { useNavigationStore } from 'src/stores/useNavigationStore';
import { toLowerCamel } from 'src/filters';
import routes from 'src/router/modules'; import routes from 'src/router/modules';
import axios from 'axios'; import LeftMenuItem from './LeftMenuItem.vue';
import LeftMenuItemGroup from './LeftMenuItemGroup.vue';
const { t } = useI18n(); const { t } = useI18n();
const role = useRole();
const route = useRoute(); const route = useRoute();
const quasar = useQuasar(); const quasar = useQuasar();
const navigation = useNavigationStore(); const navigation = useNavigationStore();
@ -21,9 +22,10 @@ const props = defineProps({
}, },
}); });
function toLowerCamel(value) { onMounted(async () => {
return value.charAt(0).toLowerCase() + value.slice(1); await navigation.fetchPinned();
} getRoutes();
});
function findMatches(search, item) { function findMatches(search, item) {
const matches = []; const matches = [];
@ -47,333 +49,146 @@ function addChildren(module, route, parent) {
const mainMenus = route.menus[props.source]; const mainMenus = route.menus[props.source];
const matches = findMatches(mainMenus, route); const matches = findMatches(mainMenus, route);
for (const child of matches) { for (const child of matches) {
addMenuItem(module, child, parent); navigation.addMenuItem(module, child, parent);
} }
} }
} }
const pinnedItems = computed(() => {
function addMenuItem(module, route, parent) { return items.value.filter((item) => item.isPinned);
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([]); const items = ref([]);
if (props.source === 'main') { function getRoutes() {
for (const module of navigation.modules) { if (props.source === 'main') {
const moduleDef = routes.find((route) => toLowerCamel(route.name) === module); const modules = Object.assign([], navigation.getModules().value);
for (const item of modules) {
const moduleDef = routes.find((route) => toLowerCamel(route.name) === item.module);
const item = addMenuItem(module, moduleDef, items.value); item.children = [];
const pinned = navigation.pinned.value;
if (pinned.contains(module)) { addChildren(item.module, moduleDef, item.children);
item.isPinned = true
} }
item.children = []; items.value = modules;
item.module = module; }
if (!item) continue; if (props.source === 'card') {
addChildren(module, moduleDef, item.children); const currentRoute = route.matched[1];
const currentModule = toLowerCamel(currentRoute.name);
const moduleDef = routes.find((route) => toLowerCamel(route.name) === currentModule);
addChildren(currentModule, moduleDef, items.value);
} }
} }
if (props.source === 'card') { async function togglePinned(item, event) {
const currentRoute = route.matched[1];
const currentModule = toLowerCamel(currentRoute.name);
const moduleDef = routes.find((route) => toLowerCamel(route.name) === currentModule);
addChildren(currentModule, moduleDef, items.value);
}
async function togglePinned(moduleName, event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
await axios.post('StarredModules/toggleStarredModule', { moduleName }); const data = { moduleName: item.module };
navigation.togglePinned(moduleName); const response = await axios.post('StarredModules/toggleStarredModule', data);
item.isPinned = false;
if (response.data && response.data.id) {
item.isPinned = true;
}
navigation.togglePinned(item.module);
quasar.notify({ quasar.notify({
message: t('globals.dataSaved'), message: t('globals.dataSaved'),
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 v-if="$props.source === 'main'" header> <template v-if="$props.source === 'main'">
{{ t('globals.favoriteModules') }} <q-item-label header>
</q-item-label> {{ t('globals.pinnedModules') }}
<!-- <template v-for="item in items" :key="item.name"> </q-item-label>
<template v-if="item.children"> <template v-for="item in pinnedItems" :key="item.name">
<q-expansion-item <template v-if="item.children">
group="itemGroup" <left-menu-item-group :item="item" group="pinnedModules" class="pinned">
class="module" <template #side>
active-class="text-primary" <q-btn
:label="item.title" v-if="item.isPinned === true"
:to="{ name: item.name }" @click="togglePinned(item, $event)"
expand-separator icon="vn:pin_off"
:default-opened="isOpen(item.name)" size="xs"
> flat
<template #header> round
<q-item-section avatar>
<q-icon :name="item.icon"></q-icon>
</q-item-section>
<q-item-section>{{ t(item.title) }}</q-item-section>
<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> <q-tooltip>{{ t('components.leftMenu.removeFromPinned') }}</q-tooltip>
</div> </q-btn>
</q-item-section> <q-btn
</template> v-if="item.isPinned === false"
<template v-for="section in item.children" :key="section.name"> @click="togglePinned(item, $event)"
<q-item active-class="text-primary" :to="{ name: section.name }" clickable v-ripple> icon="vn:pin"
<q-item-section avatar v-if="section.icon"> size="xs"
<q-icon :name="section.icon" /> flat
</q-item-section> round
<q-item-section avatar v-if="!item.icon"> >
<q-icon name="disabled_by_default" /> <q-tooltip>{{ t('components.leftMenu.addToPinned') }}</q-tooltip>
</q-item-section> </q-btn>
<q-item-section>{{ t(section.title) }}</q-item-section> </template>
</q-item> </left-menu-item-group>
</template> </template>
</q-expansion-item>
<left-menu-item v-if="!item.children" :item="item" />
</template> </template>
<template v-if="!item.children"> <q-separator />
<q-item active-class="text-primary" :to="{ name: item.name }" clickable v-ripple> <q-expansion-item :label="t('moduleIndex.allModules')">
<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> -->
<q-separator />
<q-expansion-item :label="t('moduleIndex.allModules')">
<q-list padding>
<template v-for="item in items" :key="item.name"> <template v-for="item in items" :key="item.name">
<template v-if="item.children"> <template v-if="item.children">
<q-expansion-item <left-menu-item-group :item="item" group="modules">
group="itemGroup" <template #side>
class="module" <q-btn
active-class="text-primary" v-if="item.isPinned === true"
:label="item.title" @click="togglePinned(item, $event)"
:to="{ name: item.name }" icon="vn:pin_off"
expand-separator size="xs"
:default-opened="isOpen(item.name)" flat
> round
<template #header> >
<q-item-section avatar> <q-tooltip>{{ t('components.leftMenu.removeFromPinned') }}</q-tooltip>
<q-icon :name="item.icon"></q-icon> </q-btn>
</q-item-section> <q-btn
<q-item-section>{{ t(item.title) }}</q-item-section> v-if="item.isPinned === false"
<q-item-section side> @click="togglePinned(item, $event)"
<q-btn icon="vn:pin"
@click="togglePinned(item.module, $event)" size="xs"
icon="vn:pin" flat
size="xs" round
flat >
round <q-tooltip>{{ t('components.leftMenu.addToPinned') }}</q-tooltip>
> </q-btn>
<q-tooltip>Pin this module to favorites</q-tooltip>
</q-btn>
</q-item-section>
</template> </template>
<template v-for="section in item.children" :key="section.name"> </left-menu-item-group>
<q-item active-class="text-primary" :to="{ name: section.name }" clickable v-ripple>
<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 active-class="text-primary" :to="{ name: item.name }" clickable v-ripple>
<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>
</q-list> </q-expansion-item>
</q-expansion-item> <q-separator />
<q-separator /> </template>
<!-- <template v-for="module in navigation.favorites.value" :key="module.title">--> <template v-if="$props.source === 'card'">
<!-- <div class="module" v-if="!module.children">--> <template v-for="item in items" :key="item.name">
<!-- <q-item--> <left-menu-item v-if="!item.children" :item="item" />
<!-- clickable--> </template>
<!-- v-ripple--> </template>
<!-- active-class="text-primary"-->
<!-- :key="module.title"-->
<!-- :to="{ name: module.stateName }"-->
<!-- v-if="!module.roles || !module.roles.length || hasAny(module.roles)"-->
<!-- >-->
<!-- <q-item-section avatar :if="module.icon">-->
<!-- <q-icon :name="module.icon" />-->
<!-- </q-item-section>-->
<!-- <q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>-->
<!-- <q-item-section side>-->
<!-- <div @click="onToggleFavoriteModule(module.name, $event)" class="row items-center">-->
<!-- <q-icon name="vn:pin_off"></q-icon>-->
<!-- </div>-->
<!-- </q-item-section>-->
<!-- </q-item>-->
<!-- </div>-->
<!-- <template v-if="module.children">-->
<!-- <q-expansion-item-->
<!-- class="module"-->
<!-- active-class="text-primary"-->
<!-- :label="t(`${module.name}.pageTitles.${module.title}`)"-->
<!-- v-if="!module.roles || !module.roles.length || hasAny(module.roles)"-->
<!-- :to="{ name: module.stateName }"-->
<!-- >-->
<!-- <template #header>-->
<!-- <q-item-section avatar>-->
<!-- <q-icon :name="module.icon"></q-icon>-->
<!-- </q-item-section>-->
<!-- <q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>-->
<!-- <q-item-section side>-->
<!-- <div @click="onToggleFavoriteModule(module.name, $event)" class="row items-center">-->
<!-- <q-icon name="vn:pin_off"></q-icon>-->
<!-- </div>-->
<!-- </q-item-section>-->
<!-- </template>-->
<!-- <template v-for="section in module.children" :key="section.title">-->
<!-- <q-item-->
<!-- clickable-->
<!-- v-ripple-->
<!-- active-class="text-primary"-->
<!-- :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>-->
</q-list> </q-list>
<!-- <q-expansion-item :label="t('moduleIndex.allModules')">-->
<!-- <q-list padding>-->
<!-- <template v-for="module in navigation.modules.value" :key="module.title">-->
<!-- <div class="module" v-if="!module.children">-->
<!-- <q-item-->
<!-- class="module"-->
<!-- clickable-->
<!-- v-ripple-->
<!-- active-class="text-primary"-->
<!-- :key="module.title"-->
<!-- :to="{ name: module.stateName }"-->
<!-- v-if="!module.roles || !module.roles.length || hasAny(module.roles)"-->
<!-- >-->
<!-- <q-item-section avatar :if="module.icon">-->
<!-- <q-icon :name="module.icon" />-->
<!-- </q-item-section>-->
<!-- <q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>-->
<!-- <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>-->
<!-- </q-item>-->
<!-- </div>-->
<!-- <template v-if="module.children">-->
<!-- <q-expansion-item-->
<!-- class="module"-->
<!-- active-class="text-primary"-->
<!-- :label="t(`${module.name}.pageTitles.${module.title}`)"-->
<!-- v-if="!module.roles || !module.roles.length || hasAny(module.roles)"-->
<!-- :to="{ name: module.stateName }"-->
<!-- >-->
<!-- <template #header>-->
<!-- <q-item-section avatar>-->
<!-- <q-icon :name="module.icon"></q-icon>-->
<!-- </q-item-section>-->
<!-- <q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>-->
<!-- <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 module.children" :key="section.title">-->
<!-- <q-item-->
<!-- clickable-->
<!-- v-ripple-->
<!-- active-class="text-primary"-->
<!-- :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>-->
<!-- </q-list>-->
<!-- </q-expansion-item>-->
</template> </template>
<!--<style>--> <style>
<!--.module .icon-pin,--> .pinned .icon-pin,
<!--.module .icon-pin_off {--> .pinned .icon-pin_off {
<!-- visibility: hidden;--> visibility: hidden;
<!--}--> }
<!--.module:hover .icon-pin,-->
<!--.module:hover .icon-pin_off {--> .pinned:hover .icon-pin,
<!-- visibility: visible;--> .pinned:hover .icon-pin_off {
<!--}--> visibility: visible;
<!--</style>--> }
</style>

View File

@ -0,0 +1,26 @@
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({
item: {
type: Object,
required: true,
},
});
const item = computed(() => props.item);
</script>
<template>
<q-item active-class="text-primary" :to="{ name: item.name }" clickable v-ripple>
<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>

View File

@ -0,0 +1,51 @@
<script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import LeftMenuItem from './LeftMenuItem.vue';
const route = useRoute();
const { t } = useI18n();
const props = defineProps({
item: {
type: Object,
required: true,
},
group: {
type: String,
default: '',
},
});
const item = computed(() => props.item);
const isOpened = computed(() => {
const { matched } = route;
const { name } = item.value;
return matched.some((item) => item.name === name);
});
</script>
<template>
<q-expansion-item
:group="props.group"
active-class="text-primary"
:label="item.title"
:to="{ name: item.name }"
expand-separator
:default-opened="isOpened"
>
<template #header>
<q-item-section avatar>
<q-icon :name="item.icon"></q-icon>
</q-item-section>
<q-item-section>{{ t(item.title) }}</q-item-section>
<q-item-section side>
<slot name="side" :item="item" />
</q-item-section>
</template>
<template v-for="section in item.children" :key="section.name">
<left-menu-item :item="section" />
</template>
</q-expansion-item>
</template>

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
import UserPanel from 'components/UserPanel.vue'; import UserPanel from 'components/UserPanel.vue';
import FavoriteModules from './FavoriteModules.vue'; import PinnedModules from './PinnedModules.vue';
const { t } = useI18n(); const { t } = useI18n();
const session = useSession(); const session = useSession();
@ -43,11 +43,11 @@ function onToggleDrawer() {
<q-space></q-space> <q-space></q-space>
<div class="q-pl-sm q-gutter-sm row items-center no-wrap"> <div class="q-pl-sm q-gutter-sm row items-center no-wrap">
<div id="header-actions"></div> <div id="header-actions"></div>
<q-btn id="favoriteModules" icon="apps" flat dense rounded> <q-btn id="pinnedModules" icon="apps" flat dense rounded>
<q-tooltip bottom> <q-tooltip bottom>
{{ t('globals.favoriteModules') }} {{ t('globals.pinnedModules') }}
</q-tooltip> </q-tooltip>
<FavoriteModules /> <PinnedModules />
</q-btn> </q-btn>
<q-btn rounded dense flat no-wrap id="user"> <q-btn rounded dense flat no-wrap id="user">
<q-avatar size="lg"> <q-avatar size="lg">

View File

@ -0,0 +1,57 @@
<script setup>
import { onMounted, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useNavigationStore } from 'src/stores/useNavigationStore';
const navigation = useNavigationStore();
const { t } = useI18n();
onMounted(() => {
navigation.fetchPinned();
});
const pinnedModules = computed(() => navigation.getPinnedModules());
</script>
<template>
<q-menu
anchor="bottom left"
class="row q-pa-md q-col-gutter-lg"
max-width="350px"
max-height="400px"
v-if="pinnedModules.length"
>
<div v-for="item of pinnedModules" :key="item.title" class="row no-wrap q-pa-xs flex-item">
<q-btn
align="evenly"
padding="16px"
flat
stack
size="lg"
:icon="item.icon"
color="primary"
class="col-4 button"
:to="{ name: item.name }"
>
<div class="text-center text-primary button-text">
{{ t(item.title) }}
</div>
</q-btn>
</div>
</q-menu>
</template>
<style lang="scss" scoped>
.flex-item {
width: 100px;
}
.button {
width: 100%;
line-height: normal;
align-items: center;
}
.button-text {
font-size: 10px;
margin-top: 5px;
}
</style>

View File

@ -45,7 +45,7 @@ onMounted(async () => {
}); });
function updatePreferences() { function updatePreferences() {
if (user.value.darkMode) { if (user.value.darkMode !== null) {
darkMode.value = user.value.darkMode; darkMode.value = user.value.darkMode;
} }
if (user.value.lang) { if (user.value.lang) {

View File

@ -1,96 +0,0 @@
// import routes from 'src/router/routes';
// import { ref } from 'vue';
import axios from 'axios';
// const favorites = ref([]);
//const modules = ref([]);
// const mainRoute = routes.find((route) => route.path === '/');
// const moduleRoutes = (mainRoute && mainRoute.children) || [];
//
// for (const route of moduleRoutes) {
// const module = {
// stateName: route.name,
// name: route.name.toLowerCase(),
// roles: [],
// };
//
// if (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);
// }
export function useNavigation() {
const modules = [
'customer',
'claim',
'ticket'
];
// const salixModules = {
// customer: 'Clients',
// claim: 'Claims',
// entry: 'Entries',
// invoiceIn: 'Invoices In',
// invoiceOut: 'Invoices Out',
// item: 'Items',
// monitor: 'Monitors',
// order: 'Orders',
// route: 'Routes',
// supplier: 'Suppliers',
// ticket: 'Tickets',
// travel: 'Travels',
// user: 'Users',
// worker: 'Workers',
// zone: 'Zones',
// };
// async function fetchFavorites() {
// const response = await axios.get('StarredModules/getStarredModules');
//
// const filteredModules = modules.value.filter((module) => {
// return response.data.find((element) => element.moduleFk == salixModules[module.name]);
// });
//
// return (favorites.value = filteredModules);
// }
//
async function togglePinned(moduleName, event) {
if (event.defaultPrevented) return;
event.preventDefault();
event.stopPropagation();
await axios.post('StarredModules/toggleStarredModule', { moduleName });
// updateFavorites(moduleName);
}
// function updateFavorites(name) {
// if (!favorites.value.find((module) => module.name == name)) {
// const newStarreModule = modules.value.find((module) => module.name == name);
// favorites.value.push(newStarreModule);
// } else {
// const moduleToRemove = favorites.value.find((module) => module.name == name);
// favorites.value.splice(favorites.value.indexOf(moduleToRemove), 1);
// }
// }
return { modules, togglePinned };
}

View File

@ -2,9 +2,11 @@ import toLowerCase from './toLowerCase';
import toDate from './toDate'; import toDate from './toDate';
import toCurrency from './toCurrency'; import toCurrency from './toCurrency';
import toPercentage from './toPercentage'; import toPercentage from './toPercentage';
import toLowerCamel from './toLowerCamel';
export { export {
toLowerCase, toLowerCase,
toLowerCamel,
toDate, toDate,
toCurrency, toCurrency,
toPercentage, toPercentage,

View File

@ -0,0 +1,5 @@
export default function toLowerCamel(value) {
if (!value) return;
if (typeof (value) !== 'string') return value;
return value.charAt(0).toLowerCase() + value.slice(1);
}

View File

@ -9,7 +9,7 @@ export default {
backToDashboard: 'Return to dashboard', backToDashboard: 'Return to dashboard',
notifications: 'Notifications', notifications: 'Notifications',
userPanel: 'User panel', userPanel: 'User panel',
favoriteModules: 'Favorite modules', pinnedModules: 'Pinned modules',
darkMode: 'Dark mode', darkMode: 'Dark mode',
logOut: 'Log out', logOut: 'Log out',
dataSaved: 'Data saved', dataSaved: 'Data saved',
@ -268,5 +268,9 @@ export default {
summary: 'Summary', summary: 'Summary',
moreOptions: 'More options', moreOptions: 'More options',
}, },
leftMenu: {
addToPinned: 'Add to pinned',
removeFromPinned: 'Remove from pinned',
},
}, },
}; };

View File

@ -9,7 +9,7 @@ export default {
backToDashboard: 'Volver al tablón', backToDashboard: 'Volver al tablón',
notifications: 'Notificaciones', notifications: 'Notificaciones',
userPanel: 'Panel de usuario', userPanel: 'Panel de usuario',
favoriteModules: 'Módulos favoritos', pinnedModules: 'Módulos fijados',
darkMode: 'Modo oscuro', darkMode: 'Modo oscuro',
logOut: 'Cerrar sesión', logOut: 'Cerrar sesión',
dataSaved: 'Datos guardados', dataSaved: 'Datos guardados',
@ -267,5 +267,9 @@ export default {
summary: 'Resumen', summary: 'Resumen',
moreOptions: 'Más opciones', moreOptions: 'Más opciones',
}, },
leftMenu: {
addToPinned: 'Añadir a fijados',
removeFromPinned: 'Eliminar de fijados',
},
}, },
}; };

View File

@ -1,12 +1,19 @@
<script setup> <script setup>
// import { useI18n } from 'vue-i18n'; import { onMounted, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import LeftMenu from 'components/LeftMenu.vue'; import LeftMenu from 'components/LeftMenu.vue';
// import { useNavigation } from 'composables/useNavigation'; import { useNavigationStore } from 'src/stores/useNavigationStore';
// const { t } = useI18n();
const state = useState(); const state = useState();
// const modules = useNavigation(); const navigation = useNavigationStore();
const { t } = useI18n();
onMounted(() => {
navigation.fetchPinned();
});
const pinnedModules = computed(() => navigation.getPinnedModules());
</script> </script>
<template> <template>
@ -15,38 +22,34 @@ const state = useState();
<LeftMenu /> <LeftMenu />
</q-scroll-area> </q-scroll-area>
</q-drawer> </q-drawer>
<!-- <q-page-container>--> <q-page-container>
<!-- <q-page class="q-pa-md">--> <q-page class="q-pa-md">
<!-- <div class="row items-start wrap q-col-gutter-md q-mb-lg">--> <div class="row items-start wrap q-col-gutter-md q-mb-lg">
<!-- <div class="col-12 col-md" v-if="modules.favorites.value.length">--> <div class="col-12 col-md" v-if="pinnedModules.length">
<!-- <div class="text-h6 text-grey-8 q-mb-sm">{{ t('globals.favoriteModules') }}</div>--> <div class="text-h6 text-grey-8 q-mb-sm">{{ t('globals.pinnedModules') }}</div>
<!-- <q-card class="row flex-container">--> <q-card class="row flex-container">
<!-- <div--> <div v-for="item of pinnedModules" :key="item.title" class="row no-wrap q-pa-xs flex-item">
<!-- v-for="module of modules.favorites.value"--> <q-btn
<!-- :key="module.title"--> align="evenly"
<!-- class="row no-wrap q-pa-xs flex-item"--> padding="16px"
<!-- >--> flat
<!-- <q-btn--> stack
<!-- align="evenly"--> size="lg"
<!-- padding="16px"--> :icon="item.icon"
<!-- flat--> color="orange-6"
<!-- stack--> class="col-4 button"
<!-- size="lg"--> :to="{ name: item.name }"
<!-- :icon="module.icon"--> >
<!-- color="orange-6"--> <div class="text-center text-primary button-text">
<!-- class="col-4 button"--> {{ t(item.title) }}
<!-- :to="{ name: module.stateName }"--> </div>
<!-- >--> </q-btn>
<!-- <div class="text-center text-primary button-text">--> </div>
<!-- {{ t(`${module.name}.pageTitles.${module.title}`) }}--> </q-card>
<!-- </div>--> </div>
<!-- </q-btn>--> </div>
<!-- </div>--> </q-page>
<!-- </q-card>--> </q-page-container>
<!-- </div>-->
<!-- </div>-->
<!-- </q-page>-->
<!-- </q-page-container>-->
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,36 +1,84 @@
import { onMounted, ref } from 'vue';
import axios from 'axios'; import axios from 'axios';
import { ref } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { toLowerCamel } from 'src/filters';
import { useRole } from 'src/composables/useRole';
import routes from 'src/router/modules';
export const useNavigationStore = defineStore('navigationStore', () => { export const useNavigationStore = defineStore('navigationStore', () => {
const modules = ['customer', 'claim', 'ticket']; const modules = ['customer', 'claim', 'ticket'];
const pinned = ref([]); const pinnedModules = ref([]);
const role = useRole();
onMounted(() => fetchPinned()) function getModules() {
const modulesRoutes = ref([]);
for (const module of modules) {
const moduleDef = routes.find((route) => toLowerCamel(route.name) === module);
const item = addMenuItem(module, moduleDef, modulesRoutes.value);
if (!item) continue;
item.module = module;
item.isPinned = false;
if (pinnedModules.value.includes(module)) {
item.isPinned = true;
}
}
return modulesRoutes;
}
function getPinnedModules() {
const modules = getModules();
return modules.value.filter((item) => item.isPinned);
}
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;
}
async function fetchPinned() { async function fetchPinned() {
const response = await axios.get('StarredModules/getStarredModules'); if (pinnedModules.value.length) return;
// const filteredModules = modules.value.filter((module) => {
// return response.data.find((element) => element.moduleFk == salixModules[module.name]);
// });
return (pinned.value = response.data); const response = await axios.get('StarredModules/getStarredModules');
pinnedModules.value = response.data.map((row) => row.moduleFk);
} }
function togglePinned(module) { function togglePinned(module) {
if (pinned.value.includes(module)) { if (pinnedModules.value.includes(module)) {
const index = pinned.value.indexOf(module); const index = pinnedModules.value.indexOf(module);
pinned.value.splice(index, 1); pinnedModules.value.splice(index, 1);
return; return;
} }
pinned.value.push(module); pinnedModules.value.push(module);
} }
return { return {
modules, modules,
pinned, pinnedModules,
getModules,
getPinnedModules,
fetchPinned,
togglePinned, togglePinned,
addMenuItem,
}; };
}); });