Dasboard und Workspace-Anlage

This commit is contained in:
2026-05-29 12:35:16 +02:00
parent 2d05a18a0d
commit 818377c0a8
9 changed files with 421 additions and 25 deletions
+118 -25
View File
@@ -1,40 +1,133 @@
@page "/"
@using ZahlenAnalyse.Web.Models
@using ZahlenAnalyse.Web.Services
@using Microsoft.AspNetCore.Components.Authorization
@inject WorkspaceService DbService
@inject AuthenticationStateProvider AuthStateProvider
<MudText Typo="Typo.h3" Class="mb-4">Zahlen-Analyse</MudText>
<AuthorizeView>
<Authorized>
<MudText Typo="Typo.body1">Willkommen zurück, @context.User.Identity?.Name!</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary">Deine Pocket-ID (Sub): @_userId</MudText>
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-8">
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="mb-8">
<MudText Typo="Typo.h3">Meine Workspaces</MudText>
<MudButton Href="/logout" Variant="Variant.Filled" Color="Color.Error" Class="mt-2">
Abmelden
</MudButton>
</Authorized>
<NotAuthorized>
<MudText Typo="Typo.body1" Class="mb-2">Bitte melde dich an, um deine Workspaces zu verwalten.</MudText>
<MudButton Href="/login" Variant="Variant.Filled" Color="Color.Primary">
Mit Pocket-ID anmelden
</MudButton>
</NotAuthorized>
</AuthorizeView>
<AuthorizeView>
<Authorized>
<MudButton Href="/workspaces/create"
Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add">
Neuer Workspace
</MudButton>
</Authorized>
</AuthorizeView>
</MudStack>
<AuthorizeView>
<Authorized>
@if (_isLoading)
{
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
}
else if (!_workspaces.Any())
{
<MudPaper Class="pa-8 text-center" Elevation="1">
<MudIcon Icon="@Icons.Material.Filled.FolderOpen" Size="Size.Large" Color="Color.Default" Class="mb-4" />
<MudText Typo="Typo.h5" Class="mb-2">Noch keine Workspaces vorhanden</MudText>
<MudText Typo="Typo.body1" Color="Color.Secondary" Class="mb-6">
Erstelle deinen ersten Workspace (z.B. Urlaubsabrechnung), um mit der Datenanalyse zu beginnen.
</MudText>
<MudButton Href="/workspaces/create" Variant="Variant.Outlined" Color="Color.Primary">
Jetzt erstellen
</MudButton>
</MudPaper>
}
else
{
<MudGrid>
@foreach (var ws in _workspaces)
{
<MudItem xs="12" sm="6" md="4">
<MudCard Elevation="2" Class="h-100">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6">@ws.Name</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">
Erstellt am @ws.CreatedAt.ToLocalTime().ToString("dd.MM.yyyy")
</MudText>
</CardHeaderContent>
<CardHeaderActions>
<MudIconButton Icon="@Icons.Material.Filled.Settings" Color="Color.Default" />
</CardHeaderActions>
</MudCardHeader>
<MudCardContent>
<MudText Typo="Typo.body2">
@ws.Dimensions.Count Dimensionen konfiguriert
</MudText>
<MudStack Row="true" Spacing="1" Class="mt-2 flex-wrap">
@foreach (var dim in ws.Dimensions.Take(3))
{
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined">@dim.Name</MudChip>
}
@if (ws.Dimensions.Count > 3)
{
<MudChip T="string" Size="Size.Small" Variant="Variant.Text">+@(ws.Dimensions.Count - 3)</MudChip>
}
</MudStack>
</MudCardContent>
<MudCardActions>
<MudButton Variant="Variant.Text" Color="Color.Primary">
Daten erfassen
</MudButton>
<MudButton Variant="Variant.Text" Color="Color.Secondary">
Auswertung
</MudButton>
</MudCardActions>
</MudCard>
</MudItem>
}
</MudGrid>
}
</Authorized>
<NotAuthorized>
<MudPaper Class="pa-8 text-center" Elevation="1">
<MudText Typo="Typo.h5" Class="mb-4">Willkommen beim Zahlen-Analyse Tool</MudText>
<MudText Typo="Typo.body1" Class="mb-6">Bitte melde dich an, um deine Daten zu verwalten.</MudText>
<MudButton Href="/login" Variant="Variant.Filled" Color="Color.Primary">
Mit Pocket-ID anmelden
</MudButton>
</MudPaper>
</NotAuthorized>
</AuthorizeView>
</MudContainer>
@code {
private string _userId = string.Empty;
private List<Workspace> _workspaces = new();
private bool _isLoading = true;
protected override async Task OnInitializedAsync()
{
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity?.IsAuthenticated == true)
if (authState.User.Identity?.IsAuthenticated == true)
{
// Das ist der "sub"-Claim (Subject), den wir als OwnerId in RavenDB nutzen
_userId = user.FindFirst(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value
?? user.FindFirst("sub")?.Value
?? string.Empty;
await LoadWorkspaces();
}
else
{
_isLoading = false;
}
}
private async Task LoadWorkspaces()
{
_isLoading = true;
try
{
// Dank unseres Services reicht hier ein simpler Aufruf!
_workspaces = await DbService.GetWorkspacesForUserAsync();
}
finally
{
_isLoading = false;
}
}
}
+44
View File
@@ -0,0 +1,44 @@
@using ZahlenAnalyse.Web.Models
<div class="pl-6 mt-2 border-l-2" style="border-color: var(--mud-palette-primary);">
<MudStack Row="true" AlignItems="AlignItems.Center" Class="mb-2">
<MudTextField @bind-Value="Node.Name"
Placeholder="z.B. Italien oder Maut"
Variant="Variant.Outlined"
Margin="Margin.Dense" />
<MudTooltip Text="Unterkategorie hinzufügen">
<MudIconButton Icon="@Icons.Material.Filled.SubdirectoryArrowRight"
Size="Size.Small"
Color="Color.Info"
OnClick="AddChild" />
</MudTooltip>
<MudTooltip Text="Knoten löschen">
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Size="Size.Small"
Color="Color.Error"
OnClick="() => OnRemove.InvokeAsync(Node)" />
</MudTooltip>
</MudStack>
@foreach (var child in Node.Children.ToList())
{
<NodeEditor Node="child" OnRemove="RemoveChild" />
}
</div>
@code {
[Parameter] public DimensionNode Node { get; set; } = default!;
[Parameter] public EventCallback<DimensionNode> OnRemove { get; set; }
private void AddChild()
{
Node.Children.Add(new DimensionNode());
}
private void RemoveChild(DimensionNode child)
{
Node.Children.Remove(child);
}
}
+101
View File
@@ -0,0 +1,101 @@
@page "/workspaces/create"
@using ZahlenAnalyse.Web.Models
@using ZahlenAnalyse.Web.Services
@using Microsoft.AspNetCore.Components.Authorization
@inject WorkspaceService DbService
@inject AuthenticationStateProvider AuthStateProvider
@inject NavigationManager NavManager
@inject ISnackbar Snackbar
<MudContainer MaxWidth="MaxWidth.Medium" Class="mt-8 mb-8">
<MudText Typo="Typo.h4" Class="mb-6">Neuen Workspace erstellen</MudText>
<MudPaper Class="pa-6 mb-6" Elevation="1">
<MudTextField @bind-Value="_workspace.Name"
Label="Name des Workspaces (z.B. Urlaubsabrechnung)"
Variant="Variant.Outlined"
Required="true" />
</MudPaper>
<MudText Typo="Typo.h5" Class="mb-4">Analysedimensionen</MudText>
@foreach (var dim in _workspace.Dimensions.ToList())
{
<MudCard Class="mb-4" Elevation="1">
<MudCardContent>
<MudStack Row="true" AlignItems="AlignItems.Center" Class="mb-4">
<MudTextField @bind-Value="dim.Name"
Label="Name der Dimension (z.B. Ort oder Kostenart)"
Variant="Variant.Outlined" />
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Color="Color.Error"
OnClick="() => _workspace.Dimensions.Remove(dim)" />
</MudStack>
<MudText Typo="Typo.subtitle2" Class="mb-2">Hierarchie-Knoten:</MudText>
@foreach (var rootNode in dim.Nodes.ToList())
{
<NodeEditor Node="rootNode" OnRemove="(n) => dim.Nodes.Remove(n)" />
}
<MudButton Variant="Variant.Text"
StartIcon="@Icons.Material.Filled.Add"
Color="Color.Success"
OnClick="() => dim.Nodes.Add(new DimensionNode())"
Class="mt-2">
Haupt-Knoten hinzufügen
</MudButton>
</MudCardContent>
</MudCard>
}
<MudButton Variant="Variant.Outlined"
StartIcon="@Icons.Material.Filled.AddBox"
Color="Color.Info"
OnClick="() => _workspace.Dimensions.Add(new DimensionDefinition())"
Class="mb-8">
Neue Dimension hinzufügen
</MudButton>
<MudDivider Class="mb-4" />
<MudStack Row="true" Justify="Justify.FlexEnd">
<MudButton Variant="Variant.Text" Href="/">Abbrechen</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Save"
OnClick="SaveWorkspace">
Workspace speichern
</MudButton>
</MudStack>
</MudContainer>
@code {
private Workspace _workspace = new();
private async Task SaveWorkspace()
{
if (string.IsNullOrWhiteSpace(_workspace.Name))
{
Snackbar.Add("Bitte gib dem Workspace einen Namen.", Severity.Warning);
return;
}
try
{
// Wir übergeben nur noch das blanke Formular-Objekt.
// Der Service kümmert sich um den Auth-Rest!
await DbService.SaveWorkspaceAsync(_workspace);
Snackbar.Add($"Workspace '{_workspace.Name}' erfolgreich gespeichert!", Severity.Success);
NavManager.NavigateTo("/");
}
catch (Exception ex)
{
Snackbar.Add($"Fehler beim Speichern: {ex.Message}", Severity.Error);
}
}
}