252 lines
7.2 KiB
Vue
252 lines
7.2 KiB
Vue
<template>
|
|
<v-app id="inspire">
|
|
<SideBar
|
|
:items="filteredNavItems"
|
|
v-model:drawer="drawer"
|
|
:rail="rail"
|
|
@toggle-rail="rail = !rail"
|
|
/>
|
|
|
|
<v-main app>
|
|
<slot />
|
|
</v-main>
|
|
</v-app>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onMounted, ref } from "vue";
|
|
import { useLocalStorage } from "@vueuse/core";
|
|
import SideBar from "../components/layout/SideBar.vue";
|
|
// Ensure this path matches your store location
|
|
import { useNavItemsStore } from '~/stores/navItems1';
|
|
import { useAuth } from "~/composables/useAuth";
|
|
|
|
definePageMeta({
|
|
middleware: ['auth', 'permissions']
|
|
})
|
|
|
|
// State for controlling the sidebar
|
|
const drawer = ref(true);
|
|
const rail = ref(true);
|
|
|
|
const navItemsStore = useNavItemsStore();
|
|
const { user, checkAuth } = useAuth();
|
|
|
|
interface NavItem {
|
|
id: number;
|
|
name: string;
|
|
path: string;
|
|
icon: string;
|
|
children?: NavItem[];
|
|
}
|
|
|
|
interface BackendPermission {
|
|
id: number;
|
|
create: boolean;
|
|
read: boolean;
|
|
update: boolean;
|
|
disable: boolean;
|
|
delete: boolean;
|
|
active: boolean;
|
|
pagename: string;
|
|
pagesID: number;
|
|
level?: number;
|
|
sort?: number;
|
|
parent?: number;
|
|
}
|
|
|
|
interface PermissionResponse {
|
|
message?: string;
|
|
data?: BackendPermission[];
|
|
meta?: {
|
|
count: number;
|
|
total: number;
|
|
};
|
|
error?: string;
|
|
}
|
|
|
|
// Cache for API permissions
|
|
const apiPermissions = ref<BackendPermission[]>([]);
|
|
const currentUserRoles = ref<string[]>([]);
|
|
const currentUserGroups = ref<string[]>([]);
|
|
|
|
// Get current user data with roles and groups
|
|
const fetchCurrentUserData = async () => {
|
|
try {
|
|
const userData = await $fetch('/api/users/current');
|
|
currentUserRoles.value = [
|
|
...(userData.realmRoles || []),
|
|
...(userData.roles || []),
|
|
];
|
|
|
|
// Extract groups from paths (e.g., "/Instalasi STIM/Devops/Superadmin" -> "STIM")
|
|
const groups: string[] = [];
|
|
(userData.groups || []).forEach((g: string) => {
|
|
const parts = g.split('/').filter(Boolean);
|
|
if (parts.length > 1) {
|
|
groups.push(parts[1]); // Get second part as group name
|
|
} else if (parts.length === 1) {
|
|
groups.push(parts[0]);
|
|
}
|
|
});
|
|
currentUserGroups.value = groups;
|
|
|
|
return { roles: currentUserRoles.value, groups: currentUserGroups.value };
|
|
} catch (error) {
|
|
console.error('Error fetching current user data:', error);
|
|
return { roles: [], groups: [] };
|
|
}
|
|
};
|
|
|
|
// Fetch permissions from backend API (only for nav filtering, not for saving)
|
|
// Saving is now handled by permissions middleware
|
|
const fetchPermissionsFromAPI = async () => {
|
|
const { roles, groups } = await fetchCurrentUserData();
|
|
|
|
if (roles.length === 0 || groups.length === 0) {
|
|
console.warn('No roles or groups found for current user');
|
|
return;
|
|
}
|
|
|
|
// Use first role and first group (or combine as needed)
|
|
const primaryRole = roles[0] || '';
|
|
const primaryGroup = groups[0] || '';
|
|
|
|
if (!primaryRole || !primaryGroup) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await $fetch<PermissionResponse>('/api/permission', {
|
|
query: {
|
|
roles: primaryRole,
|
|
groups: primaryGroup,
|
|
},
|
|
});
|
|
|
|
if (response && response.data && Array.isArray(response.data)) {
|
|
apiPermissions.value = response.data;
|
|
// Note: Auto-save to allHakAksesData is now handled by permissions middleware
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching permissions from API:', error);
|
|
// Fallback to local storage if API fails
|
|
apiPermissions.value = [];
|
|
}
|
|
};
|
|
|
|
const filteredNavItems = computed(() => {
|
|
// If no API permissions, check local storage as fallback
|
|
if (apiPermissions.value.length === 0) {
|
|
const hakAksesData = useLocalStorage<any[]>('allHakAksesData', []);
|
|
const roleCandidates = [
|
|
...(user.value?.roles || []),
|
|
...(user.value?.realm_access?.roles || []),
|
|
].map((role) => role.toLowerCase());
|
|
|
|
const localPermission = hakAksesData.value.find((item) =>
|
|
roleCandidates.includes(item.role?.toLowerCase() || item.namaTipeUser?.toLowerCase())
|
|
);
|
|
|
|
if (localPermission) {
|
|
const permissionMap = new Map(
|
|
localPermission.hakAksesMenu.map((menu: any) => [menu.name.toLowerCase(), menu])
|
|
);
|
|
|
|
const applyFilter = (items: NavItem[]): NavItem[] => {
|
|
return items
|
|
.map((item) => {
|
|
const menuPerm = permissionMap.get(item.name.toLowerCase());
|
|
const filteredChildren = item.children ? applyFilter(item.children) : [];
|
|
const allowThis = menuPerm ? menuPerm.canAccess : false;
|
|
const hasChildren = filteredChildren.length > 0;
|
|
|
|
if (!allowThis && !hasChildren) return null;
|
|
|
|
return {
|
|
...item,
|
|
...(hasChildren ? { children: filteredChildren } : {}),
|
|
};
|
|
})
|
|
.filter(Boolean);
|
|
};
|
|
|
|
return applyFilter(navItemsStore.navItems);
|
|
}
|
|
|
|
// If no permissions found, show all items
|
|
return navItemsStore.navItems;
|
|
}
|
|
|
|
// Use API permissions to filter
|
|
const permissionMap = new Map(
|
|
apiPermissions.value.map((perm) => [perm.pagename.toLowerCase(), perm])
|
|
);
|
|
|
|
// Mapping untuk pagename dari API ke nama menu di sidebar
|
|
const pagenameToMenuMapping: Record<string, string[]> = {
|
|
'halaman utama': ['dashboard', 'halaman utama'],
|
|
'pengaturan': ['master data'],
|
|
'halaman': ['master data'],
|
|
'dashboard': ['dashboard'],
|
|
};
|
|
|
|
const applyFilter = (items: NavItem[]): NavItem[] => {
|
|
return items
|
|
.map((item) => {
|
|
// Try to match by pagename or menu name
|
|
let perm = permissionMap.get(item.name.toLowerCase());
|
|
|
|
// If no direct match, try fuzzy matching
|
|
if (!perm) {
|
|
perm = Array.from(permissionMap.values()).find(p => {
|
|
const pagenameLower = p.pagename?.toLowerCase() || '';
|
|
const menuNameLower = item.name.toLowerCase();
|
|
|
|
// Direct match
|
|
if (pagenameLower === menuNameLower) return true;
|
|
|
|
// Contains match
|
|
if (pagenameLower.includes(menuNameLower) || menuNameLower.includes(pagenameLower)) {
|
|
return true;
|
|
}
|
|
|
|
// Check mapping
|
|
const mappedMenus = pagenameToMenuMapping[pagenameLower];
|
|
if (mappedMenus && mappedMenus.some(m => m === menuNameLower)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
|
|
const filteredChildren = item.children ? applyFilter(item.children) : [];
|
|
const allowThis = perm ? (perm.active || perm.read) : false;
|
|
const hasChildren = filteredChildren.length > 0;
|
|
|
|
// If permission allows and has children, show item with filtered children
|
|
// If permission allows but no children, show item
|
|
// If no permission but has allowed children, show item with children
|
|
if (!allowThis && !hasChildren) return null;
|
|
|
|
return {
|
|
...item,
|
|
...(hasChildren ? { children: filteredChildren } : {}),
|
|
};
|
|
})
|
|
.filter(Boolean);
|
|
};
|
|
|
|
return applyFilter(navItemsStore.navItems);
|
|
});
|
|
|
|
onMounted(async () => {
|
|
await checkAuth();
|
|
await fetchPermissionsFromAPI();
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* Global styles for layout */
|
|
</style> |