This commit is contained in:
2025-07-03 14:57:57 +07:00
parent be4de9f24a
commit 1b05076317
22 changed files with 631 additions and 35 deletions

View File

@@ -3,7 +3,7 @@ import { onMounted, ref, shallowRef, watch } from "vue";
import { useDisplay } from "vuetify";
import sidebarItems from "~/components/layout/full/vertical-sidebar/sidebarItem";
import { Menu2Icon } from "vue-tabler-icons";
import { useCustomizerStore } from "~/store/customizer";
import { useCustomizerStore } from "~/stores/customizer";
const sidebarMenu = shallowRef(sidebarItems);
const customizer = useCustomizerStore();
const { mdAndDown } = useDisplay();
@@ -14,6 +14,9 @@ onMounted(() => {
watch(mdAndDown, (val) => {
sDrawer.value = !val;
});
// console.log("sidebarMenu", sidebarMenu.value);
// console.log("sidebarMenu", sidebarItems.map((item) => item.children));
</script>
<template>
@@ -26,7 +29,7 @@ watch(mdAndDown, (val) => {
customizer.actTheme,
customizer.mini_sidebar ? 'mini-sidebar' : '',
customizer.setHorizontalLayout ? 'horizontalLayout' : 'verticalLayout',
customizer.setBorderCard ? 'cardBordered' : ''
customizer.setBorderCard ? 'cardBordered' : '',
]"
>
<!---Customizer location left side--->

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useTheme } from 'vuetify';
import { useCustomizerStore } from '~/store/customizer';
import { useCustomizerStore } from '~/stores/customizer';
import {
CheckIcon,
LayoutColumnsIcon,

View File

@@ -1,6 +1,6 @@
<script setup>
import { computed } from 'vue';
import { useCustomizerStore } from '~/store/customizer';
import { useCustomizerStore } from '~/stores/customizer';
const customizer = useCustomizerStore();

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useTheme } from 'vuetify';
import { useCustomizerStore } from '~/store/customizer';
import { useCustomizerStore } from '~/stores/customizer';
import { Icon } from '@iconify/vue';
const theme = useTheme();

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref, watch, computed } from 'vue';
import { useCustomizerStore } from '~/store/customizer';
import { useCustomizerStore } from '~/stores/customizer';
// import { useEcomStore } from '@/stores/apps/eCommerce';
// import LanguageDD from './LanguageDD.vue';
import NotificationDD from './NotificationDD.vue';

View File

@@ -1,12 +1,15 @@
<script setup lang="ts">
import { ref, shallowRef } from 'vue';
import { useCustomizerStore } from '~/store/customizer';
import sidebarItems from './sidebarItem';
import { useCustomizerStore } from '~/stores/customizer';
import sidebarItems from './sidebarItem'; //sidebar static dari template
import Logo from '../logo/Logo.vue';
import { Icon } from '@iconify/vue';
import { useRoute } from 'vue-router';
// MiniSidebar Icons
import MiniSideIcons from './MinIconItems';
//import { useSidebarAccess } from '~/composables/useSidebarAccess'
import { mergeSidebar, mergeSidebarIcon } from '~/composables/sidebarMenu/mergeSidebar';
import sidebarRole from "@/stores/rolePages"; // data dummy
const route = useRoute();
@@ -36,7 +39,25 @@ const findTitleByPath = (items: any, path: any) => {
return title;
};
const foundId = findTitleByPath(sidebarItems, route.path);
const customizer = useCustomizerStore();
const sidebarMenu = shallowRef(sidebarItems);
//Merge Sidebar dari user api
//sidebarItems dari static sidebar
//sidebarRole dari role user api
const userRole = "admin";
const roleSidebarItems = sidebarRole.filter(item => item.role === userRole);
//filter menu untuk userRole admin
//mergeSidebar (menu awal, menu role user)
const mergedPages = mergeSidebar(sidebarMenu.value, roleSidebarItems[0].pages)
console.log('sidebarMenu', sidebarMenu.value);
console.log('roleSidebarItems', roleSidebarItems[0].pages);
console.log('mergedSidebarMenu', mergedPages);
sidebarMenu.value = mergedPages
const foundId = findTitleByPath(mergedPages, route.path);
const getCurrent = foundId ? foundId : 1;
const currentMenu = ref<any>(getCurrent);
function showData(data: any) {
@@ -44,10 +65,19 @@ function showData(data: any) {
//customizer.SET_MINI_SIDEBAR(!customizer.mini_sidebar)
}
// MiniSidebar Icons End
const customizer = useCustomizerStore();
const sidebarMenu = shallowRef(sidebarItems);
//Merge Icon dari sidebarMenu api
const sidebarMiniSideIcons = shallowRef(MiniSideIcons);
if (mergedPages !== null) {
sidebarMiniSideIcons.value = mergedPages
const mergedIcon = mergeSidebarIcon(MiniSideIcons, sidebarMiniSideIcons.value)
//console.log('sidebarIcon', MiniSideIcons);
console.log('mergedsidebarIcon', mergedIcon);
sidebarMiniSideIcons.value = mergedIcon
}
// MiniSidebar Icons End
</script>
<template>
@@ -74,7 +104,7 @@ const sidebarMenu = shallowRef(sidebarItems);
<div class="miniicons mt-lg-0 mt-4">
<!-- MiniSidebar Icons -->
<div class="d-flex flex-column gap-2">
<div class="miniicons-list px-4" v-for="menu in MiniSideIcons" :key="menu.icon">
<div class="miniicons-list px-4" v-for="menu in sidebarMiniSideIcons" :key="menu.icon">
<v-btn
rounded="md"
flat

View File

@@ -14,21 +14,28 @@ export interface menu {
disabled?: boolean;
type?: string;
subCaption?: string;
role?: string[];
permission?: string[];
}
const sidebarItem: menu[] = [
{
header: 'dashboards',
header: "dashboards",
id: 1,
children: [
children: [
{
title: 'Dashboard1',
icon: 'widget-add-line-duotone',
to: '/dashboards/dashboard1'
to: '/dashboards/dashboard1',
// role: ['admin'],
//permission: ['view', 'update', 'create', 'delete'],
},
{
title: 'Dashboard2',
icon: 'chart-line-duotone',
to: '/dashboards/dashboard2'
to: '/dashboards/dashboard2',
// role: ['admin','user'],
//permission: ['view', 'update', 'create', 'delete'],
},
{
title: 'Dashboard3',
@@ -42,11 +49,15 @@ const sidebarItem: menu[] = [
children: [
{
title: 'Homepage',
to: '/front-page/homepage'
to: '/front-page/homepage',
//role: ['admin','user'],
//permission: ['view', 'update', 'create', 'delete'],
},
{
title: 'About Us',
to: '/front-page/about-us'
to: '/front-page/about-us',
//role: ['admin','user'],
//permission: ['view', 'update', 'create', 'delete'],
},
{
title: 'Blog',

View File

@@ -0,0 +1,64 @@
import type { SidebarItem } from '~/types/menuAkses/sidebar'
import type { PageAccess } from '~/types/menuAkses/access'
interface SidebarItemWithPermissions extends SidebarItem {
permissions: string[];
}
export function mergeSidebar(menuItems: SidebarItem[], allowedPages: PageAccess[]): SidebarItem[] {
const result: SidebarItem[] = []
for (const item of menuItems) {
// Jika punya anak (children)
//console.log('item', item);
//console.log('allowedPages', allowedPages);
if (Array.isArray(item.children)) {
const filteredChildren = mergeSidebar(item.children, allowedPages)
//console.log('ada array item', item);
// console.log('filteredChildren', filteredChildren);
if (filteredChildren.length > 0) {
result.push({
...item,
children: filteredChildren
})
}
//console.log('result', result);
}
// Jika punya path, cocokkan dengan json dummy / backend
else if (item.to) {
const matchPage = allowedPages.find(page => page.title === item.title && page.path === item.to) //cocokkan title dan path link halaman
//console.log('match', matchPage,matchPage?.permissions);
if (matchPage && matchPage.permissions?.includes('view')) {
result.push({
...item,
permissions: matchPage?.permissions,
} as SidebarItemWithPermissions)
}
}
}
return result
}
export function mergeSidebarIcon(staticIcon: any[], IconAccess: any[]) {
const result : any[] = []
for (const item of staticIcon) {
const matchIcon = IconAccess.find(icon => icon.id === item.id)
//console.log('matchIcon', matchIcon);
if (matchIcon) {
result.push({
...item,
})
}
}
return result
}

View File

@@ -0,0 +1,47 @@
import { useHakAksesStore } from '@/stores/sidebarMenuAkses/useMenuAksesStore'
import { mergeSidebar, mergeSidebarIcon } from '~/composables/sidebarMenu/mergeSidebar';
import sidebarRole from "@/stores/rolePages"; // data dummy
import sidebarItems from '~/components/layout/full/vertical-sidebar/sidebarItem'; //sidebar static dari template
import MiniSideIcons from '~/components/layout/full/vertical-sidebar/MinIconItems';
export function useSidebarAccess(data: any) {
const sidebarMenu = shallowRef(sidebarItems);
const sidebarMiniSideIcons = shallowRef(MiniSideIcons);
const userRole = "admin";
const roleSidebarItems = sidebarRole.filter(item => item.role === userRole);
const mergedPages = mergeSidebar(sidebarMenu.value, roleSidebarItems[0].pages)
data = mergeSidebarIcon(sidebarMiniSideIcons.value, roleSidebarItems[0].pages)
console.log('mergedPages useSidebarAccess', mergedPages);
return data
}
// export function useAccess() {
// const store = useHakAksesStore()
// const getRole = () => store.role
// const getPages = () => store.pageAccess
// const getPermissionsByPath = (path: string): string[] => {
// const match = store.pageAccess.find(p => p.path === path)
// return match?.permissions || []
// }
// return {
// getRole,
// getPages,
// getPermissionsByPath
// }
// }

10
composables/useAccess.ts Normal file
View File

@@ -0,0 +1,10 @@
//import { sidebarMeta } from '@/components/layout/sidebar'
import { mergeSidebar } from '~/composables/sidebarMenu/mergeSidebar'
import { useSidebarAccess } from '~/composables/sidebarMenu/useSidebarAkses'
export const useAccess = () => {
const result = useSidebarAccess("tes")
return result
}

25
composables/useAuth123.ts Normal file
View File

@@ -0,0 +1,25 @@
// // composables/useAuth.ts
// import { useCookie } from '#app'
// export const useAuth = () => {
// const user = useState('auth_user', () => null)
// const token = useCookie('auth_token')
// const setUser = (data: any, tokenValue: string) => {
// user.value = data
// token.value = tokenValue
// }
// const logout = () => {
// user.value = null
// token.value = null
// }
// return {
// user,
// token,
// setUser,
// logout,
// isLoggedIn: computed(() => !!user.value)
// }
// }

View File

@@ -0,0 +1,34 @@
export default defineNuxtRouteMiddleware((to) => {
// const auth = useAuthStore()
// const requiredPermission = to.meta.permission
// console.log('requiredPermission', auth)
// const userRole = auth.user?.role
// if (!auth.user) {
// return navigateTo('/login')
// }
// if (requiredPermission && !auth.hasPermission(requiredPermission)) {
// return navigateTo('/unauthorized')
// }
// // Kalau kamu mau batasi halaman berdasar role:
// const allowedRoles = to.meta.roles
// if (allowedRoles && !allowedRoles.includes(userRole)) {
// return navigateTo('/unauthorized')
// }
const { status, data: session } = useAuth();
if (status.value === "unauthenticated") {
console.log("status:", status.value);
} else {
console.log("status:", status.value);
}
console.log('masuk middeleware auth-menu')
console.log('route:', to.fullPath)
return navigateTo('/sample-page-copy')
})

40
middleware/auth-menu.ts Normal file
View File

@@ -0,0 +1,40 @@
// middleware/auth.ts
import { useAccess } from '~/composables/useAccess'
export default defineNuxtRouteMiddleware((to) => {
const { getRole, getPermissionsByPath } = useAccess()
const role = useAccess()
// const perms = getPermissionsByPath(to.path)
// const meta = to.meta
// Jika belum login, redirect ke halaman login
const isLoggedIn = !!role // role
if (!isLoggedIn) {
console.log('[ NOT LOGGED IN]', { path: to.path })
//return navigateTo('/login') //
}
// // Validasi permission
// const rolePass = !meta.role || meta.role.includes(role)
// const permissionPass = !meta.permission || meta.permission.every(p => perms.includes(p))
// if (!rolePass || !permissionPass) {
// console.log('[ ACCESS DENIED]', {
// path: to.path,
// role,
// requiredRole: meta.role,
// permissions: perms,
// requiredPermission: meta.permission
// })
// return navigateTo('/403') // Halaman tidak punya akses
// }
return navigateTo('/sample-page-copy')
// Semua valid, lanjut ke halaman
// console.log('[ACCESS GRANTED]', {
// path: to.path,
// role,
// permissions
// })
})

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
import { ref } from "vue";
import UiParentCard from "@/components/shared/UiParentCard.vue";
</script>
<template>
<v-row>
<v-col cols="12" md="12">
<v-card elevation="10">
<v-card-item>
<h5 class="text-h5 mb-3">Sample Page Copy</h5>
<p class="text-body-1">This is a sample page</p>
</v-card-item>
</v-card>
</v-col>
</v-row>
</template>

View File

@@ -1,6 +1,20 @@
<script setup lang="ts">
import { ref } from "vue";
import UiParentCard from "@/components/shared/UiParentCard.vue";
definePageMeta({
middleware: ["auth-menu"],
//middleware : ['sidebase-auth']
});
const { status, data: session } = useAuth();
if (status.value === "unauthenticated") {
console.log("status", status.value);
} else {
console.log("status", status.value);
}
</script>
<template>
<v-row>

View File

@@ -1,22 +1,34 @@
<script setup lang="ts">
/*Call Components*/
import RevenueCard from '@/components/dashboard/RevenueCard.vue';
import NewCustomer from '@/components/dashboard/NewCustomer.vue';
import Totalincome from '@/components/dashboard/TotalIncome.vue';
import RevenueProduct from '@/components/dashboard/RevenueProducts.vue';
import DailyActivities from '@/components/dashboard/DailyActivities.vue';
import BlogCards from '@/components/dashboard/BlogCards.vue';
import RevenueCard from "@/components/dashboard/RevenueCard.vue";
import NewCustomer from "@/components/dashboard/NewCustomer.vue";
import Totalincome from "@/components/dashboard/TotalIncome.vue";
import RevenueProduct from "@/components/dashboard/RevenueProducts.vue";
import DailyActivities from "@/components/dashboard/DailyActivities.vue";
import BlogCards from "@/components/dashboard/BlogCards.vue";
definePageMeta({
middleware: ["auth-menu"],
//middleware : ['sidebase-auth']
});
// const { status } = useAuth();
// if (status.value === "unauthenticated") {
// console.log("status", status.value);
// } else {
// console.log("status", status.value);
// }
</script>
<template>
<v-row>
<v-col cols="12" lg="8"><RevenueCard /></v-col>
<v-col cols="12" lg="4"
><NewCustomer class="mb-6" />
<Totalincome />
</v-col>
<v-col cols="12" lg="8"><RevenueProduct/></v-col>
<v-col cols="12" lg="4"><DailyActivities/> </v-col>
<v-col cols="12"><BlogCards/></v-col>
</v-row>
<v-row>
<v-col cols="12" lg="8"><RevenueCard /></v-col>
<v-col cols="12" lg="4"
><NewCustomer class="mb-6" />
<Totalincome />
</v-col>
<v-col cols="12" lg="8"><RevenueProduct /></v-col>
<v-col cols="12" lg="4"><DailyActivities /> </v-col>
<v-col cols="12"><BlogCards /></v-col>
</v-row>
</template>

0
store/customizer.ts → stores/customizer.ts Executable file → Normal file
View File

112
stores/rolePages.json Normal file
View File

@@ -0,0 +1,112 @@
[
{
"id_user": 1,
"username": "Super Admin",
"email": "admin@company.com",
"password": "123456",
"full_name": "System Administrator",
"role": "admin",
"role_description": "Administrative access with most permissions",
"roleMenu": [
{
"header": "dashboards",
"id": "1",
"children": [
{
"title": "Dashboard1",
"link": "/dashboards/dashboard1",
"create": "true",
"update": "false",
"delete": "false",
"view": "false"
},
{
"title": "Dashboard2",
"link": "/dashboards/dashboard1",
"create": "true",
"update": "false",
"delete": "false",
"view": "false"
},
{
"title": "Front Pages",
"link": "/",
"children": [
{
"title": "Homepage",
"to": "/front-page/homepage",
"create": "true",
"update": "false",
"delete": "false",
"view": "false"
},
{
"title": "About Us",
"to": "/front-page/about-us",
"create": "true",
"update": "false",
"delete": "false",
"view": "false"
}
]
}
]
}
]
},
{
"id_user": 2,
"username": "user",
"email": "manager1@company.com",
"password": "234567",
"full_name": "John Manager",
"role": "user",
"role_description": "User level access for operational tasks",
"roleMenu": [
{
"header": "dashboards",
"id": "1",
"children": [
{
"title": "Dashboard1",
"link": "/dashboards/dashboard1",
"create": "false",
"update": "false",
"delete": "false",
"view": "false"
},
{
"title": "Dashboard2",
"link": "/dashboards/dashboard1",
"create": "false",
"update": "false",
"delete": "false",
"view": "false"
},
{
"title": "Front Pages",
"link": "/",
"children": [
{
"title": "Homepage",
"to": "/front-page/homepage",
"create": "false",
"update": "false",
"delete": "false",
"view": "false"
},
{
"title": "About Us",
"to": "/front-page/about-us",
"create": "false",
"update": "false",
"delete": "false",
"view": "false"
}
]
}
]
}
]
}
]

97
stores/rolePages.ts Normal file
View File

@@ -0,0 +1,97 @@
const rolePages = [
{
id_user: 1,
username: "Super Admin",
email: "admin@company.com",
password: "123456",
full_name: "System Administrator",
role: "admin",
role_description: "Administrative access with most permissions",
pages: [
{
title: "Dashboard1",
path: "/dashboards/dashboard1",
permissions: ["create", "view", "update", "delete"],
},
{
title: "Dashboard3",
path: "/dashboards/dashboard3",
permissions: ["create", "view", "update", "delete"],
},
{
title: "Homepage",
path: "/front-page/homepage",
permissions: ["create", "view", "update", "delete"],
},
{
title: "About Us",
path: "/front-page/about-us",
permissions: ["create", "view", "update", "delete"],
},
{
title: "Error",
path: "/auth/404",
permissions: ["view"],
},
{
title: "Side Login",
path: "/auth/login",
permissions: ["view"],
},
{
title: "Shop",
path: "/ecommerce/products",
permissions: ["view"],
},
{
title: "Detail",
path: "/ecommerce/product/detail/1",
permissions: ["view"],
},
{
title: "Autocomplete",
path: "/forms/form-elements/autocomplete",
permissions: ["view"],
},
{
title: "Combobox",
path: "/forms/form-elements/combobox",
permissions: ["view"],
},
],
},
{
id_user: 2,
username: "user",
email: "manager1@company.com",
password: "234567",
full_name: "John Manager",
role: "user",
role_description: "User level access for operational tasks",
pages: [
{
title: "Dashboard1",
path: "/dashboards/dashboard1",
permissions: ["view"],
},
{
title: "Dashboard2",
path: "/dashboards/dashboard1",
permissions: ["view"],
},
{
title: "Homepage",
path: "/front-page/homepage",
permissions: ["view"],
},
{
title: "About Us",
path: "/front-page/about-us",
permissions: ["view"],
},
],
},
];
export default rolePages;

View File

@@ -0,0 +1,28 @@
import { defineStore } from 'pinia'
import type { PageAccess } from '@/types/menuAkses/access'
export const useHakAksesStore = defineStore('hakAksesMenu', {
state: () => ({
role: '',
pageAccess: [] as PageAccess[], //Array halaman dengan permissions
mergedSidebar: []
}),
actions: {
setAccess(payload: { role: string; pages: PageAccess[] }) {
this.role = payload.role
this.pageAccess = payload.pages
},
resetAccess() {
this.role = ''
this.pageAccess = []
},
setMergedSidebar(data: any) {
this.mergedSidebar = data
}
}
})

10
types/menuAkses/access.ts Normal file
View File

@@ -0,0 +1,10 @@
export interface PageAccess {
path?: string
permissions?: string[]
title?: string;
}
export interface RoleAccess {
role?: string
pages?: PageAccess[]
}

View File

@@ -0,0 +1,40 @@
// types/sidebar.ts
export interface SidebarItem {
header?: string;
title?: string;
icon?: any;
id?: number;
to?: string;
chip?: string;
BgColor?: string;
chipBgColor?: string;
chipColor?: string;
chipVariant?: string;
chipIcon?: string;
children?: menu[];
disabled?: boolean;
type?: string;
subCaption?: string;
role?: string[];
permission?: string[];
}
export interface menu {
header?: string;
title?: string;
icon?: any;
id?: number;
to?: string;
chip?: string;
BgColor?: string;
chipBgColor?: string;
chipColor?: string;
chipVariant?: string;
chipIcon?: string;
children?: menu[];
disabled?: boolean;
type?: string;
subCaption?: string;
role?: string[];
permission?: string[];
}