585 lines
15 KiB
Vue
585 lines
15 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref, onMounted, watch } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
import { storeToRefs } from 'pinia';
|
|
import { Icon } from '@iconify/vue';
|
|
import useMyBranch from 'stores/my-branch';
|
|
import { getUserId, getRole } from 'src/services/keycloak';
|
|
import { useQuasar } from 'quasar';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { canAccess } from 'src/stores/utils';
|
|
|
|
type Menu = {
|
|
label: string;
|
|
route?: string;
|
|
icon?: string;
|
|
hidden?: boolean;
|
|
disabled?: boolean;
|
|
isax?: boolean;
|
|
noI18n?: boolean;
|
|
children?: Menu[];
|
|
};
|
|
|
|
const router = useRouter();
|
|
const $q = useQuasar();
|
|
const { locale } = useI18n();
|
|
|
|
const userBranch = useMyBranch();
|
|
const { currentMyBranch } = storeToRefs(userBranch);
|
|
|
|
const leftDrawerOpen = defineModel<boolean>('leftDrawerOpen', {
|
|
default: true,
|
|
});
|
|
|
|
const props = defineProps<{
|
|
mini?: boolean;
|
|
}>();
|
|
|
|
const role = ref();
|
|
const menuActive = ref<boolean[]>([]);
|
|
const menuData = ref<Menu[]>([]);
|
|
const currentPath = computed(() => {
|
|
return router.currentRoute.value.path;
|
|
});
|
|
|
|
function navigateTo(label: string, destination?: string) {
|
|
if (!destination) return;
|
|
router.push(`${destination}`);
|
|
}
|
|
|
|
function reActiveMenu() {
|
|
const currMenu = menuData.value.find((m) =>
|
|
m.children?.some((c) => c.route === currentPath.value),
|
|
);
|
|
|
|
const currMenuIndex = menuData.value.findIndex((m) => m === currMenu);
|
|
|
|
if ($q.screen.lt.sm) menuActive.value.fill(false);
|
|
menuActive.value[currMenuIndex] = true;
|
|
}
|
|
|
|
function branchSetting() {
|
|
//TODO: click setting (cog icon) on drawer menu
|
|
}
|
|
|
|
function initMenu() {
|
|
menuData.value = [
|
|
{
|
|
label: 'menu.manage',
|
|
icon: 'mdi-account-cog-outline',
|
|
children: [
|
|
{
|
|
label: 'branch',
|
|
route: '/branch-management',
|
|
hidden: !canAccess('branch'),
|
|
},
|
|
{
|
|
label: 'personnel',
|
|
route: '/personnel-management',
|
|
hidden: !canAccess('personnel'),
|
|
},
|
|
{
|
|
label: 'group',
|
|
route: '/group-management',
|
|
hidden: !canAccess('personnel'),
|
|
},
|
|
{
|
|
label: 'workflow',
|
|
route: '/workflow',
|
|
hidden: !canAccess('workflow'),
|
|
},
|
|
{
|
|
label: 'property',
|
|
route: '/property',
|
|
hidden: !canAccess('workflow'),
|
|
},
|
|
{
|
|
label: 'businessType',
|
|
route: '/business-type',
|
|
},
|
|
{
|
|
label: 'productService',
|
|
route: '/product-service',
|
|
},
|
|
{
|
|
label: 'customer',
|
|
route: '/customer-management',
|
|
hidden: !canAccess('customer'),
|
|
},
|
|
{
|
|
label: 'agencies',
|
|
route: '/agencies-management',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: 'menu.sales',
|
|
icon: 'mdi-store-settings-outline',
|
|
children: [
|
|
{ label: 'quotation', route: '/quotation' },
|
|
{ label: 'invoice', route: '/invoice' },
|
|
],
|
|
},
|
|
{
|
|
label: 'menu.order',
|
|
icon: 'mdi-file-chart-outline',
|
|
disabled: false,
|
|
children: [
|
|
{ label: 'requestList', route: '/request-list' },
|
|
{ label: 'documentCheck', route: '', hidden: true },
|
|
{ label: 'taskOrder', route: '/task-order' },
|
|
{ label: 'goodReceipt', route: '', hidden: true },
|
|
{ label: 'workReceipt', route: '', hidden: true },
|
|
{ label: 'workDelivery', route: '', hidden: true },
|
|
],
|
|
},
|
|
{
|
|
label: 'menu.account',
|
|
icon: 'mdi-bank-outline',
|
|
disabled: false,
|
|
children: [
|
|
{ label: 'receipt', route: '/receipt' },
|
|
{ label: 'creditNote', route: '/credit-note' },
|
|
{ label: 'debitNote', route: '/debit-note' },
|
|
],
|
|
},
|
|
|
|
{
|
|
label: 'menu.manageDocument',
|
|
icon: 'mdi-archive-outline',
|
|
children: [
|
|
{
|
|
label: 'document',
|
|
route: '/document-management',
|
|
},
|
|
],
|
|
},
|
|
|
|
{
|
|
label: 'menu.overall',
|
|
icon: 'mdi-monitor-dashboard',
|
|
children: [
|
|
{ label: 'report', route: '/report' },
|
|
{
|
|
label: 'dashboard',
|
|
route: '/dash-board',
|
|
hidden: !canAccess('dashBoard'),
|
|
},
|
|
],
|
|
},
|
|
|
|
{
|
|
label: 'menu.manual',
|
|
icon: 'mdi-book-open-variant-outline',
|
|
children: [
|
|
{
|
|
label: 'usage',
|
|
route: '/manual',
|
|
},
|
|
{
|
|
label: 'troubleshooting',
|
|
route: '/troubleshooting',
|
|
},
|
|
],
|
|
},
|
|
];
|
|
}
|
|
|
|
watch(
|
|
() => currentPath.value,
|
|
() => {
|
|
if (currentPath.value === '/') {
|
|
menuActive.value.fill(false);
|
|
menuActive.value[0] = true;
|
|
} else reActiveMenu();
|
|
},
|
|
);
|
|
|
|
watch(
|
|
() => props.mini,
|
|
() => {
|
|
if (props.mini) {
|
|
reActiveMenu();
|
|
}
|
|
},
|
|
);
|
|
|
|
watch(
|
|
() => locale.value,
|
|
() => {
|
|
initMenu();
|
|
},
|
|
);
|
|
|
|
onMounted(async () => {
|
|
const uid = getUserId();
|
|
|
|
role.value = getRole();
|
|
|
|
if (!uid) return;
|
|
|
|
if (role.value.includes('system')) {
|
|
const result = await userBranch.fetchListOptionBranch();
|
|
if (result && result.total > 0) currentMyBranch.value = result.result[0];
|
|
}
|
|
const result = await userBranch.fetchListMyBranch(uid);
|
|
if (result && result.total > 0) currentMyBranch.value = result.result[0];
|
|
|
|
initMenu();
|
|
|
|
menuActive.value = menuData.value.map(() => false);
|
|
|
|
if (currentPath.value === '/') menuActive.value[0] = true;
|
|
else reActiveMenu();
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<!-- Drawer -->
|
|
<q-drawer
|
|
no-swipe-open
|
|
v-model="leftDrawerOpen"
|
|
side="left"
|
|
:breakpoint="599"
|
|
class="column justify-between no-wrap"
|
|
:class="{ dark: $q.dark.isActive }"
|
|
:width="mini ? 80 : 256"
|
|
show-if-above
|
|
>
|
|
<section
|
|
class="scroll"
|
|
style="overflow-x: hidden; scrollbar-gutter: stable"
|
|
>
|
|
<header
|
|
class="flex justify-center items-center q-pl-sm cursor-pointer"
|
|
@click="$router.push('/')"
|
|
id="btn-drawer-home"
|
|
style="height: 128px"
|
|
>
|
|
<q-img
|
|
fit="contain"
|
|
:src="mini ? '/logo_jws.png' : '/logo.png'"
|
|
:style="{ filter: `brightness(${$q.dark.isActive ? '1.3' : '1'})` }"
|
|
style="height: 64px"
|
|
/>
|
|
</header>
|
|
|
|
<div id="drawer-menu" class="q-pl-md q-mr-xs q-gutter-y-sm">
|
|
<template
|
|
v-for="(menu, i) in menuData.filter(
|
|
(v) =>
|
|
!(v.children?.length === 0 || v.children?.every((i) => i.hidden)),
|
|
)"
|
|
:key="i"
|
|
>
|
|
<q-expansion-item
|
|
v-if="!menu.hidden"
|
|
:id="menu.label"
|
|
:for="menu.label"
|
|
dense
|
|
:model-value="menuActive[i]"
|
|
expand-icon-class="no-padding"
|
|
:hide-expand-icon="mini"
|
|
:disable="menu.disabled"
|
|
:header-class="{
|
|
row: true,
|
|
'no-padding justify-center': mini,
|
|
'active-menu text-weight-bold': menuActive[i],
|
|
'text-weight-medium': !menu.disabled,
|
|
}"
|
|
@update:model-value="
|
|
() => {
|
|
mini ? '' : (menuActive[i] = !menuActive[i]);
|
|
}
|
|
"
|
|
>
|
|
<template #header>
|
|
<nav class="row items-center q-py-sm no-wrap">
|
|
<i
|
|
v-if="menu.isax"
|
|
:class="`isax ${menu.icon}`"
|
|
style="font-size: 24px"
|
|
/>
|
|
<Icon
|
|
v-else
|
|
:icon="menu.icon || ''"
|
|
width="24px"
|
|
:class="{ 'fix-icon': !menuActive[i] }"
|
|
/>
|
|
<span
|
|
v-if="!mini"
|
|
class="q-pl-sm"
|
|
style="white-space: nowrap"
|
|
:style="!menuActive[i] && `color: var(--foreground)`"
|
|
>
|
|
{{ menu.noI18n ? menu.label : $t(`${menu.label}.title`) }}
|
|
</span>
|
|
<q-tooltip :delay="500">
|
|
{{ menu.noI18n ? menu.label : $t(`${menu.label}.title`) }}
|
|
</q-tooltip>
|
|
|
|
<q-menu
|
|
v-if="mini && !menu.disabled"
|
|
fit
|
|
anchor="center end"
|
|
self="top left"
|
|
:offset="[16, 0]"
|
|
>
|
|
<q-list dense>
|
|
<template v-for="(sub, i) in menu.children" :key="i">
|
|
<q-item
|
|
v-if="!sub.hidden"
|
|
clickable
|
|
:disabled="sub.disabled"
|
|
class="row items-center no-wrap"
|
|
:class="{
|
|
'text-weight-bold app-bg-brand-1':
|
|
currentPath === sub.route,
|
|
}"
|
|
@click="
|
|
() => {
|
|
!sub.disabled && navigateTo('', sub.route);
|
|
}
|
|
"
|
|
:id="`sub-menu-${sub.label}`"
|
|
>
|
|
<span style="white-space: nowrap">
|
|
{{
|
|
sub.noI18n
|
|
? sub.label
|
|
: $t(`${menu.label}.${sub.label}`)
|
|
}}
|
|
</span>
|
|
</q-item>
|
|
</template>
|
|
</q-list>
|
|
</q-menu>
|
|
</nav>
|
|
</template>
|
|
|
|
<q-list v-if="!mini" dense>
|
|
<template v-for="(sub, i) in menu.children" :key="i">
|
|
<div
|
|
v-if="!sub.hidden"
|
|
class="row items-center q-ml-lg q-py-sm no-wrap"
|
|
:class="{ 'q-mt-sm': i === 0, 'sub-menu': !sub.disabled }"
|
|
style="border-left: 2px solid var(--surface-tab)"
|
|
@click="!sub.disabled && navigateTo('', sub.route)"
|
|
:id="`sub-menu-${sub.label}`"
|
|
>
|
|
<nav
|
|
class="row items-center no-wrap"
|
|
:class="{
|
|
active: sub.route && currentPath.includes(sub.route),
|
|
disabled: sub.disabled,
|
|
}"
|
|
>
|
|
<span class="q-px-md" style="white-space: nowrap">
|
|
{{
|
|
sub.noI18n
|
|
? sub.label
|
|
: $t(`${menu.label}.${sub.label}`)
|
|
}}
|
|
</span>
|
|
</nav>
|
|
</div>
|
|
</template>
|
|
</q-list>
|
|
</q-expansion-item>
|
|
</template>
|
|
|
|
<!-- <q-item
|
|
v-for="v in labelMenu.filter((v) => !v.hidden)"
|
|
:id="`drawer-${v.label}`"
|
|
dense
|
|
clickable
|
|
class="row items-center q-my-xs q-px-xs q-py-sm"
|
|
:key="`drawer-${v.label}`"
|
|
:disable="!!v.disabled"
|
|
:class="{
|
|
active: currentPath === v.route,
|
|
'border-active': currentPath === v.route,
|
|
dark: $q.dark.isActive,
|
|
}"
|
|
@click="navigateTo(v.label, v.route)"
|
|
>
|
|
<div class="row justify-center items-center">
|
|
<i
|
|
v-if="v.isax"
|
|
:class="`isax ${v.icon}`"
|
|
style="font-size: 24px"
|
|
/>
|
|
<Icon v-else :icon="v.icon" width="24px" />
|
|
<q-tooltip v-if="mini">
|
|
{{ $t(v.label) }}
|
|
</q-tooltip>
|
|
</div>
|
|
|
|
<div v-if="!mini" class="q-ml-md" style="white-space: nowrap">
|
|
{{ $t(v.label) }}
|
|
</div>
|
|
</q-item> -->
|
|
</div>
|
|
</section>
|
|
|
|
<footer
|
|
class="surface-2 q-px-md q-py-sm row items-center no-wrap justify-start"
|
|
:class="{ 'justify-center': mini }"
|
|
style="min-height: 56px; overflow-x: hidden"
|
|
>
|
|
<q-avatar
|
|
text-color="white"
|
|
size="md"
|
|
:style="`background-color: hsla(var(--violet-${$q.dark.isActive ? '10' : '11'}-hsl)/0.15)`"
|
|
class="relative-position"
|
|
>
|
|
<q-icon
|
|
name="mdi-home-group"
|
|
size="sm"
|
|
:style="`color: var(--violet-${$q.dark.isActive ? '10' : '11'})`"
|
|
/>
|
|
<div class="dot absolute-bottom-right" />
|
|
</q-avatar>
|
|
|
|
<div
|
|
v-if="!mini"
|
|
class="text-caption column q-mx-md col"
|
|
style="white-space: nowrap"
|
|
>
|
|
<span class="text-weight-bold ellipsis full-width">
|
|
{{
|
|
($i18n.locale === 'eng'
|
|
? currentMyBranch?.nameEN
|
|
: currentMyBranch?.name) ?? '-'
|
|
}}
|
|
<q-tooltip>
|
|
{{
|
|
($i18n.locale === 'eng'
|
|
? currentMyBranch?.nameEN
|
|
: currentMyBranch?.name) ?? '-'
|
|
}}
|
|
</q-tooltip>
|
|
</span>
|
|
<span>
|
|
{{ currentMyBranch?.code ?? '-' }}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- v-if="!mini" -->
|
|
<q-btn
|
|
v-if="false"
|
|
dense
|
|
flat
|
|
rounded
|
|
icon="mdi-cog-outline"
|
|
size="sm"
|
|
@click="branchSetting"
|
|
style="margin-left: auto"
|
|
/>
|
|
</footer>
|
|
</q-drawer>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
#drawer-menu :deep(.q-item) {
|
|
color: var(--gray-7);
|
|
border-radius: var(--radius-2);
|
|
|
|
&.dark {
|
|
color: var(--gray-3);
|
|
}
|
|
}
|
|
|
|
#drawer-menu :deep(.q-item.active) {
|
|
--_drawer-item-background-color: var(--brand-1) !important;
|
|
background-color: var(--_drawer-item-background-color);
|
|
color: white;
|
|
|
|
// border-left: 10px solid $secondary;
|
|
}
|
|
|
|
#drawer-menu :deep(.q-item.active)::before {
|
|
display: block;
|
|
position: absolute;
|
|
content: ' ';
|
|
background: var(--brand-2);
|
|
border-radius: 99rem;
|
|
width: 6px;
|
|
left: -0.6rem;
|
|
top: 18%;
|
|
bottom: 18%;
|
|
cursor: context-menu;
|
|
}
|
|
|
|
:deep(.q-drawer) {
|
|
transition: 0.1s width ease-in-out;
|
|
background-color: transparent;
|
|
padding: var(--size-4);
|
|
padding-right: 0;
|
|
}
|
|
|
|
:deep(.q-drawer__content) {
|
|
border-radius: var(--radius-2);
|
|
background-color: rgba(255, 255, 255, 0.5);
|
|
backdrop-filter: blur(11.2px);
|
|
-webkit-backdrop-filter: blur(11.2px);
|
|
|
|
&.dark {
|
|
background-color: hsla(var(--gray-10-hsl) / 0.5);
|
|
}
|
|
}
|
|
|
|
.dot {
|
|
height: 10px;
|
|
width: 10px;
|
|
background-color: var(--teal-6);
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
border: 1.5px solid white;
|
|
}
|
|
|
|
:deep(.active-menu) {
|
|
color: white !important;
|
|
background: var(--brand-1) !important;
|
|
|
|
&
|
|
i.q-icon.mdi.mdi-chevron-down.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated {
|
|
color: white !important;
|
|
}
|
|
}
|
|
|
|
.sub-menu {
|
|
font-weight: 400;
|
|
|
|
& .active {
|
|
color: var(--brand-1) !important;
|
|
font-weight: bold;
|
|
|
|
&::before {
|
|
content: '';
|
|
display: inline-block;
|
|
margin-left: -3px;
|
|
width: 4px;
|
|
height: 18px;
|
|
border-radius: 50px;
|
|
background-color: var(--brand-2);
|
|
}
|
|
}
|
|
|
|
&:hover {
|
|
cursor: pointer;
|
|
background-color: hsla(var(--stone-8-hsl) / 0.1);
|
|
transition: 0.3s ease-in-out;
|
|
border-top-right-radius: var(--radius-2);
|
|
border-bottom-right-radius: var(--radius-2);
|
|
}
|
|
}
|
|
|
|
.fix-icon {
|
|
color: var(--text-mute-2) !important;
|
|
}
|
|
|
|
:deep(.q-item.q-item-type.row.no-wrap.q-item--dense.disabled) {
|
|
opacity: 30% !important;
|
|
}
|
|
</style>
|