diff --git a/apps/admin-panel/src/layouts/components/VerticalNavMenu.vue b/apps/admin-panel/src/layouts/components/VerticalNavMenu.vue index 495ed27..1712b5d 100644 --- a/apps/admin-panel/src/layouts/components/VerticalNavMenu.vue +++ b/apps/admin-panel/src/layouts/components/VerticalNavMenu.vue @@ -24,25 +24,31 @@ @@ -73,6 +82,7 @@ import { computed, ref, watch } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { getMenu, type NavMenuNode } from '../navMenuItems'; import { useUsersStore } from '../../stores/users'; +import { resolveIcon } from '../../components/icons'; withDefaults(defineProps<{ title?: string; collapsed?: boolean }>(), { title: 'هنزا گلد', @@ -113,13 +123,45 @@ const visibleMenu = computed(() => const subKey = (node: NavMenuNode, i: number) => `sub-${node.name ?? ''}-${i}`; +/** + * Finds the sub-menu key(s) that must be open so the current route's menu + * item is visible. Previously openKeys only ever changed via user clicks + * (@update:openKeys), so a direct link or a page refresh landed with every + * submenu collapsed even when the active item was nested inside one. + */ +function findOpenKeysFor(path: string): string[] { + for (let i = 0; i < visibleMenu.value.length; i++) { + const node = visibleMenu.value[i]; + + if (node.header) { + const children = visibleChildren(node.items); + for (let j = 0; j < children.length; j++) { + const child = children[j]; + if (child.submenu && visibleChildren(child.submenu).some((leaf) => leaf.url === path)) { + return [subKey(child, j)]; + } + } + continue; + } + + if (node.submenu && visibleChildren(node.submenu).some((leaf) => leaf.url === path)) { + return [subKey(node, i)]; + } + } + return []; +} + const selectedKeys = ref([route.path]); -const openKeys = ref([]); +const openKeys = ref(findOpenKeysFor(route.path)); watch( () => route.path, (p) => { selectedKeys.value = [p]; + const needed = findOpenKeysFor(p); + for (const key of needed) { + if (!openKeys.value.includes(key)) openKeys.value = [...openKeys.value, key]; + } }, ); diff --git a/apps/admin-panel/src/layouts/main/Main.vue b/apps/admin-panel/src/layouts/main/Main.vue index 82c3815..5c26582 100644 --- a/apps/admin-panel/src/layouts/main/Main.vue +++ b/apps/admin-panel/src/layouts/main/Main.vue @@ -9,7 +9,7 @@ - +
@@ -40,6 +40,7 @@ import { computed, ref, watch } from 'vue'; import { useRoute } from 'vue-router'; import VerticalNavMenu from '../components/VerticalNavMenu.vue'; import TheNavbar from '../components/TheNavbar.vue'; +import { useLayoutStore } from '../../stores/layout'; interface Crumb { title: string; @@ -48,7 +49,10 @@ interface Crumb { } const route = useRoute(); -const collapsed = ref(false); +const layout = useLayoutStore(); +// Persisted via stores/layout.ts (localStorage) so the sidebar stays +// collapsed/expanded across reloads instead of resetting every time. +const collapsed = computed(() => layout.sidebarCollapsed); const pageTitle = computed(() => route.meta?.pageTitle as string | undefined); const breadcrumb = computed(() => route.meta?.breadcrumb as Crumb[] | undefined); diff --git a/apps/admin-panel/src/stores/layout.ts b/apps/admin-panel/src/stores/layout.ts index b58bb70..d34255c 100644 --- a/apps/admin-panel/src/stores/layout.ts +++ b/apps/admin-panel/src/stores/layout.ts @@ -5,6 +5,20 @@ import { defineStore } from 'pinia'; import themeConfig, { type ThemeMode } from '../themeConfig'; +const SIDEBAR_COLLAPSED_KEY = 'admin-panel:sidebarCollapsed'; + +/** Reads the persisted collapse state, falling back to themeConfig's default + * when localStorage is unavailable or has never been set. */ +function readPersistedCollapsed(): boolean { + if (typeof window === 'undefined') return themeConfig.sidebarCollapsed; + try { + const raw = window.localStorage.getItem(SIDEBAR_COLLAPSED_KEY); + return raw === null ? themeConfig.sidebarCollapsed : raw === '1'; + } catch { + return themeConfig.sidebarCollapsed; + } +} + export const useLayoutStore = defineStore('layout', { state: () => ({ windowWidth: typeof window !== 'undefined' ? window.innerWidth : 0, @@ -12,7 +26,7 @@ export const useLayoutStore = defineStore('layout', { theme: themeConfig.theme as ThemeMode, // legacy state.js: sidebar visibility for the vertical nav menu isSidebarActive: true, - sidebarCollapsed: themeConfig.sidebarCollapsed, + sidebarCollapsed: readPersistedCollapsed(), }), getters: { // legacy used breakpoints of 1200 (theme tour) and 992 (nav menu collapse) @@ -32,6 +46,13 @@ export const useLayoutStore = defineStore('layout', { }, setSidebarCollapsed(value: boolean) { this.sidebarCollapsed = value; + if (typeof window === 'undefined') return; + try { + window.localStorage.setItem(SIDEBAR_COLLAPSED_KEY, value ? '1' : '0'); + } catch { + // localStorage unavailable (private mode, disabled site data, ...) — + // collapse state just won't persist across reloads. + } }, }, });