@using Microsoft.AspNetCore.Components @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Routing @using TaskTrackingSystem.Shared.Models.Menu @using Microsoft.JSInterop @implements IDisposable @inject TaskTrackingSystem.WebApp.Localization.UiLanguageService Language @inject AuthenticationStateProvider AuthStateProvider @inject MenuAuthorizationService MenuAuthorization @inject NavigationManager Navigation @inject IJSRuntime JS @code { private List menus = new(); private string currentPath = string.Empty; private HashSet expandedMenuGroups = new(); protected override async Task OnInitializedAsync() { Language.Changed += OnLanguageChanged; currentPath = Navigation.ToBaseRelativePath(Navigation.Uri).Trim('/'); Navigation.LocationChanged += OnLocationChanged; try { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var user = authState.User; if (user.Identity?.IsAuthenticated == true) { var rawMenus = await MenuAuthorization.GetUserMenusAsync(user); menus = ProcessMenus(rawMenus); BuildUrlHierarchy(menus); foreach (var menu in menus) { if (menu.SubMenus != null && menu.SubMenus.Count > 0 && IsMenuExpanded(menu.MenuCode, menu.SubMenus)) { expandedMenuGroups.Add(menu.MenuCode); } } } } catch (Exception ex) { Console.WriteLine($"Error loading dynamic menus: {ex.Message}"); } } private void OnLocationChanged(object? sender, LocationChangedEventArgs e) { currentPath = Navigation.ToBaseRelativePath(e.Location).Trim('/'); RefreshExpandedMenuGroups(); _ = InvokeAsync(StateHasChanged); } private void OnLanguageChanged() { _ = InvokeAsync(StateHasChanged); } private List ProcessMenus(List rawMenus) { return rawMenus .Where(menu => !string.Equals(menu.MenuCode, "ROLE_LAYOUTS", StringComparison.OrdinalIgnoreCase) && !string.Equals(menu.MenuCode, "DASHBOARD_WIDGETS", StringComparison.OrdinalIgnoreCase)) .OrderBy(menu => string.Equals(menu.MenuCode, "DASHBOARD", StringComparison.OrdinalIgnoreCase) ? 0 : 1) .ThenBy(menu => menu.OrderNo) .ThenBy(menu => AppLocalization.Menu(menu.MenuCode, menu.MenuName)) .ToList(); } private async Task RefreshMenusAsync() { try { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var user = authState.User; if (user.Identity?.IsAuthenticated == true) { var freshMenus = await MenuAuthorization.GetUserMenusAsync(user); var processedMenus = ProcessMenus(freshMenus); // Only update and re-render if the menu list actually changed. if (processedMenus.Count != menus.Count || processedMenus.Select(m => m.MenuCode).SequenceEqual(menus.Select(m => m.MenuCode)) == false) { menus = processedMenus; BuildUrlHierarchy(menus); expandedMenuGroups.Clear(); foreach (var menu in menus) { if (menu.SubMenus != null && menu.SubMenus.Count > 0 && IsMenuExpanded(menu.MenuCode, menu.SubMenus)) { expandedMenuGroups.Add(menu.MenuCode); } } } else { // Access items are the same - just refresh expanded state for the new route. foreach (var menu in menus) { if (menu.SubMenus != null && menu.SubMenus.Count > 0 && IsMenuExpanded(menu.MenuCode, menu.SubMenus)) { expandedMenuGroups.Add(menu.MenuCode); } } } } } catch (Exception ex) { Console.WriteLine($"Error refreshing menus on navigation: {ex.Message}"); } StateHasChanged(); } private void RefreshExpandedMenuGroups() { foreach (var menu in menus) { if (menu.SubMenus != null && menu.SubMenus.Count > 0 && IsMenuExpanded(menu.MenuCode, menu.SubMenus)) { expandedMenuGroups.Add(menu.MenuCode); } } } private void ToggleMenuGroup(string menuCode) { if (!expandedMenuGroups.Remove(menuCode)) { expandedMenuGroups.Add(menuCode); } } private bool IsMenuGroupExpanded(string menuCode) => expandedMenuGroups.Contains(menuCode); private bool IsMenuExpanded(string menuCode, IEnumerable subMenus) { return subMenus.Any(s => !string.IsNullOrWhiteSpace(s.MenuUrl) && currentPath.StartsWith(s.MenuUrl.Trim('/'), StringComparison.OrdinalIgnoreCase)); } private static NavLinkMatch GetNavLinkMatch(string? menuUrl) { return string.IsNullOrWhiteSpace(menuUrl) || menuUrl.Trim('/') is "dashboard" or "" ? NavLinkMatch.All : NavLinkMatch.Prefix; } private static void BuildUrlHierarchy(List menuList) { var toRemove = new List(); foreach (var parent in menuList) { if (string.IsNullOrWhiteSpace(parent.MenuUrl)) { continue; } if (parent.SubMenus != null && parent.SubMenus.Count > 0) { continue; } var parentPath = parent.MenuUrl.Trim('/'); if (string.IsNullOrEmpty(parentPath)) { continue; } var children = menuList .Where(m => m != parent && !string.IsNullOrWhiteSpace(m.MenuUrl) && m.MenuUrl.Trim('/').StartsWith(parentPath + "/", StringComparison.OrdinalIgnoreCase)) .OrderBy(m => m.OrderNo) .ToList(); if (children.Count > 0) { parent.SubMenus = children; toRemove.AddRange(children); } } foreach (var item in toRemove) { menuList.Remove(item); } } private static RenderFragment RenderIcon(string? icon) => builder => { builder.OpenElement(0, "i"); builder.AddAttribute(1, "data-lucide", string.IsNullOrWhiteSpace(icon) ? "circle" : icon); builder.AddAttribute(2, "class", "h-4 w-4 mr-3 text-slate-400 shrink-0"); builder.CloseElement(); }; private static RenderFragment RenderSmallIcon(string? icon) => builder => { builder.OpenElement(0, "i"); builder.AddAttribute(1, "data-lucide", string.IsNullOrWhiteSpace(icon) ? "circle" : icon); builder.AddAttribute(2, "class", "h-3.5 w-3.5 mr-3 text-slate-400 shrink-0"); builder.CloseElement(); }; protected override async Task OnAfterRenderAsync(bool firstRender) { try { await JS.InvokeVoidAsync("initIcons"); } catch { } } public void Dispose() { Navigation.LocationChanged -= OnLocationChanged; Language.Changed -= OnLanguageChanged; } }