Files
web-antrean/layouts/default.vue
T
2025-12-18 15:11:41 +07:00

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>