Update dashboard navigation and dynamic loading

- Changed NavMenu to link to /dashboards instead of /dashboards/default
- Refactored Dashboard.razor to list dashboards from API
- Dashboard viewer/designer now loads by selected dashboard ID
- Mode toggle preserves selected dashboard and mode
- Added DashboardApiClient and DashboardInfoDto for API integration
- Registered DashboardApiClient for DI and HTTP client setup in Program.cs
This commit is contained in:
OlgunR
2026-02-03 17:23:04 +01:00
parent 32b6d30ba1
commit dc2cccac1f
10 changed files with 149 additions and 17 deletions

View File

@@ -22,7 +22,7 @@
</NavLink> </NavLink>
</div> </div>
<div class="nav-item px-3"> <div class="nav-item px-3">
<NavLink class="nav-link" href="dashboards/default"> <NavLink class="nav-link" href="dashboards">
<span class="oi oi-list-rich" aria-hidden="true"></span> Dashboards <span class="oi oi-list-rich" aria-hidden="true"></span> Dashboards
</NavLink> </NavLink>
</div> </div>

View File

@@ -0,0 +1,7 @@
namespace DbFirst.BlazorWasm.Models;
public class DashboardInfoDto
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
}

View File

@@ -2,6 +2,7 @@
@page "/dashboards/{DashboardId?}" @page "/dashboards/{DashboardId?}"
@inject Microsoft.Extensions.Configuration.IConfiguration Configuration @inject Microsoft.Extensions.Configuration.IConfiguration Configuration
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject DashboardApiClient DashboardApi
<style> <style>
.dashboard-shell { .dashboard-shell {
@@ -48,7 +49,17 @@
<div class="dashboard-shell"> <div class="dashboard-shell">
<aside class="dashboard-nav"> <aside class="dashboard-nav">
<div class="dashboard-nav-title">Dashboards</div> <div class="dashboard-nav-title">Dashboards</div>
<NavLink class="dashboard-nav-link" href="dashboards/default">Default Dashboard</NavLink> @if (dashboards.Count == 0)
{
<div class="px-3 py-2 text-muted">Keine Dashboards vorhanden.</div>
}
else
{
@foreach (var dashboard in dashboards)
{
<NavLink class="dashboard-nav-link" href="@($"dashboards/{dashboard.Id}")">@dashboard.Name</NavLink>
}
}
</aside> </aside>
<section class="dashboard-content"> <section class="dashboard-content">
<div class="mb-3"> <div class="mb-3">
@@ -56,7 +67,7 @@
@(IsDesigner ? "Zum Viewer wechseln" : "Zum Designer wechseln") @(IsDesigner ? "Zum Viewer wechseln" : "Zum Designer wechseln")
</DxButton> </DxButton>
</div> </div>
<DxDashboard @key="DashboardKey" Endpoint="@DashboardEndpoint" InitialDashboardId="DefaultDashboard" WorkingMode="@CurrentMode" style="width: 100%; height: 800px;"> <DxDashboard @key="DashboardKey" Endpoint="@DashboardEndpoint" InitialDashboardId="@SelectedDashboardId" WorkingMode="@CurrentMode" style="width: 100%; height: 800px;">
</DxDashboard> </DxDashboard>
</section> </section>
</div> </div>
@@ -65,24 +76,47 @@
[Parameter] public string? DashboardId { get; set; } [Parameter] public string? DashboardId { get; set; }
[SupplyParameterFromQuery] public string? Mode { get; set; } [SupplyParameterFromQuery] public string? Mode { get; set; }
private readonly List<DashboardInfoDto> dashboards = new();
private bool IsDesigner => !string.Equals(Mode, "viewer", StringComparison.OrdinalIgnoreCase); private bool IsDesigner => !string.Equals(Mode, "viewer", StringComparison.OrdinalIgnoreCase);
private WorkingMode CurrentMode => IsDesigner ? WorkingMode.Designer : WorkingMode.ViewerOnly; private WorkingMode CurrentMode => IsDesigner ? WorkingMode.Designer : WorkingMode.ViewerOnly;
private string DashboardKey => $"DefaultDashboard-{(IsDesigner ? "designer" : "viewer")}"; private string SelectedDashboardId { get; set; } = "";
private string DashboardKey => $"{SelectedDashboardId}-{(IsDesigner ? "designer" : "viewer")}";
private string DashboardEndpoint => $"{Configuration["ApiBaseUrl"]?.TrimEnd('/')}/api/dashboard"; private string DashboardEndpoint => $"{Configuration["ApiBaseUrl"]?.TrimEnd('/')}/api/dashboard";
protected override void OnParametersSet() protected override async Task OnParametersSetAsync()
{ {
if (!string.Equals(DashboardId, "default", StringComparison.OrdinalIgnoreCase)) if (dashboards.Count == 0)
{ {
Navigation.NavigateTo("dashboards/default", replace: true); dashboards.AddRange(await DashboardApi.GetAllAsync());
}
var requestedId = string.IsNullOrWhiteSpace(DashboardId) || string.Equals(DashboardId, "default", StringComparison.OrdinalIgnoreCase)
? null
: DashboardId;
var resolved = !string.IsNullOrWhiteSpace(requestedId)
? dashboards.FirstOrDefault(d => string.Equals(d.Id, requestedId, StringComparison.OrdinalIgnoreCase))
: dashboards.FirstOrDefault(d => string.Equals(d.Id, "DefaultDashboard", StringComparison.OrdinalIgnoreCase))
?? dashboards.FirstOrDefault();
if (resolved == null)
{
return;
}
SelectedDashboardId = resolved.Id;
if (!string.Equals(DashboardId, resolved.Id, StringComparison.OrdinalIgnoreCase))
{
Navigation.NavigateTo($"dashboards/{resolved.Id}?mode={(IsDesigner ? "designer" : "viewer")}", replace: true);
} }
} }
private void ToggleMode() private void ToggleMode()
{ {
var targetMode = IsDesigner ? "viewer" : "designer"; var targetMode = IsDesigner ? "viewer" : "designer";
Navigation.NavigateTo($"dashboards/default?mode={targetMode}", replace: true); Navigation.NavigateTo($"dashboards/{SelectedDashboardId}?mode={targetMode}", replace: true);
} }
} }

