From 8799799214df13a7b4e2b9361476b74e403dbd0b Mon Sep 17 00:00:00 2001 From: puriphatt Date: Wed, 2 Jul 2025 14:58:44 +0700 Subject: [PATCH] refactor: replace role-based access checks with canAccess utility in menu components --- src/layouts/DrawerComponent.vue | 71 +++++------------------------ src/pages/MainPage.vue | 25 ++-------- src/router/routes.ts | 65 +++++++++++++------------- src/stores/utils/index.ts | 81 ++++++++++++++++++++++++++++++++- 4 files changed, 131 insertions(+), 111 deletions(-) diff --git a/src/layouts/DrawerComponent.vue b/src/layouts/DrawerComponent.vue index 6875f9e8..9c6572f6 100644 --- a/src/layouts/DrawerComponent.vue +++ b/src/layouts/DrawerComponent.vue @@ -7,7 +7,7 @@ import useMyBranch from 'stores/my-branch'; import { getUserId, getRole } from 'src/services/keycloak'; import { useQuasar } from 'quasar'; import { useI18n } from 'vue-i18n'; -import { isRoleInclude } from 'src/stores/utils'; +import { canAccess } from 'src/stores/utils'; type Menu = { label: string; @@ -71,82 +71,41 @@ function initMenu() { { label: 'branch', route: '/branch-management', - hidden: !isRoleInclude([ - 'system', - 'head_of_admin', - 'admin', - 'branch_manager', - 'head_of_accountant', - ]), + hidden: !canAccess('branch'), }, { label: 'personnel', route: '/personnel-management', - hidden: !isRoleInclude([ - 'owner', - 'system', - 'head_of_admin', - 'admin', - 'branch_manager', - ]), + hidden: !canAccess('personnel'), }, { label: 'workflow', route: '/workflow', - hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']), + hidden: !canAccess('workflow'), }, { label: 'property', route: '/property', - hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']), + hidden: !canAccess('workflow'), }, { label: 'productService', route: '/product-service', - hidden: !isRoleInclude([ - 'system', - 'head_of_admin', - 'admin', - 'branch_manager', - 'head_of_accountant', - 'head_of_sale', - 'sale', - ]), }, { label: 'customer', route: '/customer-management', - hidden: !isRoleInclude([ - 'system', - 'head_of_admin', - 'admin', - 'branch_manager', - 'head_of_accountant', - 'accountant', - 'head_of_sale', - 'sale', - ]), + hidden: !canAccess('customer'), }, { label: 'agencies', route: '/agencies-management', - hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']), }, ], }, { label: 'menu.sales', icon: 'mdi-store-settings-outline', - hidden: !isRoleInclude([ - 'system', - 'head_of_admin', - 'admin', - 'branch_manager', - 'head_of_accountant', - 'accountant', - 'head_of_sale', - 'sale', - ]), children: [ { label: 'quotation', route: '/quotation' }, { label: 'invoice', route: '/invoice' }, @@ -169,16 +128,7 @@ function initMenu() { label: 'menu.account', icon: 'mdi-bank-outline', disabled: false, - hidden: !isRoleInclude([ - 'system', - 'head_of_admin', - 'admin', - 'branch_manager', - 'head_of_accountant', - 'accountant', - 'head_of_sale', - 'sale', - ]), + hidden: !canAccess('account'), children: [ { label: 'receipt', route: '/receipt' }, { label: 'creditNote', route: '/credit-note' }, @@ -200,10 +150,13 @@ function initMenu() { { label: 'menu.overall', icon: 'mdi-monitor-dashboard', - hidden: !isRoleInclude(['system', 'head_of_admin', 'admin', 'executive']), children: [ { label: 'report', route: '/report' }, - { label: 'dashboard', route: '/dash-board' }, + { + label: 'dashboard', + route: '/dash-board', + hidden: !canAccess('dashBoard'), + }, ], }, diff --git a/src/pages/MainPage.vue b/src/pages/MainPage.vue index 24d2f8fb..de8d1f7d 100644 --- a/src/pages/MainPage.vue +++ b/src/pages/MainPage.vue @@ -2,17 +2,15 @@ import MenuItem from 'components/00_home/MenuItem.vue'; import { useNavigator } from 'src/stores/navigator'; import { onMounted, ref } from 'vue'; -import { getRole } from 'src/services/keycloak'; +import { canAccess } from 'src/stores/utils'; const navigatorStore = useNavigator(); const menu = ref['$props']['list']>([]); -const role = ref(); onMounted(() => { navigatorStore.current.title = ''; navigatorStore.current.path = [{ text: '' }]; - role.value = getRole(); menu.value = [ { value: 'branch-management', @@ -20,14 +18,7 @@ onMounted(() => { color: 'green', title: 'menu.branch', caption: 'menu.branchCaption', - hidden: - role.value.includes('admin') || - role.value.includes('branch_admin') || - role.value.includes('head_of_admin') || - role.value.includes('system') || - role.value.includes('owner') - ? false - : true, + hidden: !canAccess('branch'), }, { value: 'personnel-management', @@ -35,15 +26,7 @@ onMounted(() => { color: 'cyan', title: 'menu.user', caption: 'menu.userCaption', - hidden: - role.value.includes('admin') || - role.value.includes('branch_admin') || - role.value.includes('head_of_admin') || - role.value.includes('system') || - role.value.includes('owner') || - role.value.includes('branch_manager') - ? false - : true, + hidden: !canAccess('personnel'), }, { value: 'customer-management', @@ -52,6 +35,7 @@ onMounted(() => { title: 'menu.customer', caption: 'menu.customerCaption', isax: true, + hidden: !canAccess('customer'), }, { value: 'product-service', @@ -115,6 +99,7 @@ onMounted(() => { title: 'menu.dashboard', caption: 'menu.dashboardCaption', isax: true, + hidden: !canAccess('dashBoard'), }, ]; }); diff --git a/src/router/routes.ts b/src/router/routes.ts index c3ef0438..86a7fa2a 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -1,5 +1,5 @@ import { RouteRecordRaw } from 'vue-router'; -import { isRoleInclude } from 'stores/utils'; +import { isRoleInclude, canAccess } from 'stores/utils'; const routes: RouteRecordRaw[] = [ { @@ -16,21 +16,8 @@ const routes: RouteRecordRaw[] = [ path: '/branch-management', name: 'BranchManagement', beforeEnter: (to, from, next) => { - if ( - isRoleInclude([ - 'admin', - 'branch_admin', - 'head_of_admin', - 'head_of_account', - 'system', - 'owner', - 'branch_manager', - ]) - ) { - next(); - } else { - next('/'); - } + if (canAccess('branch')) next(); + else next('/'); }, component: () => import('pages/01_branch-management/MainPage.vue'), }, @@ -38,36 +25,36 @@ const routes: RouteRecordRaw[] = [ path: '/personnel-management', name: 'PersonnelManagement', beforeEnter: (to, from, next) => { - if ( - isRoleInclude([ - 'admin', - 'branch_admin', - 'head_of_admin', - 'system', - 'owner', - 'branch_manager', - ]) - ) { - next(); - } else { - next('/'); - } + if (canAccess('personnel')) next(); + else next('/'); }, component: () => import('pages/02_personnel-management/MainPage.vue'), }, { path: '/customer-management', name: 'CustomerManagement', + beforeEnter: (to, from, next) => { + if (canAccess('customer')) next(); + else next('/'); + }, component: () => import('pages/03_customer-management/MainPage.vue'), }, { path: '/customer-management/:customerId', name: 'CustomerSpecificManagement', + beforeEnter: (to, from, next) => { + if (canAccess('customer')) next(); + else next('/'); + }, component: () => import('pages/03_customer-management/MainPage.vue'), }, { path: '/customer-management/:customerId/branch', name: 'CustomerBranchManagement', + beforeEnter: (to, from, next) => { + if (canAccess('customer')) next(); + else next('/'); + }, component: () => import('pages/03_customer-management/MainPage.vue'), }, { @@ -78,11 +65,19 @@ const routes: RouteRecordRaw[] = [ { path: '/workflow', name: 'Workflow', + beforeEnter: (to, from, next) => { + if (canAccess('workflow')) next(); + else next('/'); + }, component: () => import('pages/04_flow-managment/MainPage.vue'), }, { path: '/property', name: 'Property', + beforeEnter: (to, from, next) => { + if (canAccess('workflow')) next(); + else next('/'); + }, component: () => import('pages/04_property-managment/MainPage.vue'), }, { @@ -98,6 +93,10 @@ const routes: RouteRecordRaw[] = [ { path: '/agencies-management', name: 'agencies-management', + beforeEnter: (to, from, next) => { + if (canAccess('agencies')) next(); + else next('/'); + }, component: () => import('pages/07_agencies-management/MainPage.vue'), }, { @@ -138,6 +137,10 @@ const routes: RouteRecordRaw[] = [ { path: '/dash-board', name: 'dashBoard', + beforeEnter: (to, from, next) => { + if (canAccess('dashBoard')) next(); + else next('/'); + }, component: () => import('pages/15_dash-board/MainPage.vue'), }, { @@ -225,7 +228,7 @@ const routes: RouteRecordRaw[] = [ }, { path: '/receipt/:id', - name: 'receiptform', + name: 'receiptForm', component: () => import('pages/13_receipt/MainPage.vue'), }, { diff --git a/src/stores/utils/index.ts b/src/stores/utils/index.ts index 39beb6d6..4b63c72b 100644 --- a/src/stores/utils/index.ts +++ b/src/stores/utils/index.ts @@ -221,11 +221,90 @@ export function checkTabBeforeAdd(data: unknown[], except?: string[]) { export function isRoleInclude(role2check: string[]): boolean { const roles = getRole() ?? []; - const isIncluded = role2check.some((r) => roles.includes(r)); + const filterRole = roles.filter( + (role: string) => + role !== 'offline_access' && + role !== 'uma_authorization' && + !role.startsWith('default-roles'), + ); + const isIncluded = role2check.some((r) => filterRole.includes(r)); return isIncluded; } +export function canAccess( + menu: string, + action: 'edit' | 'view' = 'view', +): boolean { + // uma_authorization = all roles + const allRoles = [ + 'head_of_admin', + 'admin', + 'executive', + 'accountant', + 'branch_admin', + 'branch_manager', + 'branch_accountant', + 'head_of_sale', + 'sale', + 'data_entry', + 'document_checker', + 'messenger', + 'corporate_customer', + 'agency', + ]; + + const permissions = { + branch: { + edit: allRoles.slice(0, 7), + view: allRoles.slice(0, 7), + }, + personnel: { + edit: allRoles.slice(0, 6).filter((r) => r !== 'accountant'), + view: allRoles.slice(0, 6).filter((r) => r !== 'accountant'), + }, + product: { + edit: allRoles.slice(0, 7), + view: allRoles, + }, + workflow: { + edit: allRoles.slice(0, 6), + view: allRoles.filter((r) => r !== 'branch_accountant'), + }, + customer: { + edit: allRoles.slice(0, 6), + view: allRoles.slice(0, 8), + }, + agencies: { + edit: allRoles.slice(0, 7), + view: allRoles, + }, + related: { + // ใช้กับหลายเมนู + edit: allRoles.slice(0, 6), + view: allRoles, + }, + account: { + edit: allRoles.slice(0, 6), + view: allRoles.slice(0, 7), + }, + uploadSlip: { + edit: allRoles.slice(0, 6), + view: allRoles.filter((r) => r !== 'head_of_sale' && r !== 'sale'), + }, + dashBoard: { + edit: allRoles.slice(0, 6).filter((r) => r !== 'admin'), + view: allRoles.filter((r) => r !== 'admin'), + }, + }; + const roles = getRole() ?? []; + if (roles.includes('system')) return true; + + const allowedRoles = permissions[menu]?.[action] || []; + + return allowedRoles.some((role: string) => roles.includes(role)); +} + export function resetScrollBar(elementId: string) { const element = document.getElementById(elementId);