Merge pull request #158 from dikstub-rssa/feat/menu-structure
Feat/menu structure
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
const navMenu = ref([])
|
||||
type NavGroup = {
|
||||
heading?: string
|
||||
items: any[]
|
||||
}
|
||||
|
||||
const { getActiveRole } = useUserStore()
|
||||
|
||||
const navMenu = ref<NavGroup[]>([])
|
||||
|
||||
const teams: {
|
||||
name: string
|
||||
@@ -12,7 +19,14 @@ const teams: {
|
||||
plan: 'Saiful Anwar Hospital',
|
||||
},
|
||||
]
|
||||
const sidebar = {
|
||||
|
||||
type Sidebar = {
|
||||
collapsible: 'offcanvas' | 'icon' | 'none'
|
||||
side: 'left' | 'right'
|
||||
variant: 'sidebar' | 'floating' | 'inset'
|
||||
}
|
||||
|
||||
const sidebar: Sidebar = {
|
||||
collapsible: 'offcanvas', // 'offcanvas' | 'icon' | 'none'
|
||||
side: 'left', // 'left' | 'right'
|
||||
variant: 'sidebar', // 'sidebar' | 'floating' | 'inset'
|
||||
@@ -29,18 +43,34 @@ onMounted(async () => {
|
||||
await setMenu()
|
||||
})
|
||||
|
||||
async function setMenu() {
|
||||
const activeRole = getActiveRole()
|
||||
const activeRoleParts = activeRole ? activeRole.split('|') : []
|
||||
const role = activeRoleParts[0]+(activeRoleParts.length > 1 ? `-${activeRoleParts[1]}` : '')
|
||||
try {
|
||||
const res = await fetch(`/side-menu-items/${role.toLowerCase()}.json`)
|
||||
const rawMenu = await res.text()
|
||||
navMenu.value = JSON.parse(rawMenu)
|
||||
} catch (e) {
|
||||
const res = await fetch(`/side-menu-items/blank.json`)
|
||||
const rawMenu = await res.text()
|
||||
navMenu.value = JSON.parse(rawMenu)
|
||||
}
|
||||
}
|
||||
|
||||
function resolveNavItemComponent(item: any): any {
|
||||
if ('children' in item) return resolveComponent('LayoutSidebarNavGroup')
|
||||
|
||||
return resolveComponent('LayoutSidebarNavLink')
|
||||
}
|
||||
|
||||
async function setMenu() {
|
||||
const position_code = 'sys'
|
||||
const res = await fetch(`/side-menu-items/${position_code}.json`)
|
||||
const rawMenu = await res.text()
|
||||
navMenu.value = JSON.parse(rawMenu)
|
||||
}
|
||||
watch(getActiveRole, async () => {
|
||||
await setMenu()
|
||||
// const activeRole = getActiveRole()
|
||||
// const res = await fetch(`/side-menu-items/${activeRole}.json`)
|
||||
// const rawMenu = await res.text()
|
||||
// navMenu.value = JSON.parse(rawMenu)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -10,8 +10,8 @@ import { useSidebar } from '~/components/pub/ui/sidebar'
|
||||
// }>()
|
||||
|
||||
const { isMobile } = useSidebar()
|
||||
const { logout } = useUserStore()
|
||||
const userStore = useUserStore().user
|
||||
const { user, logout, setActiveRole, getActiveRole } = useUserStore()
|
||||
// const userStore = useUserStore().user
|
||||
|
||||
function handleLogout() {
|
||||
navigateTo('/auth/login')
|
||||
@@ -32,19 +32,19 @@ const showModalTheme = ref(false)
|
||||
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar class="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src="" :alt="userStore?.user_name || 'system'" />
|
||||
<AvatarImage src="" :alt="user.user_name || 'system'" />
|
||||
<AvatarFallback class="rounded-lg">
|
||||
{{
|
||||
userStore?.user_name
|
||||
user.user_name
|
||||
?.split(' ')
|
||||
.map((n) => n[0])
|
||||
.map((n: string) => n[0])
|
||||
.join('') || ''
|
||||
}}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-semibold">{{ userStore?.user_name || '' }}</span>
|
||||
<span class="truncate text-xs">{{ userStore?.user_email || '' }}</span>
|
||||
<span class="truncate font-semibold">{{ user.user_name || '' }}</span>
|
||||
<span class="truncate text-xs">{{ user.user_email || '' }}</span>
|
||||
</div>
|
||||
<Icon name="i-lucide-chevrons-up-down" class="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
@@ -52,35 +52,40 @@ const showModalTheme = ref(false)
|
||||
<DropdownMenuContent
|
||||
class="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg bg-white"
|
||||
:side="isMobile ? 'bottom' : 'right'"
|
||||
align="end"
|
||||
:align="'end'"
|
||||
>
|
||||
<DropdownMenuLabel class="p-0 font-normal">
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar class="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src="" :alt="userStore?.user_name || 'system'" />
|
||||
<AvatarImage src="" :alt="user.user_name || 'system'" />
|
||||
<AvatarFallback class="rounded-lg">
|
||||
{{
|
||||
userStore?.user_name
|
||||
user.user_name
|
||||
?.split(' ')
|
||||
.map((n) => n[0])
|
||||
.map((n: string) => n[0])
|
||||
.join('') || ''
|
||||
}}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-semibold">{{ userStore?.user_name || '' }}</span>
|
||||
<span class="truncate text-xs">{{ userStore?.user_email || '' }}</span>
|
||||
<span class="truncate font-semibold">{{ user.user_name || '' }}</span>
|
||||
<span class="truncate text-xs">{{ user.user_email || '' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem class="hover:bg-gray-100" @click="showModalTheme = true">
|
||||
<DropdownMenuItem class="hover:bg-gray-100" @click="showModalTheme = true">
|
||||
<Icon name="i-lucide-user" />
|
||||
Profile
|
||||
</DropdownMenuItem>
|
||||
<template v-if="user.roles.length > 1">
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem v-for="role in user.roles" class="hover:bg-gray-100" @click="setActiveRole(role)">
|
||||
<Icon name="i-lucide-user" />
|
||||
Profile
|
||||
{{ role }}
|
||||
<Icon name="i-lucide-check" class="ml-auto" v-if="getActiveRole() === role" />
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</template>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem class="hover:bg-gray-100" @click="handleLogout">
|
||||
<Icon name="i-lucide-log-out" />
|
||||
|
||||
+48
-48
@@ -2,67 +2,67 @@ import type { RoleAccess } from '~/models/role'
|
||||
|
||||
export const PAGE_PERMISSIONS = {
|
||||
'/patient': {
|
||||
doctor: ['R'],
|
||||
nurse: ['R'],
|
||||
admisi: ['C', 'R', 'U', 'D'],
|
||||
pharmacy: ['R'],
|
||||
billing: ['R'],
|
||||
management: ['R'],
|
||||
'emp|doc': ['R'],
|
||||
'emp|nur': ['R'],
|
||||
'emp|reg': ['C', 'R', 'U', 'D'],
|
||||
'emp|pha': ['R'],
|
||||
'emp|pay': ['R'],
|
||||
'emp|mng': ['R'],
|
||||
},
|
||||
'/doctor': {
|
||||
doctor: ['C', 'R', 'U', 'D'],
|
||||
nurse: ['R'],
|
||||
admisi: ['R'],
|
||||
pharmacy: ['R'],
|
||||
billing: ['R'],
|
||||
management: ['R'],
|
||||
'emp|doc': ['C', 'R', 'U', 'D'],
|
||||
'emp|nur': ['R'],
|
||||
'emp|reg': ['R'],
|
||||
'emp|pha': ['R'],
|
||||
'emp|pay': ['R'],
|
||||
'emp|mng': ['R'],
|
||||
},
|
||||
'/satusehat': {
|
||||
doctor: ['R'],
|
||||
nurse: ['R'],
|
||||
admisi: ['C', 'R', 'U', 'D'],
|
||||
pharmacy: ['R'],
|
||||
billing: ['R'],
|
||||
management: ['R'],
|
||||
'emp|doc': ['R'],
|
||||
'emp|nur': ['R'],
|
||||
'emp|reg': ['C', 'R', 'U', 'D'],
|
||||
'emp|pha': ['R'],
|
||||
'emp|pay': ['R'],
|
||||
'emp|mng': ['R'],
|
||||
},
|
||||
'/outpatient/encounter': {
|
||||
doctor: ['C', 'R', 'U', 'D'],
|
||||
nurse: ['C', 'R', 'U', 'D'],
|
||||
admisi: ['R'],
|
||||
pharmacy: ['R'],
|
||||
billing: ['R'],
|
||||
management: ['R'],
|
||||
'emp|doc': ['C', 'R', 'U', 'D'],
|
||||
'emp|nur': ['C', 'R', 'U', 'D'],
|
||||
'emp|reg': ['R'],
|
||||
'emp|pha': ['R'],
|
||||
'emp|pay': ['R'],
|
||||
'emp|mng': ['R'],
|
||||
},
|
||||
'/emergency/encounter': {
|
||||
doctor: ['C', 'R', 'U', 'D'],
|
||||
nurse: ['C', 'R', 'U', 'D'],
|
||||
admisi: ['R'],
|
||||
pharmacy: ['R'],
|
||||
billing: ['R'],
|
||||
management: ['R'],
|
||||
'emp|doc': ['C', 'R', 'U', 'D'],
|
||||
'emp|nur': ['C', 'R', 'U', 'D'],
|
||||
'emp|reg': ['R'],
|
||||
'emp|pha': ['R'],
|
||||
'emp|pay': ['R'],
|
||||
'emp|mng': ['R'],
|
||||
},
|
||||
'/inpatient/encounter': {
|
||||
doctor: ['C', 'R', 'U', 'D'],
|
||||
nurse: ['C', 'R', 'U', 'D'],
|
||||
admisi: ['R'],
|
||||
pharmacy: ['R'],
|
||||
billing: ['R'],
|
||||
management: ['R'],
|
||||
'emp|doc': ['C', 'R', 'U', 'D'],
|
||||
'emp|nur': ['C', 'R', 'U', 'D'],
|
||||
'emp|reg': ['R'],
|
||||
'emp|pha': ['R'],
|
||||
'emp|pay': ['R'],
|
||||
'emp|mng': ['R'],
|
||||
},
|
||||
'/rehab/encounter': {
|
||||
doctor: ['C', 'R', 'U', 'D'],
|
||||
nurse: ['R'],
|
||||
admisi: ['R'],
|
||||
pharmacy: ['R'],
|
||||
billing: ['R'],
|
||||
management: ['R'],
|
||||
'emp|doc': ['C', 'R', 'U', 'D'],
|
||||
'emp|nur': ['R'],
|
||||
'emp|reg': ['R'],
|
||||
'emp|pha': ['R'],
|
||||
'emp|pay': ['R'],
|
||||
'emp|mng': ['R'],
|
||||
},
|
||||
'/rehab/registration': {
|
||||
doctor: ['C', 'R', 'U', 'D'],
|
||||
nurse: ['R'],
|
||||
admisi: ['R'],
|
||||
pharmacy: ['R'],
|
||||
billing: ['R'],
|
||||
management: ['R'],
|
||||
'emp|doc': ['C', 'R', 'U', 'D'],
|
||||
'emp|nur': ['R'],
|
||||
'emp|reg': ['R'],
|
||||
'emp|pha': ['R'],
|
||||
'emp|pay': ['R'],
|
||||
'emp|mng': ['R'],
|
||||
},
|
||||
} as const satisfies Record<string, RoleAccess>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { PAGE_PERMISSIONS } from '~/lib/page-permission'
|
||||
|
||||
definePageMeta({
|
||||
middleware: ['rbac'],
|
||||
roles: ['system', 'doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
||||
roles: ['system', 'emp-doc', 'emp-nur', 'emp-reg', 'emp-pha', 'emp-pay', 'emp-mng'],
|
||||
title: 'Daftar Kunjungan',
|
||||
contentFrame: 'cf-full-width',
|
||||
})
|
||||
@@ -23,7 +23,7 @@ const { checkRole, hasReadAccess } = useRBAC()
|
||||
// Check if user has access to this page
|
||||
const hasAccess = checkRole(roleAccess)
|
||||
if (!hasAccess) {
|
||||
navigateTo('/403')
|
||||
// navigateTo('/403')
|
||||
}
|
||||
|
||||
// Define permission-based computed properties
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<div>Examination</div>
|
||||
</template>
|
||||
+24
-5
@@ -5,12 +5,13 @@ export const useUserStore = defineStore(
|
||||
// const token = useCookie('authentication')
|
||||
|
||||
const isAuthenticated = computed(() => !!user.value)
|
||||
|
||||
const userRole = computed(() => {
|
||||
const roles = user.value?.roles || []
|
||||
return roles.map((input: string) => {
|
||||
const parts = input.split('-')
|
||||
return parts.length > 1 ? parts[1]: parts[0]
|
||||
})
|
||||
return user.value?.roles || []
|
||||
// return roles.map((input: string) => {
|
||||
// const parts = input.split('|')
|
||||
// return parts.length > 1 ? parts[1]: parts[0]
|
||||
// })
|
||||
})
|
||||
|
||||
const login = async (userData: any) => {
|
||||
@@ -21,12 +22,30 @@ export const useUserStore = defineStore(
|
||||
user.value = null
|
||||
}
|
||||
|
||||
const setActiveRole = (role: string) => {
|
||||
if (user.value && user.value.roles.includes(role)) {
|
||||
user.value.activeRole = role
|
||||
}
|
||||
}
|
||||
|
||||
const getActiveRole = () => {
|
||||
if (user.value?.activeRole) {
|
||||
return user.value.activeRole
|
||||
}
|
||||
if (user.value?.roles.length > 0) {
|
||||
user.value.activeRole = user.value.roles[0]
|
||||
return user.value.activeRole
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
isAuthenticated,
|
||||
userRole,
|
||||
login,
|
||||
logout,
|
||||
setActiveRole,
|
||||
getActiveRole,
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"heading": "Menu Utama",
|
||||
"items": [
|
||||
{
|
||||
"title": "Dashboard",
|
||||
"icon": "i-lucide-home",
|
||||
"link": "/"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"heading": "Menu Utama",
|
||||
"items": [
|
||||
{
|
||||
"title": "Dashboard",
|
||||
"icon": "i-lucide-home",
|
||||
"link": "/"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -20,7 +20,7 @@
|
||||
{
|
||||
"title": "Rehabilitasi Medik",
|
||||
"icon": "i-lucide-bike",
|
||||
"link": "/rehab/polyclinic-queue"
|
||||
"link": "/rehab/encounter-queue"
|
||||
},
|
||||
{
|
||||
"title": "Rawat Inap",
|
||||
@@ -13,10 +13,10 @@
|
||||
"children": [
|
||||
{
|
||||
"title": "Antrian Poliklinik",
|
||||
"link": "/outpatient/polyclinic-queue"
|
||||
"link": "/outpatient/encounter-queue"
|
||||
},
|
||||
{
|
||||
"title": "Pendaftaran",
|
||||
"title": "Kunjungan",
|
||||
"link": "/outpatient/encounter"
|
||||
}
|
||||
]
|
||||
@@ -30,7 +30,7 @@
|
||||
"link": "/emergency/triage"
|
||||
},
|
||||
{
|
||||
"title": "Pemeriksaan",
|
||||
"title": "Kunjungan",
|
||||
"link": "/emergency/encounter"
|
||||
}
|
||||
]
|
||||
@@ -42,7 +42,7 @@
|
||||
"children": [
|
||||
{
|
||||
"title": "Antrean Poliklinik",
|
||||
"link": "/rehab/polyclinic-queue"
|
||||
"link": "/rehab/encounter-queue"
|
||||
},
|
||||
{
|
||||
"title": "Kunjungan",
|
||||
@@ -17,7 +17,7 @@
|
||||
},
|
||||
{
|
||||
"title": "Antrian Poliklinik",
|
||||
"link": "/outpatient/polyclinic-queue"
|
||||
"link": "/outpatient/encounter-queue"
|
||||
},
|
||||
{
|
||||
"title": "Kunjungan",
|
||||
@@ -57,7 +57,7 @@
|
||||
},
|
||||
{
|
||||
"title": "Antrean Poliklinik",
|
||||
"link": "/rehab/polyclinic-queue"
|
||||
"link": "/rehab/encounter-queue"
|
||||
},
|
||||
{
|
||||
"title": "Kunjungan",
|
||||
Reference in New Issue
Block a user