View File

@@ -17,5 +17,6 @@ builder.Services.AddDevExpressBlazor();
var apiBaseUrl = builder.Configuration["ApiBaseUrl"] ?? builder.HostEnvironment.BaseAddress; var apiBaseUrl = builder.Configuration["ApiBaseUrl"] ?? builder.HostEnvironment.BaseAddress;
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(apiBaseUrl) }); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(apiBaseUrl) });
builder.Services.AddScoped<CatalogApiClient>(); builder.Services.AddScoped<CatalogApiClient>();
builder.Services.AddScoped<DashboardApiClient>();
await builder.Build().RunAsync(); await builder.Build().RunAsync();

View File

@@ -0,0 +1,21 @@
using System.Net.Http.Json;
using DbFirst.BlazorWasm.Models;
namespace DbFirst.BlazorWasm.Services;
public class DashboardApiClient
{
private readonly HttpClient _httpClient;
private const string Endpoint = "api/dashboard/dashboards";
public DashboardApiClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<List<DashboardInfoDto>> GetAllAsync()
{
var result = await _httpClient.GetFromJsonAsync<List<DashboardInfoDto>>(Endpoint);
return result ?? new List<DashboardInfoDto>();
}
}

View File

@@ -32,7 +32,7 @@
</div> </div>
<div class="nav-item px-3"> <div class="nav-item px-3">
<NavLink class="nav-link" href="dashboards/default"> <NavLink class="nav-link" href="dashboards">
<span class="oi oi-list-rich" aria-hidden="true"></span> Dashboards <span class="oi oi-list-rich" aria-hidden="true"></span> Dashboards
</NavLink> </NavLink>
</div> </div>

View File

