Compare commits
18 Commits
feat/timer
...
3a1cb68cf0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a1cb68cf0 | ||
|
|
c93518202b | ||
|
|
6792b426ff | ||
|
|
6ac48a472d | ||
|
|
96dfd5b3c6 | ||
|
|
810771f385 | ||
|
|
a0e0d7ed03 | ||
|
|
39cb63a78d | ||
|
|
7d08923444 | ||
|
|
11374347d3 | ||
|
|
7552b34ced | ||
|
|
13d134df0e | ||
|
|
285f029311 | ||
|
|
cdf225bad1 | ||
|
|
59e051f162 | ||
|
|
1352dda20e | ||
|
|
33b5b03bf4 | ||
|
|
6b986db62e |
@@ -5,10 +5,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
@DxResourceManager.RegisterTheme(Themes.Fluent.Clone(properties =>
|
@DxResourceManager.RegisterTheme(Themes.Fluent)
|
||||||
{
|
|
||||||
properties.ApplyToPageElements = true;
|
|
||||||
}))
|
|
||||||
@DxResourceManager.RegisterScripts()
|
@DxResourceManager.RegisterScripts()
|
||||||
|
|
||||||
<link href="_content/DevExpress.Blazor.Dashboard/ace.css" rel="stylesheet" />
|
<link href="_content/DevExpress.Blazor.Dashboard/ace.css" rel="stylesheet" />
|
||||||
@@ -21,6 +18,7 @@
|
|||||||
<link href="_content/DevExpress.Blazor.Dashboard/dx-dashboard.light.min.css" rel="stylesheet" />
|
<link href="_content/DevExpress.Blazor.Dashboard/dx-dashboard.light.min.css" rel="stylesheet" />
|
||||||
|
|
||||||
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
|
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" />
|
||||||
|
|
||||||
<link rel="stylesheet" href="app.css" />
|
<link rel="stylesheet" href="app.css" />
|
||||||
<link rel="stylesheet" href="DbFirst.BlazorWebApp.styles.css" />
|
<link rel="stylesheet" href="DbFirst.BlazorWebApp.styles.css" />
|
||||||
|
|||||||
55
DbFirst.BlazorWebApp/Components/BandEditor.razor
Normal file
55
DbFirst.BlazorWebApp/Components/BandEditor.razor
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<div class="band-editor">
|
||||||
|
<button class="band-editor-toggle" @onclick="() => IsExpanded = !IsExpanded">
|
||||||
|
<span class="band-editor-toggle-icon @(IsExpanded ? "expanded" : "")">►</span>
|
||||||
|
<span>Layout</span>
|
||||||
|
</button>
|
||||||
|
@if (IsExpanded)
|
||||||
|
{
|
||||||
|
<div class="band-editor-body">
|
||||||
|
<div class="band-controls">
|
||||||
|
<DxButton Text="Band hinzufügen" Click="OnAddBand" />
|
||||||
|
<DxButton Text="Layout speichern" Click="OnSaveLayout" Enabled="@CanSave" />
|
||||||
|
<DxButton Text="Layout zurücksetzen" Click="OnResetLayout" />
|
||||||
|
</div>
|
||||||
|
@foreach (var band in Bands)
|
||||||
|
{
|
||||||
|
<div class="band-row">
|
||||||
|
<DxTextBox Text="@band.Caption" TextChanged="@(value => OnBandCaptionChanged.InvokeAsync((band, value)))" />
|
||||||
|
<DxButton Text="Entfernen" Click="@(() => OnRemoveBand.InvokeAsync(band))" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<DxFormLayout CssClass="band-columns" ColCount="2">
|
||||||
|
@foreach (var column in Columns)
|
||||||
|
{
|
||||||
|
<DxFormLayoutItem Caption="@column.Caption">
|
||||||
|
<DxComboBox Data="@BandOptions"
|
||||||
|
TData="BandOption"
|
||||||
|
TValue="string"
|
||||||
|
TextFieldName="Caption"
|
||||||
|
ValueFieldName="Id"
|
||||||
|
Value="@GetColumnBand(column.FieldName)"
|
||||||
|
ValueChanged="@(value => OnColumnBandChanged.InvokeAsync((column.FieldName, value)))"
|
||||||
|
Width="100%" />
|
||||||
|
</DxFormLayoutItem>
|
||||||
|
}
|
||||||
|
</DxFormLayout>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private bool IsExpanded { get; set; }
|
||||||
|
|
||||||
|
[Parameter, EditorRequired] public List<BandDefinition> Bands { get; set; } = new();
|
||||||
|
[Parameter, EditorRequired] public List<BandOption> BandOptions { get; set; } = new();
|
||||||
|
[Parameter, EditorRequired] public List<ColumnDefinition> Columns { get; set; } = new();
|
||||||
|
[Parameter, EditorRequired] public Func<string, string> GetColumnBand { get; set; } = _ => string.Empty;
|
||||||
|
[Parameter, EditorRequired] public bool CanSave { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public EventCallback OnAddBand { get; set; }
|
||||||
|
[Parameter] public EventCallback OnSaveLayout { get; set; }
|
||||||
|
[Parameter] public EventCallback OnResetLayout { get; set; }
|
||||||
|
[Parameter] public EventCallback<BandDefinition> OnRemoveBand { get; set; }
|
||||||
|
[Parameter] public EventCallback<(BandDefinition Band, string Value)> OnBandCaptionChanged { get; set; }
|
||||||
|
[Parameter] public EventCallback<(string FieldName, string? BandId)> OnColumnBandChanged { get; set; }
|
||||||
|
}
|
||||||
219
DbFirst.BlazorWebApp/Components/BandGridBase.cs
Normal file
219
DbFirst.BlazorWebApp/Components/BandGridBase.cs
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
using DbFirst.BlazorWebApp.Models.Grid;
|
||||||
|
using DbFirst.BlazorWebApp.Services;
|
||||||
|
using DevExpress.Blazor;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
|
|
||||||
|
namespace DbFirst.BlazorWebApp.Components;
|
||||||
|
|
||||||
|
public abstract class BandGridBase<TItem> : ComponentBase
|
||||||
|
{
|
||||||
|
[Inject] protected BandLayoutService BandLayoutService { get; set; } = default!;
|
||||||
|
|
||||||
|
// --- Abstract: jedes Grid definiert diese selbst ---
|
||||||
|
protected abstract string LayoutKey { get; }
|
||||||
|
protected abstract List<ColumnDefinition> ColumnDefinitions { get; }
|
||||||
|
|
||||||
|
// --- Band-Layout Felder ---
|
||||||
|
protected BandLayout bandLayout = new();
|
||||||
|
protected Dictionary<string, string> columnBandAssignments = new();
|
||||||
|
protected List<BandOption> bandOptions = new();
|
||||||
|
protected Dictionary<string, ColumnDefinition> columnLookup = new();
|
||||||
|
protected string? layoutUser;
|
||||||
|
protected bool gridLayoutApplied;
|
||||||
|
protected IGrid? gridRef;
|
||||||
|
|
||||||
|
// --- SizeMode ---
|
||||||
|
protected SizeMode _sizeMode = SizeMode.Medium;
|
||||||
|
protected static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();
|
||||||
|
|
||||||
|
private const string LayoutType = "GRID_BANDS";
|
||||||
|
|
||||||
|
// --- Lifecycle ---
|
||||||
|
protected async Task InitializeBandLayoutAsync()
|
||||||
|
{
|
||||||
|
columnLookup = ColumnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||||
|
layoutUser = await BandLayoutService.EnsureLayoutUserAsync();
|
||||||
|
bandLayout = await BandLayoutService.LoadBandLayoutAsync(LayoutType, LayoutKey, layoutUser, columnLookup);
|
||||||
|
columnBandAssignments = BandLayoutService.BuildAssignmentsFromLayout(bandLayout);
|
||||||
|
ApplyColumnLayoutFromStorage();
|
||||||
|
_sizeMode = bandLayout.SizeMode;
|
||||||
|
UpdateBandOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task ApplyGridLayoutAfterRenderAsync()
|
||||||
|
{
|
||||||
|
if (!gridLayoutApplied && gridRef != null && bandLayout.GridLayout != null)
|
||||||
|
{
|
||||||
|
gridRef.LoadLayout(bandLayout.GridLayout);
|
||||||
|
gridLayoutApplied = true;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Layout speichern / zurücksetzen ---
|
||||||
|
protected async Task SaveLayoutAsync()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(layoutUser)) return;
|
||||||
|
CaptureColumnLayoutFromGrid();
|
||||||
|
await BandLayoutService.SaveBandLayoutAsync(LayoutType, LayoutKey, layoutUser, bandLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task ResetLayoutAsync()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(layoutUser)) return;
|
||||||
|
await BandLayoutService.ResetBandLayoutAsync(LayoutType, LayoutKey, layoutUser);
|
||||||
|
bandLayout = new BandLayout();
|
||||||
|
columnBandAssignments.Clear();
|
||||||
|
UpdateBandOptions();
|
||||||
|
foreach (var column in ColumnDefinitions)
|
||||||
|
column.Width = null;
|
||||||
|
columnLookup = ColumnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||||
|
_sizeMode = SizeMode.Medium;
|
||||||
|
gridRef?.LoadLayout(new GridPersistentLayout());
|
||||||
|
gridLayoutApplied = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CaptureColumnLayoutFromGrid()
|
||||||
|
{
|
||||||
|
if (gridRef == null) return;
|
||||||
|
var layout = gridRef.SaveLayout();
|
||||||
|
bandLayout.GridLayout = layout;
|
||||||
|
bandLayout.SizeMode = _sizeMode;
|
||||||
|
var orderedColumns = layout.Columns
|
||||||
|
.Where(c => !string.IsNullOrWhiteSpace(c.FieldName))
|
||||||
|
.OrderBy(c => c.VisibleIndex)
|
||||||
|
.ToList();
|
||||||
|
bandLayout.ColumnOrder = orderedColumns.Select(c => c.FieldName).ToList();
|
||||||
|
bandLayout.ColumnWidths = orderedColumns
|
||||||
|
.Where(c => !string.IsNullOrWhiteSpace(c.Width))
|
||||||
|
.ToDictionary(c => c.FieldName, c => c.Width, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyColumnLayoutFromStorage()
|
||||||
|
{
|
||||||
|
foreach (var column in ColumnDefinitions)
|
||||||
|
{
|
||||||
|
if (bandLayout.ColumnWidths.TryGetValue(column.FieldName, out var width) && !string.IsNullOrWhiteSpace(width))
|
||||||
|
column.Width = width;
|
||||||
|
}
|
||||||
|
columnLookup = ColumnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Band-Methoden ---
|
||||||
|
protected bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
|
||||||
|
|
||||||
|
protected virtual bool ShowCommandColumn => true;
|
||||||
|
|
||||||
|
protected void AddBand()
|
||||||
|
{
|
||||||
|
bandLayout.Bands.Add(new BandDefinition { Id = Guid.NewGuid().ToString("N"), Caption = "Band" });
|
||||||
|
UpdateBandOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RemoveBand(BandDefinition band)
|
||||||
|
{
|
||||||
|
bandLayout.Bands.Remove(band);
|
||||||
|
foreach (var key in columnBandAssignments.Where(p => p.Value == band.Id).Select(p => p.Key).ToList())
|
||||||
|
columnBandAssignments.Remove(key);
|
||||||
|
UpdateBandOptions();
|
||||||
|
SyncBandsFromAssignments();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UpdateBandCaption(BandDefinition band, string value)
|
||||||
|
{
|
||||||
|
band.Caption = value;
|
||||||
|
UpdateBandOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UpdateColumnBand(string fieldName, string? bandId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(bandId))
|
||||||
|
columnBandAssignments.Remove(fieldName);
|
||||||
|
else
|
||||||
|
columnBandAssignments[fieldName] = bandId;
|
||||||
|
SyncBandsFromAssignments();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected string GetColumnBand(string fieldName)
|
||||||
|
=> columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
|
||||||
|
|
||||||
|
protected void SyncBandsFromAssignments()
|
||||||
|
{
|
||||||
|
foreach (var band in bandLayout.Bands)
|
||||||
|
{
|
||||||
|
band.Columns = ColumnDefinitions
|
||||||
|
.Where(c => columnBandAssignments.TryGetValue(c.FieldName, out var id) && id == band.Id)
|
||||||
|
.Select(c => c.FieldName)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UpdateBandOptions()
|
||||||
|
{
|
||||||
|
bandOptions = new List<BandOption> { new() { Id = string.Empty, Caption = "Ohne Band" } };
|
||||||
|
bandOptions.AddRange(bandLayout.Bands.Select(b => new BandOption { Id = b.Id, Caption = b.Caption }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- SizeMode ---
|
||||||
|
protected string FormatSizeText(SizeMode size) => size switch
|
||||||
|
{
|
||||||
|
SizeMode.Small => "Klein",
|
||||||
|
SizeMode.Medium => "Mittel",
|
||||||
|
SizeMode.Large => "Groß",
|
||||||
|
_ => size.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
protected void OnSizeChange(DropDownButtonItemClickEventArgs args)
|
||||||
|
{
|
||||||
|
_sizeMode = Enum.Parse<SizeMode>(args.ItemInfo.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- RenderColumns / BuildDataColumn ---
|
||||||
|
protected RenderFragment RenderColumns() => builder =>
|
||||||
|
{
|
||||||
|
var seq = 0;
|
||||||
|
if (ShowCommandColumn)
|
||||||
|
{
|
||||||
|
builder.OpenComponent<DxGridCommandColumn>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Width", "120px");
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
var grouped = bandLayout.Bands.SelectMany(b => b.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var column in ColumnDefinitions.Where(c => !grouped.Contains(c.FieldName)))
|
||||||
|
BuildDataColumn(builder, ref seq, column);
|
||||||
|
|
||||||
|
foreach (var band in bandLayout.Bands)
|
||||||
|
{
|
||||||
|
if (band.Columns.Count == 0) continue;
|
||||||
|
builder.OpenComponent<DxGridBandColumn>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Caption", band.Caption);
|
||||||
|
builder.AddAttribute(seq++, "Columns", (RenderFragment)(bandBuilder =>
|
||||||
|
{
|
||||||
|
var bandSeq = 0;
|
||||||
|
foreach (var columnName in band.Columns)
|
||||||
|
{
|
||||||
|
if (columnLookup.TryGetValue(columnName, out var column))
|
||||||
|
BuildDataColumn(bandBuilder, ref bandSeq, column);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected void BuildDataColumn(RenderTreeBuilder builder, ref int seq, ColumnDefinition column)
|
||||||
|
{
|
||||||
|
builder.OpenComponent<DxGridDataColumn>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "FieldName", column.FieldName);
|
||||||
|
builder.AddAttribute(seq++, "Caption", column.Caption);
|
||||||
|
if (!string.IsNullOrWhiteSpace(column.Width))
|
||||||
|
builder.AddAttribute(seq++, "Width", column.Width);
|
||||||
|
if (!string.IsNullOrWhiteSpace(column.DisplayFormat))
|
||||||
|
builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat);
|
||||||
|
if (column.ReadOnly)
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
@inherits BandGridBase<CatalogReadDto>
|
||||||
@inject CatalogApiClient Api
|
@inject CatalogApiClient Api
|
||||||
@inject BandLayoutService BandLayoutService
|
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
||||||
{
|
{
|
||||||
@@ -24,47 +24,21 @@ else if (items.Count == 0)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="band-editor">
|
<BandEditor Bands="@bandLayout.Bands"
|
||||||
<button class="band-editor-toggle" @onclick="() => bandEditorExpanded = !bandEditorExpanded">
|
BandOptions="@bandOptions"
|
||||||
<span class="band-editor-toggle-icon @(bandEditorExpanded ? "expanded" : "")">►</span>
|
Columns="@ColumnDefinitions"
|
||||||
<span>Layout</span>
|
GetColumnBand="GetColumnBand"
|
||||||
</button>
|
CanSave="@CanSaveBandLayout"
|
||||||
@if (bandEditorExpanded)
|
OnAddBand="AddBand"
|
||||||
{
|
OnSaveLayout="SaveLayoutWithFeedbackAsync"
|
||||||
<div class="band-editor-body">
|
OnResetLayout="ResetLayoutWithFeedbackAsync"
|
||||||
<div class="band-controls">
|
OnRemoveBand="RemoveBand"
|
||||||
<DxButton Text="Band hinzufügen" Click="AddBand" />
|
OnBandCaptionChanged="@(args => UpdateBandCaption(args.Band, args.Value))"
|
||||||
<DxButton Text="Layout speichern" Click="SaveLayoutAsync" Enabled="@CanSaveBandLayout" />
|
OnColumnBandChanged="@(args => UpdateColumnBand(args.FieldName, args.BandId))" />
|
||||||
<DxButton Text="Layout zurücksetzen" Click="ResetLayoutAsync" />
|
|
||||||
</div>
|
|
||||||
@foreach (var band in bandLayout.Bands)
|
|
||||||
{
|
|
||||||
<div class="band-row">
|
|
||||||
<DxTextBox Text="@band.Caption" TextChanged="@(value => UpdateBandCaption(band, value))" />
|
|
||||||
<DxButton Text="Entfernen" Click="@(() => RemoveBand(band))" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<DxFormLayout CssClass="band-columns" ColCount="2">
|
|
||||||
@foreach (var column in columnDefinitions)
|
|
||||||
{
|
|
||||||
<DxFormLayoutItem Caption="@column.Caption">
|
|
||||||
<DxComboBox Data="@bandOptions"
|
|
||||||
TData="BandOption"
|
|
||||||
TValue="string"
|
|
||||||
TextFieldName="Caption"
|
|
||||||
ValueFieldName="Id"
|
|
||||||
Value="@GetColumnBand(column.FieldName)"
|
|
||||||
ValueChanged="@(value => UpdateColumnBand(column.FieldName, value))"
|
|
||||||
Width="100%" />
|
|
||||||
</DxFormLayoutItem>
|
|
||||||
}
|
|
||||||
</DxFormLayout>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid-section">
|
<div class="grid-section">
|
||||||
<DxGrid Data="@items"
|
<DxGrid Data="@items"
|
||||||
|
ColumnChooserButtonDisplayMode="GridColumnChooserButtonDisplayMode.Always"
|
||||||
TItem="CatalogReadDto"
|
TItem="CatalogReadDto"
|
||||||
KeyFieldName="@nameof(CatalogReadDto.Guid)"
|
KeyFieldName="@nameof(CatalogReadDto.Guid)"
|
||||||
SizeMode="@_sizeMode"
|
SizeMode="@_sizeMode"
|
||||||
@@ -85,9 +59,42 @@ else
|
|||||||
DataItemDeleting="OnDataItemDeleting"
|
DataItemDeleting="OnDataItemDeleting"
|
||||||
FocusedRowEnabled="true"
|
FocusedRowEnabled="true"
|
||||||
@bind-FocusedRowKey="focusedRowKey"
|
@bind-FocusedRowKey="focusedRowKey"
|
||||||
|
RowClick="@(args => _focusedVisibleIndex = args.VisibleIndex)"
|
||||||
@ref="gridRef">
|
@ref="gridRef">
|
||||||
<ToolbarTemplate>
|
<ToolbarTemplate>
|
||||||
<DxToolbar>
|
<DxToolbar>
|
||||||
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Left">
|
||||||
|
<Template Context="_">
|
||||||
|
<DxButton IconCssClass="bi bi-plus-lg"
|
||||||
|
RenderStyle="ButtonRenderStyle.Secondary"
|
||||||
|
RenderStyleMode="ButtonRenderStyleMode.Text"
|
||||||
|
Click="@(() => gridRef!.StartEditNewRowAsync())" />
|
||||||
|
</Template>
|
||||||
|
</DxToolbarItem>
|
||||||
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Left">
|
||||||
|
<Template Context="_">
|
||||||
|
<DxButton IconCssClass="bi bi-pencil"
|
||||||
|
RenderStyle="ButtonRenderStyle.Secondary"
|
||||||
|
RenderStyleMode="ButtonRenderStyleMode.Text"
|
||||||
|
Click="EditFocusedRow" />
|
||||||
|
</Template>
|
||||||
|
</DxToolbarItem>
|
||||||
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Left">
|
||||||
|
<Template Context="_">
|
||||||
|
<DxButton IconCssClass="bi bi-trash"
|
||||||
|
RenderStyle="ButtonRenderStyle.Secondary"
|
||||||
|
RenderStyleMode="ButtonRenderStyleMode.Text"
|
||||||
|
Click="DeleteFocusedRow" />
|
||||||
|
</Template>
|
||||||
|
</DxToolbarItem>
|
||||||
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Right">
|
||||||
|
<Template Context="_">
|
||||||
|
<DxButton Text="Spalten"
|
||||||
|
RenderStyle="ButtonRenderStyle.Secondary"
|
||||||
|
RenderStyleMode="ButtonRenderStyleMode.Text"
|
||||||
|
Click="@(() => gridRef!.ShowColumnChooser())" />
|
||||||
|
</Template>
|
||||||
|
</DxToolbarItem>
|
||||||
<DxToolbarItem Alignment="ToolbarItemAlignment.Right">
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Right">
|
||||||
<Template Context="_">
|
<Template Context="_">
|
||||||
<DxDropDownButton Text="@FormatSizeText(_sizeMode)"
|
<DxDropDownButton Text="@FormatSizeText(_sizeMode)"
|
||||||
@@ -148,29 +155,22 @@ else
|
|||||||
private string? infoMessage;
|
private string? infoMessage;
|
||||||
private EditContext? editContext;
|
private EditContext? editContext;
|
||||||
private ValidationMessageStore? validationMessageStore;
|
private ValidationMessageStore? validationMessageStore;
|
||||||
private IGrid? gridRef;
|
|
||||||
private int? focusedRowKey;
|
private int? focusedRowKey;
|
||||||
private string popupHeaderText = "Edit";
|
private string popupHeaderText = "Edit";
|
||||||
private const string LayoutType = "GRID_BANDS";
|
|
||||||
private const string LayoutKey = "CatalogsGrid";
|
|
||||||
private string? layoutUser;
|
|
||||||
private BandLayout bandLayout = new();
|
|
||||||
private Dictionary<string, string> columnBandAssignments = new();
|
|
||||||
private List<BandOption> bandOptions = new();
|
|
||||||
private Dictionary<string, ColumnDefinition> columnLookup = new();
|
|
||||||
private bool gridLayoutApplied;
|
|
||||||
private bool bandEditorExpanded;
|
|
||||||
|
|
||||||
private List<ColumnDefinition> columnDefinitions = new()
|
protected override string LayoutKey => "CatalogsGrid";
|
||||||
{
|
protected override bool ShowCommandColumn => false;
|
||||||
new() { FieldName = nameof(CatalogReadDto.Guid), Caption = "Id", Width = "140px", FilterType = ColumnFilterType.Text },
|
|
||||||
new() { FieldName = nameof(CatalogReadDto.CatTitle), Caption = "Titel", FilterType = ColumnFilterType.Text },
|
protected override List<ColumnDefinition> ColumnDefinitions { get; } = new()
|
||||||
new() { FieldName = nameof(CatalogReadDto.CatString), Caption = "String", FilterType = ColumnFilterType.Text },
|
{
|
||||||
new() { FieldName = nameof(CatalogReadDto.AddedWho), Caption = "Angelegt von", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
new() { FieldName = nameof(CatalogReadDto.Guid), Caption = "Id", Width = "140px", FilterType = ColumnFilterType.Text },
|
||||||
new() { FieldName = nameof(CatalogReadDto.AddedWhen), Caption = "Angelegt am", ReadOnly = true, FilterType = ColumnFilterType.Date },
|
new() { FieldName = nameof(CatalogReadDto.CatTitle), Caption = "Titel", FilterType = ColumnFilterType.Text },
|
||||||
new() { FieldName = nameof(CatalogReadDto.ChangedWho), Caption = "Geändert von", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
new() { FieldName = nameof(CatalogReadDto.CatString), Caption = "String", FilterType = ColumnFilterType.Text },
|
||||||
new() { FieldName = nameof(CatalogReadDto.ChangedWhen), Caption = "Geändert am", ReadOnly = true, FilterType = ColumnFilterType.Date }
|
new() { FieldName = nameof(CatalogReadDto.AddedWho), Caption = "Angelegt von", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
||||||
};
|
new() { FieldName = nameof(CatalogReadDto.AddedWhen), Caption = "Angelegt am", ReadOnly = true, FilterType = ColumnFilterType.Date },
|
||||||
|
new() { FieldName = nameof(CatalogReadDto.ChangedWho), Caption = "Geändert von", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
||||||
|
new() { FieldName = nameof(CatalogReadDto.ChangedWhen),Caption = "Geändert am", ReadOnly = true, FilterType = ColumnFilterType.Date }
|
||||||
|
};
|
||||||
|
|
||||||
private readonly List<ProcedureOption> procedureOptions = new()
|
private readonly List<ProcedureOption> procedureOptions = new()
|
||||||
{
|
{
|
||||||
@@ -178,44 +178,15 @@ else
|
|||||||
new() { Value = 1, Text = "PRTBMY_CATALOG_SAVE" }
|
new() { Value = 1, Text = "PRTBMY_CATALOG_SAVE" }
|
||||||
};
|
};
|
||||||
|
|
||||||
private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
|
|
||||||
|
|
||||||
private SizeMode _sizeMode = SizeMode.Medium;
|
|
||||||
private static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();
|
|
||||||
|
|
||||||
private string FormatSizeText(SizeMode size) => size switch
|
|
||||||
{
|
|
||||||
SizeMode.Small => "Klein",
|
|
||||||
SizeMode.Medium => "Mittel",
|
|
||||||
SizeMode.Large => "Groß",
|
|
||||||
_ => size.ToString()
|
|
||||||
};
|
|
||||||
|
|
||||||
private void OnSizeChange(DropDownButtonItemClickEventArgs args)
|
|
||||||
{
|
|
||||||
_sizeMode = Enum.Parse<SizeMode>(args.ItemInfo.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
await InitializeBandLayoutAsync();
|
||||||
layoutUser = await BandLayoutService.EnsureLayoutUserAsync();
|
|
||||||
bandLayout = await BandLayoutService.LoadBandLayoutAsync(LayoutType, LayoutKey, layoutUser, columnLookup);
|
|
||||||
columnBandAssignments = BandLayoutService.BuildAssignmentsFromLayout(bandLayout);
|
|
||||||
ApplyColumnLayoutFromStorage();
|
|
||||||
_sizeMode = bandLayout.SizeMode;
|
|
||||||
UpdateBandOptions();
|
|
||||||
await LoadCatalogs();
|
await LoadCatalogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (!gridLayoutApplied && gridRef != null && bandLayout.GridLayout != null)
|
await ApplyGridLayoutAfterRenderAsync();
|
||||||
{
|
|
||||||
gridRef.LoadLayout(bandLayout.GridLayout);
|
|
||||||
gridLayoutApplied = true;
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadCatalogs()
|
private async Task LoadCatalogs()
|
||||||
@@ -238,174 +209,6 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveLayoutAsync()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
CaptureColumnLayoutFromGrid();
|
|
||||||
await BandLayoutService.SaveBandLayoutAsync(LayoutType, LayoutKey, layoutUser, bandLayout);
|
|
||||||
infoMessage = "Layout gespeichert.";
|
|
||||||
errorMessage = null;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
errorMessage = $"Layout konnte nicht gespeichert werden: {ex.Message}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ResetLayoutAsync()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
|
||||||
return;
|
|
||||||
|
|
||||||
await BandLayoutService.ResetBandLayoutAsync(LayoutType, LayoutKey, layoutUser);
|
|
||||||
|
|
||||||
bandLayout = new BandLayout();
|
|
||||||
columnBandAssignments.Clear();
|
|
||||||
UpdateBandOptions();
|
|
||||||
|
|
||||||
foreach (var column in columnDefinitions)
|
|
||||||
column.Width = null;
|
|
||||||
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
_sizeMode = SizeMode.Medium;
|
|
||||||
|
|
||||||
if (gridRef != null)
|
|
||||||
gridRef.LoadLayout(new GridPersistentLayout());
|
|
||||||
gridLayoutApplied = false;
|
|
||||||
|
|
||||||
infoMessage = "Layout zurückgesetzt.";
|
|
||||||
errorMessage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CaptureColumnLayoutFromGrid()
|
|
||||||
{
|
|
||||||
if (gridRef == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var layout = gridRef.SaveLayout();
|
|
||||||
bandLayout.GridLayout = layout;
|
|
||||||
bandLayout.SizeMode = _sizeMode;
|
|
||||||
|
|
||||||
var orderedColumns = layout.Columns
|
|
||||||
.Where(c => !string.IsNullOrWhiteSpace(c.FieldName))
|
|
||||||
.OrderBy(c => c.VisibleIndex)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
bandLayout.ColumnOrder = orderedColumns.Select(c => c.FieldName).ToList();
|
|
||||||
bandLayout.ColumnWidths = orderedColumns
|
|
||||||
.Where(c => !string.IsNullOrWhiteSpace(c.Width))
|
|
||||||
.ToDictionary(c => c.FieldName, c => c.Width, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyColumnLayoutFromStorage()
|
|
||||||
{
|
|
||||||
foreach (var column in columnDefinitions)
|
|
||||||
{
|
|
||||||
if (bandLayout.ColumnWidths.TryGetValue(column.FieldName, out var width) && !string.IsNullOrWhiteSpace(width))
|
|
||||||
column.Width = width;
|
|
||||||
}
|
|
||||||
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddBand()
|
|
||||||
{
|
|
||||||
bandLayout.Bands.Add(new BandDefinition { Id = Guid.NewGuid().ToString("N"), Caption = "Band" });
|
|
||||||
UpdateBandOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveBand(BandDefinition band)
|
|
||||||
{
|
|
||||||
bandLayout.Bands.Remove(band);
|
|
||||||
foreach (var key in columnBandAssignments.Where(p => p.Value == band.Id).Select(p => p.Key).ToList())
|
|
||||||
columnBandAssignments.Remove(key);
|
|
||||||
UpdateBandOptions();
|
|
||||||
SyncBandsFromAssignments();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateBandCaption(BandDefinition band, string value)
|
|
||||||
{
|
|
||||||
band.Caption = value;
|
|
||||||
UpdateBandOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateColumnBand(string fieldName, string? bandId)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(bandId))
|
|
||||||
columnBandAssignments.Remove(fieldName);
|
|
||||||
else
|
|
||||||
columnBandAssignments[fieldName] = bandId;
|
|
||||||
SyncBandsFromAssignments();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetColumnBand(string fieldName)
|
|
||||||
=> columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
|
|
||||||
|
|
||||||
private void SyncBandsFromAssignments()
|
|
||||||
{
|
|
||||||
foreach (var band in bandLayout.Bands)
|
|
||||||
{
|
|
||||||
band.Columns = columnDefinitions
|
|
||||||
.Where(c => columnBandAssignments.TryGetValue(c.FieldName, out var id) && id == band.Id)
|
|
||||||
.Select(c => c.FieldName)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateBandOptions()
|
|
||||||
{
|
|
||||||
bandOptions = new List<BandOption> { new() { Id = string.Empty, Caption = "Ohne Band" } };
|
|
||||||
bandOptions.AddRange(bandLayout.Bands.Select(b => new BandOption { Id = b.Id, Caption = b.Caption }));
|
|
||||||
}
|
|
||||||
|
|
||||||
private RenderFragment RenderColumns() => builder =>
|
|
||||||
{
|
|
||||||
var seq = 0;
|
|
||||||
builder.OpenComponent<DxGridCommandColumn>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Width", "120px");
|
|
||||||
builder.CloseComponent();
|
|
||||||
|
|
||||||
var grouped = bandLayout.Bands.SelectMany(b => b.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
||||||
foreach (var column in columnDefinitions.Where(c => !grouped.Contains(c.FieldName)))
|
|
||||||
BuildDataColumn(builder, ref seq, column);
|
|
||||||
|
|
||||||
foreach (var band in bandLayout.Bands)
|
|
||||||
{
|
|
||||||
if (band.Columns.Count == 0) continue;
|
|
||||||
|
|
||||||
builder.OpenComponent<DxGridBandColumn>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Caption", band.Caption);
|
|
||||||
builder.AddAttribute(seq++, "Columns", (RenderFragment)(bandBuilder =>
|
|
||||||
{
|
|
||||||
var bandSeq = 0;
|
|
||||||
foreach (var columnName in band.Columns)
|
|
||||||
{
|
|
||||||
if (columnLookup.TryGetValue(columnName, out var column))
|
|
||||||
BuildDataColumn(bandBuilder, ref bandSeq, column);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private void BuildDataColumn(RenderTreeBuilder builder, ref int seq, ColumnDefinition column)
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxGridDataColumn>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "FieldName", column.FieldName);
|
|
||||||
builder.AddAttribute(seq++, "Caption", column.Caption);
|
|
||||||
if (!string.IsNullOrWhiteSpace(column.Width))
|
|
||||||
builder.AddAttribute(seq++, "Width", column.Width);
|
|
||||||
if (!string.IsNullOrWhiteSpace(column.DisplayFormat))
|
|
||||||
builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat);
|
|
||||||
if (column.ReadOnly)
|
|
||||||
builder.AddAttribute(seq++, "ReadOnly", true);
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetEditContext(EditContext context)
|
private void SetEditContext(EditContext context)
|
||||||
{
|
{
|
||||||
if (editContext == context) return;
|
if (editContext == context) return;
|
||||||
@@ -575,4 +378,36 @@ else
|
|||||||
public int Value { get; set; }
|
public int Value { get; set; }
|
||||||
public string Text { get; set; } = string.Empty;
|
public string Text { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int _focusedVisibleIndex;
|
||||||
|
|
||||||
|
private async Task EditFocusedRow()
|
||||||
|
=> await gridRef!.StartEditRowAsync(_focusedVisibleIndex);
|
||||||
|
|
||||||
|
private Task DeleteFocusedRow()
|
||||||
|
{
|
||||||
|
gridRef!.ShowRowDeleteConfirmation(_focusedVisibleIndex);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveLayoutWithFeedbackAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SaveLayoutAsync();
|
||||||
|
infoMessage = "Layout gespeichert.";
|
||||||
|
errorMessage = null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
errorMessage = $"Layout konnte nicht gespeichert werden: {ex.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ResetLayoutWithFeedbackAsync()
|
||||||
|
{
|
||||||
|
await ResetLayoutAsync();
|
||||||
|
infoMessage = "Layout zurückgesetzt.";
|
||||||
|
errorMessage = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
@inherits BandGridBase<MassDataReadDto>
|
||||||
@inject MassDataApiClient Api
|
@inject MassDataApiClient Api
|
||||||
@inject BandLayoutService BandLayoutService
|
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
||||||
{
|
{
|
||||||
@@ -25,44 +25,17 @@ else if (items.Count == 0)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
<div class="band-editor">
|
<BandEditor Bands="@bandLayout.Bands"
|
||||||
<button class="band-editor-toggle" @onclick="() => bandEditorExpanded = !bandEditorExpanded">
|
BandOptions="@bandOptions"
|
||||||
<span class="band-editor-toggle-icon @(bandEditorExpanded ? "expanded" : "")">►</span>
|
Columns="@ColumnDefinitions"
|
||||||
<span>Layout</span>
|
GetColumnBand="GetColumnBand"
|
||||||
</button>
|
CanSave="@CanSaveBandLayout"
|
||||||
@if (bandEditorExpanded)
|
OnAddBand="AddBand"
|
||||||
{
|
OnSaveLayout="SaveLayoutWithFeedbackAsync"
|
||||||
<div class="band-editor-body">
|
OnResetLayout="ResetLayoutWithFeedbackAsync"
|
||||||
<div class="band-controls">
|
OnRemoveBand="RemoveBand"
|
||||||
<DxButton Text="Band hinzufügen" Click="AddBand" />
|
OnBandCaptionChanged="@(args => UpdateBandCaption(args.Band, args.Value))"
|
||||||
<DxButton Text="Layout speichern" Click="SaveLayoutAsync" Enabled="@CanSaveBandLayout" />
|
OnColumnBandChanged="@(args => UpdateColumnBand(args.FieldName, args.BandId))" />
|
||||||
<DxButton Text="Layout zurücksetzen" Click="ResetLayoutAsync" />
|
|
||||||
</div>
|
|
||||||
@foreach (var band in bandLayout.Bands)
|
|
||||||
{
|
|
||||||
<div class="band-row">
|
|
||||||
<DxTextBox Text="@band.Caption" TextChanged="@(value => UpdateBandCaption(band, value))" />
|
|
||||||
<DxButton Text="Entfernen" Click="@(() => RemoveBand(band))" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<DxFormLayout CssClass="band-columns" ColCount="2">
|
|
||||||
@foreach (var column in columnDefinitions)
|
|
||||||
{
|
|
||||||
<DxFormLayoutItem Caption="@column.Caption">
|
|
||||||
<DxComboBox Data="@bandOptions"
|
|
||||||
TData="BandOption"
|
|
||||||
TValue="string"
|
|
||||||
TextFieldName="Caption"
|
|
||||||
ValueFieldName="Id"
|
|
||||||
Value="@GetColumnBand(column.FieldName)"
|
|
||||||
ValueChanged="@(value => UpdateColumnBand(column.FieldName, value))"
|
|
||||||
Width="100%" />
|
|
||||||
</DxFormLayoutItem>
|
|
||||||
}
|
|
||||||
</DxFormLayout>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3 page-size-selector">
|
<div class="mb-3 page-size-selector">
|
||||||
<span class="page-size-label">Datensätze je Seite:</span>
|
<span class="page-size-label">Datensätze je Seite:</span>
|
||||||
@@ -78,6 +51,7 @@ else
|
|||||||
|
|
||||||
<div class="grid-section">
|
<div class="grid-section">
|
||||||
<DxGrid Data="@items"
|
<DxGrid Data="@items"
|
||||||
|
ColumnChooserButtonDisplayMode="GridColumnChooserButtonDisplayMode.Always"
|
||||||
TItem="MassDataReadDto"
|
TItem="MassDataReadDto"
|
||||||
KeyFieldName="@nameof(MassDataReadDto.Id)"
|
KeyFieldName="@nameof(MassDataReadDto.Id)"
|
||||||
SizeMode="@_sizeMode"
|
SizeMode="@_sizeMode"
|
||||||
@@ -98,9 +72,42 @@ else
|
|||||||
DataItemDeleting="OnDataItemDeleting"
|
DataItemDeleting="OnDataItemDeleting"
|
||||||
FocusedRowEnabled="true"
|
FocusedRowEnabled="true"
|
||||||
@bind-FocusedRowKey="focusedRowKey"
|
@bind-FocusedRowKey="focusedRowKey"
|
||||||
|
RowClick="@(args => _focusedVisibleIndex = args.VisibleIndex)"
|
||||||
@ref="gridRef">
|
@ref="gridRef">
|
||||||
<ToolbarTemplate>
|
<ToolbarTemplate>
|
||||||
<DxToolbar>
|
<DxToolbar>
|
||||||
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Left">
|
||||||
|
<Template Context="_">
|
||||||
|
<DxButton IconCssClass="bi bi-plus-lg"
|
||||||
|
RenderStyle="ButtonRenderStyle.Secondary"
|
||||||
|
RenderStyleMode="ButtonRenderStyleMode.Text"
|
||||||
|
Click="@(() => gridRef!.StartEditNewRowAsync())" />
|
||||||
|
</Template>
|
||||||
|
</DxToolbarItem>
|
||||||
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Left">
|
||||||
|
<Template Context="_">
|
||||||
|
<DxButton IconCssClass="bi bi-pencil"
|
||||||
|
RenderStyle="ButtonRenderStyle.Secondary"
|
||||||
|
RenderStyleMode="ButtonRenderStyleMode.Text"
|
||||||
|
Click="EditFocusedRow" />
|
||||||
|
</Template>
|
||||||
|
</DxToolbarItem>
|
||||||
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Left">
|
||||||
|
<Template Context="_">
|
||||||
|
<DxButton IconCssClass="bi bi-trash"
|
||||||
|
RenderStyle="ButtonRenderStyle.Secondary"
|
||||||
|
RenderStyleMode="ButtonRenderStyleMode.Text"
|
||||||
|
Click="DeleteFocusedRow" />
|
||||||
|
</Template>
|
||||||
|
</DxToolbarItem>
|
||||||
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Right">
|
||||||
|
<Template Context="_">
|
||||||
|
<DxButton Text="Spalten"
|
||||||
|
RenderStyle="ButtonRenderStyle.Secondary"
|
||||||
|
RenderStyleMode="ButtonRenderStyleMode.Text"
|
||||||
|
Click="@(() => gridRef!.ShowColumnChooser())" />
|
||||||
|
</Template>
|
||||||
|
</DxToolbarItem>
|
||||||
<DxToolbarItem Alignment="ToolbarItemAlignment.Right">
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Right">
|
||||||
<Template Context="_">
|
<Template Context="_">
|
||||||
<DxDropDownButton Text="@FormatSizeText(_sizeMode)"
|
<DxDropDownButton Text="@FormatSizeText(_sizeMode)"
|
||||||
@@ -178,28 +185,21 @@ else
|
|||||||
private string popupHeaderText = "Edit";
|
private string popupHeaderText = "Edit";
|
||||||
private EditContext? editContext;
|
private EditContext? editContext;
|
||||||
private ValidationMessageStore? validationMessageStore;
|
private ValidationMessageStore? validationMessageStore;
|
||||||
private IGrid? gridRef;
|
|
||||||
private int? focusedRowKey;
|
private int? focusedRowKey;
|
||||||
private const string LayoutType = "GRID_BANDS";
|
|
||||||
private const string LayoutKey = "MassDataGrid";
|
|
||||||
private string? layoutUser;
|
|
||||||
private BandLayout bandLayout = new();
|
|
||||||
private Dictionary<string, string> columnBandAssignments = new();
|
|
||||||
private List<BandOption> bandOptions = new();
|
|
||||||
private Dictionary<string, ColumnDefinition> columnLookup = new();
|
|
||||||
private bool gridLayoutApplied;
|
|
||||||
private bool bandEditorExpanded;
|
|
||||||
|
|
||||||
private List<ColumnDefinition> columnDefinitions = new()
|
protected override string LayoutKey => "MassDataGrid";
|
||||||
{
|
protected override bool ShowCommandColumn => false;
|
||||||
new() { FieldName = nameof(MassDataReadDto.Id), Caption = "Id", Width = "90px", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
|
||||||
new() { FieldName = nameof(MassDataReadDto.CustomerName), Caption = "CustomerName", FilterType = ColumnFilterType.Text },
|
protected override List<ColumnDefinition> ColumnDefinitions { get; } = new()
|
||||||
new() { FieldName = nameof(MassDataReadDto.Amount), Caption = "Amount", DisplayFormat = "c2", FilterType = ColumnFilterType.Text },
|
{
|
||||||
new() { FieldName = nameof(MassDataReadDto.Category), Caption = "Category", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
new() { FieldName = nameof(MassDataReadDto.Id), Caption = "Id", Width = "90px", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
||||||
new() { FieldName = nameof(MassDataReadDto.StatusFlag), Caption = "Status", ReadOnly = true, FilterType = ColumnFilterType.Bool },
|
new() { FieldName = nameof(MassDataReadDto.CustomerName), Caption = "CustomerName", FilterType = ColumnFilterType.Text },
|
||||||
new() { FieldName = nameof(MassDataReadDto.AddedWhen), Caption = "Added", ReadOnly = true, FilterType = ColumnFilterType.Date },
|
new() { FieldName = nameof(MassDataReadDto.Amount), Caption = "Amount", DisplayFormat = "c2", FilterType = ColumnFilterType.Text },
|
||||||
new() { FieldName = nameof(MassDataReadDto.ChangedWhen), Caption = "Changed", ReadOnly = true, FilterType = ColumnFilterType.Date }
|
new() { FieldName = nameof(MassDataReadDto.Category), Caption = "Category", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
||||||
};
|
new() { FieldName = nameof(MassDataReadDto.StatusFlag), Caption = "Status", ReadOnly = true, FilterType = ColumnFilterType.Bool },
|
||||||
|
new() { FieldName = nameof(MassDataReadDto.AddedWhen), Caption = "Added", ReadOnly = true, FilterType = ColumnFilterType.Date },
|
||||||
|
new() { FieldName = nameof(MassDataReadDto.ChangedWhen), Caption = "Changed", ReadOnly = true, FilterType = ColumnFilterType.Date }
|
||||||
|
};
|
||||||
|
|
||||||
private readonly List<PageSizeOption> pageSizeOptions = new()
|
private readonly List<PageSizeOption> pageSizeOptions = new()
|
||||||
{
|
{
|
||||||
@@ -215,44 +215,15 @@ else
|
|||||||
new() { Value = 0, Text = "PRMassdata_UpsertByCustomerName" }
|
new() { Value = 0, Text = "PRMassdata_UpsertByCustomerName" }
|
||||||
};
|
};
|
||||||
|
|
||||||
private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
|
|
||||||
|
|
||||||
private SizeMode _sizeMode = SizeMode.Medium;
|
|
||||||
private static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();
|
|
||||||
|
|
||||||
private string FormatSizeText(SizeMode size) => size switch
|
|
||||||
{
|
|
||||||
SizeMode.Small => "Klein",
|
|
||||||
SizeMode.Medium => "Mittel",
|
|
||||||
SizeMode.Large => "Groß",
|
|
||||||
_ => size.ToString()
|
|
||||||
};
|
|
||||||
|
|
||||||
private void OnSizeChange(DropDownButtonItemClickEventArgs args)
|
|
||||||
{
|
|
||||||
_sizeMode = Enum.Parse<SizeMode>(args.ItemInfo.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
await InitializeBandLayoutAsync();
|
||||||
layoutUser = await BandLayoutService.EnsureLayoutUserAsync();
|
|
||||||
bandLayout = await BandLayoutService.LoadBandLayoutAsync(LayoutType, LayoutKey, layoutUser, columnLookup);
|
|
||||||
columnBandAssignments = BandLayoutService.BuildAssignmentsFromLayout(bandLayout);
|
|
||||||
ApplyColumnLayoutFromStorage();
|
|
||||||
_sizeMode = bandLayout.SizeMode;
|
|
||||||
UpdateBandOptions();
|
|
||||||
await LoadPage(0);
|
await LoadPage(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (!gridLayoutApplied && gridRef != null && bandLayout.GridLayout != null)
|
await ApplyGridLayoutAfterRenderAsync();
|
||||||
{
|
|
||||||
gridRef.LoadLayout(bandLayout.GridLayout);
|
|
||||||
gridLayoutApplied = true;
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadPage(int page)
|
private async Task LoadPage(int page)
|
||||||
@@ -288,160 +259,6 @@ else
|
|||||||
await LoadPage(0);
|
await LoadPage(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveLayoutAsync()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
|
||||||
return;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
CaptureColumnLayoutFromGrid();
|
|
||||||
await BandLayoutService.SaveBandLayoutAsync(LayoutType, LayoutKey, layoutUser, bandLayout);
|
|
||||||
infoMessage = "Layout gespeichert.";
|
|
||||||
errorMessage = null;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
errorMessage = $"Layout konnte nicht gespeichert werden: {ex.Message}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ResetLayoutAsync()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
|
||||||
return;
|
|
||||||
await BandLayoutService.ResetBandLayoutAsync(LayoutType, LayoutKey, layoutUser);
|
|
||||||
bandLayout = new BandLayout();
|
|
||||||
columnBandAssignments.Clear();
|
|
||||||
UpdateBandOptions();
|
|
||||||
foreach (var column in columnDefinitions)
|
|
||||||
column.Width = null;
|
|
||||||
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
|
||||||
_sizeMode = SizeMode.Medium;
|
|
||||||
if (gridRef != null)
|
|
||||||
gridRef.LoadLayout(new GridPersistentLayout());
|
|
||||||
gridLayoutApplied = false;
|
|
||||||
infoMessage = "Layout zurückgesetzt.";
|
|
||||||
errorMessage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CaptureColumnLayoutFromGrid()
|
|
||||||
{
|
|
||||||
if (gridRef == null) return;
|
|
||||||
var layout = gridRef.SaveLayout();
|
|
||||||
bandLayout.GridLayout = layout;
|
|
||||||
bandLayout.SizeMode = _sizeMode;
|
|
||||||
var orderedColumns = layout.Columns
|
|
||||||
.Where(c => !string.IsNullOrWhiteSpace(c.FieldName))
|
|
||||||
.OrderBy(c => c.VisibleIndex)
|
|
||||||
.ToList();
|
|
||||||
bandLayout.ColumnOrder = orderedColumns.Select(c => c.FieldName).ToList();
|
|
||||||
bandLayout.ColumnWidths = orderedColumns
|
|
||||||
.Where(c => !string.IsNullOrWhiteSpace(c.Width))
|
|
||||||
.ToDictionary(c => c.FieldName, c => c.Width, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyColumnLayoutFromStorage()
|
|
||||||
{
|
|
||||||
foreach (var column in columnDefinitions)
|
|
||||||
{
|
|
||||||
if (bandLayout.ColumnWidths.TryGetValue(column.FieldName, out var width) && !string.IsNullOrWhiteSpace(width))
|
|
||||||
column.Width = width;
|
|
||||||
}
|
|
||||||
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddBand()
|
|
||||||
{
|
|
||||||
bandLayout.Bands.Add(new BandDefinition { Id = Guid.NewGuid().ToString("N"), Caption = "Band" });
|
|
||||||
UpdateBandOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveBand(BandDefinition band)
|
|
||||||
{
|
|
||||||
bandLayout.Bands.Remove(band);
|
|
||||||
foreach (var key in columnBandAssignments.Where(p => p.Value == band.Id).Select(p => p.Key).ToList())
|
|
||||||
columnBandAssignments.Remove(key);
|
|
||||||
UpdateBandOptions();
|
|
||||||
SyncBandsFromAssignments();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateBandCaption(BandDefinition band, string value)
|
|
||||||
{
|
|
||||||
band.Caption = value;
|
|
||||||
UpdateBandOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateColumnBand(string fieldName, string? bandId)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(bandId))
|
|
||||||
columnBandAssignments.Remove(fieldName);
|
|
||||||
else
|
|
||||||
columnBandAssignments[fieldName] = bandId;
|
|
||||||
SyncBandsFromAssignments();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetColumnBand(string fieldName)
|
|
||||||
=> columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
|
|
||||||
|
|
||||||
private void SyncBandsFromAssignments()
|
|
||||||
{
|
|
||||||
foreach (var band in bandLayout.Bands)
|
|
||||||
{
|
|
||||||
band.Columns = columnDefinitions
|
|
||||||
.Where(c => columnBandAssignments.TryGetValue(c.FieldName, out var id) && id == band.Id)
|
|
||||||
.Select(c => c.FieldName)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateBandOptions()
|
|
||||||
{
|
|
||||||
bandOptions = new List<BandOption> { new() { Id = string.Empty, Caption = "Ohne Band" } };
|
|
||||||
bandOptions.AddRange(bandLayout.Bands.Select(b => new BandOption { Id = b.Id, Caption = b.Caption }));
|
|
||||||
}
|
|
||||||
|
|
||||||
private RenderFragment RenderColumns() => builder =>
|
|
||||||
{
|
|
||||||
var seq = 0;
|
|
||||||
builder.OpenComponent<DxGridCommandColumn>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Width", "120px");
|
|
||||||
builder.CloseComponent();
|
|
||||||
var grouped = bandLayout.Bands.SelectMany(b => b.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
||||||
foreach (var column in columnDefinitions.Where(c => !grouped.Contains(c.FieldName)))
|
|
||||||
BuildDataColumn(builder, ref seq, column);
|
|
||||||
foreach (var band in bandLayout.Bands)
|
|
||||||
{
|
|
||||||
if (band.Columns.Count == 0) continue;
|
|
||||||
builder.OpenComponent<DxGridBandColumn>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Caption", band.Caption);
|
|
||||||
builder.AddAttribute(seq++, "Columns", (RenderFragment)(bandBuilder =>
|
|
||||||
{
|
|
||||||
var bandSeq = 0;
|
|
||||||
foreach (var columnName in band.Columns)
|
|
||||||
{
|
|
||||||
if (columnLookup.TryGetValue(columnName, out var column))
|
|
||||||
BuildDataColumn(bandBuilder, ref bandSeq, column);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private void BuildDataColumn(RenderTreeBuilder builder, ref int seq, ColumnDefinition column)
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxGridDataColumn>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "FieldName", column.FieldName);
|
|
||||||
builder.AddAttribute(seq++, "Caption", column.Caption);
|
|
||||||
if (!string.IsNullOrWhiteSpace(column.Width))
|
|
||||||
builder.AddAttribute(seq++, "Width", column.Width);
|
|
||||||
if (!string.IsNullOrWhiteSpace(column.DisplayFormat))
|
|
||||||
builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat);
|
|
||||||
if (column.ReadOnly)
|
|
||||||
builder.AddAttribute(seq++, "ReadOnly", true);
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetEditContext(EditContext context)
|
private void SetEditContext(EditContext context)
|
||||||
{
|
{
|
||||||
if (editContext == context) return;
|
if (editContext == context) return;
|
||||||
@@ -470,7 +287,7 @@ else
|
|||||||
|
|
||||||
private void SetPopupHeaderText(bool isNew) => popupHeaderText = isNew ? "Neu" : "Edit";
|
private void SetPopupHeaderText(bool isNew) => popupHeaderText = isNew ? "Neu" : "Edit";
|
||||||
|
|
||||||
private async Task OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
|
private void OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.IsNew)
|
if (e.IsNew)
|
||||||
{
|
{
|
||||||
@@ -523,18 +340,17 @@ else
|
|||||||
Category = editModel.Category,
|
Category = editModel.Category,
|
||||||
StatusFlag = editModel.StatusFlag
|
StatusFlag = editModel.StatusFlag
|
||||||
};
|
};
|
||||||
try
|
|
||||||
|
var result = await Api.UpsertAsync(dto);
|
||||||
|
if (!result.Success)
|
||||||
{
|
{
|
||||||
var saved = await Api.UpsertAsync(dto);
|
errorMessage = result.Error ?? "Speichern fehlgeschlagen.";
|
||||||
infoMessage = editModel.IsNew ? "MassData angelegt." : "MassData aktualisiert.";
|
|
||||||
focusedRowKey = saved.Id;
|
|
||||||
await LoadPage(pageIndex);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
errorMessage = $"Fehler beim Speichern: {ex.Message}";
|
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
infoMessage = editModel.IsNew ? "MassData angelegt." : "MassData aktualisiert.";
|
||||||
|
focusedRowKey = result.Value?.Id;
|
||||||
|
await LoadPage(pageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddValidationError(MassDataEditModel editModel, string fieldName, string message)
|
private void AddValidationError(MassDataEditModel editModel, string fieldName, string message)
|
||||||
@@ -575,4 +391,36 @@ else
|
|||||||
public int? Value { get; set; }
|
public int? Value { get; set; }
|
||||||
public string Text { get; set; } = string.Empty;
|
public string Text { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int _focusedVisibleIndex;
|
||||||
|
|
||||||
|
private async Task EditFocusedRow()
|
||||||
|
=> await gridRef!.StartEditRowAsync(_focusedVisibleIndex);
|
||||||
|
|
||||||
|
private Task DeleteFocusedRow()
|
||||||
|
{
|
||||||
|
gridRef!.ShowRowDeleteConfirmation(_focusedVisibleIndex);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveLayoutWithFeedbackAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SaveLayoutAsync();
|
||||||
|
infoMessage = "Layout gespeichert.";
|
||||||
|
errorMessage = null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
errorMessage = $"Layout konnte nicht gespeichert werden: {ex.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ResetLayoutWithFeedbackAsync()
|
||||||
|
{
|
||||||
|
await ResetLayoutAsync();
|
||||||
|
infoMessage = "Layout zurückgesetzt.";
|
||||||
|
errorMessage = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -67,11 +67,6 @@
|
|||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
if (dashboards.Count == 0)
|
|
||||||
{
|
|
||||||
await RefreshDashboards();
|
|
||||||
}
|
|
||||||
|
|
||||||
var requestedId = string.IsNullOrWhiteSpace(DashboardId) || string.Equals(DashboardId, "default", StringComparison.OrdinalIgnoreCase)
|
var requestedId = string.IsNullOrWhiteSpace(DashboardId) || string.Equals(DashboardId, "default", StringComparison.OrdinalIgnoreCase)
|
||||||
? null
|
? null
|
||||||
: DashboardId;
|
: DashboardId;
|
||||||
|
|||||||
@@ -31,8 +31,8 @@
|
|||||||
|
|
||||||
public class CarouselData
|
public class CarouselData
|
||||||
{
|
{
|
||||||
public string Source { get; set; }
|
public string Source { get; set; } = string.Empty;
|
||||||
public string AlternateText { get; set; }
|
public string AlternateText { get; set; } = string.Empty;
|
||||||
|
|
||||||
public CarouselData(string source, string alt)
|
public CarouselData(string source, string alt)
|
||||||
{
|
{
|
||||||
|
|||||||
60
DbFirst.BlazorWebApp/Services/ApiClientHelper.cs
Normal file
60
DbFirst.BlazorWebApp/Services/ApiClientHelper.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
|
||||||
|
namespace DbFirst.BlazorWebApp.Services;
|
||||||
|
|
||||||
|
internal sealed class ProblemDetailsDto
|
||||||
|
{
|
||||||
|
public string? Type { get; set; }
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Detail { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class ApiClientHelper
|
||||||
|
{
|
||||||
|
public static async Task<string> ReadErrorAsync(HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
string? problemTitle = null;
|
||||||
|
string? problemDetail = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var problem = await response.Content.ReadFromJsonAsync<ProblemDetailsDto>();
|
||||||
|
if (problem != null)
|
||||||
|
{
|
||||||
|
problemTitle = problem.Title;
|
||||||
|
problemDetail = problem.Detail ?? problem.Type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
var status = response.StatusCode;
|
||||||
|
var reason = response.ReasonPhrase;
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
string? detail = problemDetail;
|
||||||
|
if (string.IsNullOrWhiteSpace(detail) && !string.IsNullOrWhiteSpace(body))
|
||||||
|
detail = body;
|
||||||
|
|
||||||
|
return status switch
|
||||||
|
{
|
||||||
|
HttpStatusCode.BadRequest => $"Eingabe ungültig{FormatSuffix(problemTitle, detail, reason)}",
|
||||||
|
HttpStatusCode.NotFound => $"Nicht gefunden{FormatSuffix(problemTitle, detail, reason)}",
|
||||||
|
HttpStatusCode.Conflict => $"Konflikt{FormatSuffix(problemTitle, detail, reason)}",
|
||||||
|
HttpStatusCode.Unauthorized => $"Nicht autorisiert{FormatSuffix(problemTitle, detail, reason)}",
|
||||||
|
HttpStatusCode.Forbidden => $"Nicht erlaubt{FormatSuffix(problemTitle, detail, reason)}",
|
||||||
|
HttpStatusCode.InternalServerError => $"Serverfehler{FormatSuffix(problemTitle, detail, reason)}",
|
||||||
|
_ => $"Fehler {(int)status} {reason ?? string.Empty}{FormatSuffix(problemTitle, detail, reason)}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatSuffix(string? title, string? detail, string? reason)
|
||||||
|
{
|
||||||
|
var parts = new List<string>();
|
||||||
|
if (!string.IsNullOrWhiteSpace(title)) parts.Add(title);
|
||||||
|
if (!string.IsNullOrWhiteSpace(detail)) parts.Add(detail);
|
||||||
|
if (parts.Count == 0 && !string.IsNullOrWhiteSpace(reason)) parts.Add(reason);
|
||||||
|
if (parts.Count == 0) return string.Empty;
|
||||||
|
return ": " + string.Join(" | ", parts);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ public class CatalogApiClient
|
|||||||
return ApiResult<CatalogReadDto?>.Ok(payload);
|
return ApiResult<CatalogReadDto?>.Ok(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
var error = await ReadErrorAsync(response);
|
var error = await ApiClientHelper.ReadErrorAsync(response);
|
||||||
return ApiResult<CatalogReadDto?>.Fail(error);
|
return ApiResult<CatalogReadDto?>.Fail(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ public class CatalogApiClient
|
|||||||
return ApiResult<bool>.Ok(true);
|
return ApiResult<bool>.Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var error = await ReadErrorAsync(response);
|
var error = await ApiClientHelper.ReadErrorAsync(response);
|
||||||
return ApiResult<bool>.Fail(error);
|
return ApiResult<bool>.Fail(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,68 +58,9 @@ public class CatalogApiClient
|
|||||||
return ApiResult<bool>.Ok(true);
|
return ApiResult<bool>.Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var error = await ReadErrorAsync(response);
|
var error = await ApiClientHelper.ReadErrorAsync(response);
|
||||||
return ApiResult<bool>.Fail(error);
|
return ApiResult<bool>.Fail(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<string> ReadErrorAsync(HttpResponseMessage response)
|
|
||||||
{
|
|
||||||
string? problemTitle = null;
|
|
||||||
string? problemDetail = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var problem = await response.Content.ReadFromJsonAsync<ProblemDetailsDto>();
|
|
||||||
if (problem != null)
|
|
||||||
{
|
|
||||||
problemTitle = problem.Title;
|
|
||||||
problemDetail = problem.Detail ?? problem.Type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
var status = response.StatusCode;
|
|
||||||
var reason = response.ReasonPhrase;
|
|
||||||
var body = await response.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
string? detail = problemDetail;
|
|
||||||
if (string.IsNullOrWhiteSpace(detail) && !string.IsNullOrWhiteSpace(body))
|
|
||||||
{
|
|
||||||
detail = body;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status == HttpStatusCode.Conflict)
|
|
||||||
{
|
|
||||||
return "Datensatz existiert bereits. Bitte wählen Sie einen anderen Titel.";
|
|
||||||
}
|
|
||||||
if (status == HttpStatusCode.BadRequest && (detail?.Contains("CatTitle cannot be changed", StringComparison.OrdinalIgnoreCase) ?? false))
|
|
||||||
{
|
|
||||||
return "Titel kann nicht geändert werden.";
|
|
||||||
}
|
|
||||||
|
|
||||||
return status switch
|
|
||||||
{
|
|
||||||
HttpStatusCode.BadRequest => $"Eingabe ungültig{FormatSuffix(problemTitle, detail, reason)}",
|
|
||||||
HttpStatusCode.NotFound => $"Nicht gefunden{FormatSuffix(problemTitle, detail, reason)}",
|
|
||||||
HttpStatusCode.Conflict => $"Konflikt{FormatSuffix(problemTitle, detail, reason)}",
|
|
||||||
HttpStatusCode.Unauthorized => $"Nicht autorisiert{FormatSuffix(problemTitle, detail, reason)}",
|
|
||||||
HttpStatusCode.Forbidden => $"Nicht erlaubt{FormatSuffix(problemTitle, detail, reason)}",
|
|
||||||
HttpStatusCode.InternalServerError => $"Serverfehler{FormatSuffix(problemTitle, detail, reason)}",
|
|
||||||
_ => $"Fehler {(int)status} {reason ?? string.Empty}{FormatSuffix(problemTitle, detail, reason)}"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string FormatSuffix(string? title, string? detail, string? reason)
|
|
||||||
{
|
|
||||||
var parts = new List<string>();
|
|
||||||
if (!string.IsNullOrWhiteSpace(title)) parts.Add(title);
|
|
||||||
if (!string.IsNullOrWhiteSpace(detail)) parts.Add(detail);
|
|
||||||
if (parts.Count == 0 && !string.IsNullOrWhiteSpace(reason)) parts.Add(reason);
|
|
||||||
if (parts.Count == 0) return string.Empty;
|
|
||||||
return ": " + string.Join(" | ", parts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public record ApiResult<T>(bool Success, T? Value, string? Error)
|
public record ApiResult<T>(bool Success, T? Value, string? Error)
|
||||||
@@ -128,9 +69,3 @@ public record ApiResult<T>(bool Success, T? Value, string? Error)
|
|||||||
public static ApiResult<T> Fail(string? error) => new(false, default, error);
|
public static ApiResult<T> Fail(string? error) => new(false, default, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class ProblemDetailsDto
|
|
||||||
{
|
|
||||||
public string? Type { get; set; }
|
|
||||||
public string? Title { get; set; }
|
|
||||||
public string? Detail { get; set; }
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -36,12 +36,17 @@ public class MassDataApiClient
|
|||||||
return result ?? new List<MassDataReadDto>();
|
return result ?? new List<MassDataReadDto>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MassDataReadDto> UpsertAsync(MassDataWriteDto dto)
|
public async Task<ApiResult<MassDataReadDto?>> UpsertAsync(MassDataWriteDto dto)
|
||||||
{
|
{
|
||||||
var response = await _httpClient.PostAsJsonAsync($"{Endpoint}/upsert", dto);
|
var response = await _httpClient.PostAsJsonAsync($"{Endpoint}/upsert", dto);
|
||||||
response.EnsureSuccessStatusCode();
|
if (response.IsSuccessStatusCode)
|
||||||
var payload = await response.Content.ReadFromJsonAsync<MassDataReadDto>();
|
{
|
||||||
return payload ?? new MassDataReadDto();
|
var payload = await response.Content.ReadFromJsonAsync<MassDataReadDto>();
|
||||||
|
return ApiResult<MassDataReadDto?>.Ok(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
var error = await ApiClientHelper.ReadErrorAsync(response);
|
||||||
|
return ApiResult<MassDataReadDto?>.Fail(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MassDataReadDto?> GetByCustomerNameAsync(string customerName)
|
public async Task<MassDataReadDto?> GetByCustomerNameAsync(string customerName)
|
||||||
|
|||||||
1157
docs/PROJEKTDOKUMENTATION.md
Normal file
1157
docs/PROJEKTDOKUMENTATION.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user