@@ -2,6 +2,7 @@
@page "/dashboards/{DashboardId?}" @page "/dashboards/{DashboardId?}"
@inject Microsoft.Extensions.Configuration.IConfiguration Configuration @inject Microsoft.Extensions.Configuration.IConfiguration Configuration
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject DashboardApiClient DashboardApi
<style> <style>
.dashboard-shell { .dashboard-shell {
@@ -48,7 +49,17 @@
<div class="dashboard-shell"> <div class="dashboard-shell">
<aside class="dashboard-nav"> <aside class="dashboard-nav">
<div class="dashboard-nav-title">Dashboards</div> <div class="dashboard-nav-title">Dashboards</div>
<NavLink class="dashboard-nav-link" href="dashboards/default">Default Dashboard</NavLink> @if (dashboards.Count == 0)
{
<div class="px-3 py-2 text-muted">Keine Dashboards vorhanden.</div>
}
else
{
@foreach (var dashboard in dashboards)
{
<NavLink class="dashboard-nav-link" href="@($"dashboards/{dashboard.Id}")">@dashboard.Name</NavLink>
}
}
</aside> </aside>
<section class="dashboard-content"> <section class="dashboard-content">
<div class="mb-3"> <div class="mb-3">
@@ -56,7 +67,7 @@
@(IsDesigner ? "Zum Viewer wechseln" : "Zum Designer wechseln") @(IsDesigner ? "Zum Viewer wechseln" : "Zum Designer wechseln")
</DxButton> </DxButton>
</div> </div>
<DxDashboard @key="DashboardKey" Endpoint="@DashboardEndpoint" InitialDashboardId="DefaultDashboard" WorkingMode="@CurrentMode" style="width: 100%; height: 800px;"> <DxDashboard @key="DashboardKey" Endpoint="@DashboardEndpoint" InitialDashboardId="@SelectedDashboardId" WorkingMode="@CurrentMode" style="width: 100%; height: 800px;">
</DxDashboard> </DxDashboard>
</section> </section>
</div> </div>
@@ -65,22 +76,47 @@
[Parameter] public string? DashboardId { get; set; } [Parameter] public string? DashboardId { get; set; }
[SupplyParameterFromQuery] public string? Mode { get; set; } [SupplyParameterFromQuery] public string? Mode { get; set; }
private readonly List<DashboardInfoDto> dashboards = new();
private bool IsDesigner => !string.Equals(Mode, "viewer", StringComparison.OrdinalIgnoreCase); private bool IsDesigner => !string.Equals(Mode, "viewer", StringComparison.OrdinalIgnoreCase);
private WorkingMode CurrentMode => IsDesigner ? WorkingMode.Designer : WorkingMode.ViewerOnly; private WorkingMode CurrentMode => IsDesigner ? WorkingMode.Designer : WorkingMode.ViewerOnly;
private string DashboardKey => $"DefaultDashboard-{(IsDesigner ? "designer" : "viewer")}"; private string SelectedDashboardId { get; set; } = "";
private string DashboardKey => $"{SelectedDashboardId}-{(IsDesigner ? "designer" : "viewer")}";
private string DashboardEndpoint => $"{Configuration["ApiBaseUrl"]?.TrimEnd('/')}/api/dashboard"; private string DashboardEndpoint => $"{Configuration["ApiBaseUrl"]?.TrimEnd('/')}/api/dashboard";
protected override void OnParametersSet() protected override async Task OnParametersSetAsync()
{ {
if (!string.Equals(DashboardId, "default", StringComparison.OrdinalIgnoreCase)) if (dashboards.Count == 0)
{ {
Navigation.NavigateTo("dashboards/default", replace: true); dashboards.AddRange(await DashboardApi.GetAllAsync());
}
var requestedId = string.IsNullOrWhiteSpace(DashboardId) || string.Equals(DashboardId, "default", StringComparison.OrdinalIgnoreCase)
? null
: DashboardId;
var resolved = !string.IsNullOrWhiteSpace(requestedId)
? dashboards.FirstOrDefault(d => string.Equals(d.Id, requestedId, StringComparison.OrdinalIgnoreCase))
: dashboards.FirstOrDefault(d => string.Equals(d.Id, "DefaultDashboard", StringComparison.OrdinalIgnoreCase))
?? dashboards.FirstOrDefault();
if (resolved == null)
{
return;
}
SelectedDashboardId = resolved.Id;
if (!string.Equals(DashboardId, resolved.Id, StringComparison.OrdinalIgnoreCase))
{
Navigation.NavigateTo($"dashboards/{resolved.Id}?mode={(IsDesigner ? "designer" : "viewer")}", replace: true);
} }
} }
private void ToggleMode() private void ToggleMode()
{ {
var targetMode = IsDesigner ? "viewer" : "designer"; var targetMode = IsDesigner ? "viewer" : "designer";
Navigation.NavigateTo($"dashboards/default?mode={targetMode}", replace: true); Navigation.NavigateTo($"dashboards/{SelectedDashboardId}?mode={targetMode}", replace: true);
} }
} }

View File

@@ -0,0 +1,7 @@
namespace DbFirst.BlazorWebApp.Models;
public class DashboardInfoDto
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
}

View File

@@ -17,10 +17,15 @@ if (!string.IsNullOrWhiteSpace(apiBaseUrl))
{ {
client.BaseAddress = new Uri(apiBaseUrl); client.BaseAddress = new Uri(apiBaseUrl);
}); });
builder.Services.AddHttpClient<DashboardApiClient>(client =>
{
client.BaseAddress = new Uri(apiBaseUrl);
});
} }
else else
{ {
builder.Services.AddHttpClient<CatalogApiClient>(); builder.Services.AddHttpClient<CatalogApiClient>();
builder.Services.AddHttpClient<DashboardApiClient>();
} }
var app = builder.Build(); var app = builder.Build();

View File

@@ -0,0 +1,21 @@
using System.Net.Http.Json;
using DbFirst.BlazorWebApp.Models;
namespace DbFirst.BlazorWebApp.Services;
public class DashboardApiClient
{
private readonly HttpClient _httpClient;
private const string Endpoint = "api/dashboard/dashboards";
public DashboardApiClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<List<DashboardInfoDto>> GetAllAsync()
{
var result = await _httpClient.GetFromJsonAsync<List<DashboardInfoDto>>(Endpoint);
return result ?? new List<DashboardInfoDto>();
}
}