Compare commits
8 Commits
c5ca9f0048
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ac8e94334 | ||
|
|
dc74d21426 | ||
|
|
566c3b3276 | ||
|
|
ac84866abe | ||
|
|
d9ce4a5dca | ||
|
|
13617dde87 | ||
|
|
789066a214 | ||
|
|
964d508630 |
@@ -1,60 +1,5 @@
|
|||||||
@using System.Text.Json
|
|
||||||
@using Microsoft.AspNetCore.Components
|
|
||||||
@using Microsoft.AspNetCore.Components.Rendering
|
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
|
||||||
@using DevExpress.Blazor
|
|
||||||
@using DevExpress.Data.Filtering
|
|
||||||
@inject CatalogApiClient Api
|
@inject CatalogApiClient Api
|
||||||
@inject LayoutApiClient LayoutApi
|
@inject BandLayoutService BandLayoutService
|
||||||
@inject IJSRuntime JsRuntime
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.action-panel { margin-bottom: 16px; }
|
|
||||||
.grid-section { margin-top: 12px; }
|
|
||||||
.catalog-edit-popup {
|
|
||||||
min-width: 720px;
|
|
||||||
}
|
|
||||||
.band-editor {
|
|
||||||
display: grid;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
.band-controls {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.band-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.band-columns {
|
|
||||||
max-width: 720px;
|
|
||||||
}
|
|
||||||
.filter-row-cell {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.filter-operator {
|
|
||||||
width: 52px;
|
|
||||||
min-width: 52px;
|
|
||||||
flex: 0 0 52px;
|
|
||||||
}
|
|
||||||
.filter-value {
|
|
||||||
min-width: 160px;
|
|
||||||
flex: 1 1 160px;
|
|
||||||
}
|
|
||||||
.loading-container {
|
|
||||||
min-height: 160px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
||||||
{
|
{
|
||||||
@@ -75,15 +20,15 @@ else if (!string.IsNullOrWhiteSpace(infoMessage))
|
|||||||
}
|
}
|
||||||
else if (items.Count == 0)
|
else if (items.Count == 0)
|
||||||
{
|
{
|
||||||
<p>Keine Einträge vorhanden.</p>
|
<p>Keine Einträge vorhanden.</p>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="band-editor">
|
<div class="band-editor">
|
||||||
<div class="band-controls">
|
<div class="band-controls">
|
||||||
<DxButton Text="Band hinzufügen" Click="AddBand" />
|
<DxButton Text="Band hinzufügen" Click="AddBand" />
|
||||||
<DxButton Text="Layout speichern" Click="SaveLayoutAsync" Enabled="@CanSaveBandLayout" />
|
<DxButton Text="Layout speichern" Click="SaveLayoutAsync" Enabled="@CanSaveBandLayout" />
|
||||||
<DxButton Text="Band-Layout zurücksetzen" Click="ResetBandLayoutAsync" />
|
<DxButton Text="Layout zurücksetzen" Click="ResetLayoutAsync" />
|
||||||
</div>
|
</div>
|
||||||
@foreach (var band in bandLayout.Bands)
|
@foreach (var band in bandLayout.Bands)
|
||||||
{
|
{
|
||||||
@@ -155,7 +100,9 @@ else
|
|||||||
@RenderColumns()
|
@RenderColumns()
|
||||||
</Columns>
|
</Columns>
|
||||||
<EditFormTemplate Context="editFormContext">
|
<EditFormTemplate Context="editFormContext">
|
||||||
@{ SetEditContext(editFormContext.EditContext); var editModel = (CatalogEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew); }
|
@{
|
||||||
|
SetEditContext(editFormContext.EditContext); var editModel = (CatalogEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew);
|
||||||
|
}
|
||||||
<DxFormLayout ColCount="2">
|
<DxFormLayout ColCount="2">
|
||||||
<DxFormLayoutItem Caption="Titel">
|
<DxFormLayoutItem Caption="Titel">
|
||||||
<DxTextBox @bind-Text="editModel.CatTitle" Width="100%" />
|
<DxTextBox @bind-Text="editModel.CatTitle" Width="100%" />
|
||||||
@@ -197,13 +144,13 @@ else
|
|||||||
private string popupHeaderText = "Edit";
|
private string popupHeaderText = "Edit";
|
||||||
private const string LayoutType = "GRID_BANDS";
|
private const string LayoutType = "GRID_BANDS";
|
||||||
private const string LayoutKey = "CatalogsGrid";
|
private const string LayoutKey = "CatalogsGrid";
|
||||||
private const string LayoutUserStorageKey = "layoutUser";
|
|
||||||
private string? layoutUser;
|
private string? layoutUser;
|
||||||
private BandLayout bandLayout = new();
|
private BandLayout bandLayout = new();
|
||||||
private Dictionary<string, string> columnBandAssignments = new();
|
private Dictionary<string, string> columnBandAssignments = new();
|
||||||
private List<BandOption> bandOptions = new();
|
private List<BandOption> bandOptions = new();
|
||||||
private Dictionary<string, ColumnDefinition> columnLookup = new();
|
private Dictionary<string, ColumnDefinition> columnLookup = new();
|
||||||
private readonly JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web);
|
private bool gridLayoutApplied;
|
||||||
|
|
||||||
private List<ColumnDefinition> columnDefinitions = new()
|
private List<ColumnDefinition> columnDefinitions = new()
|
||||||
{
|
{
|
||||||
new() { FieldName = nameof(CatalogReadDto.Guid), Caption = "Id", Width = "140px", FilterType = ColumnFilterType.Text },
|
new() { FieldName = nameof(CatalogReadDto.Guid), Caption = "Id", Width = "140px", FilterType = ColumnFilterType.Text },
|
||||||
@@ -211,8 +158,8 @@ else
|
|||||||
new() { FieldName = nameof(CatalogReadDto.CatString), Caption = "String", FilterType = ColumnFilterType.Text },
|
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.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.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.ChangedWho), Caption = "Geändert von", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
||||||
new() { FieldName = nameof(CatalogReadDto.ChangedWhen), Caption = "Geändert am", ReadOnly = true, FilterType = ColumnFilterType.Date }
|
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()
|
||||||
@@ -222,30 +169,32 @@ else
|
|||||||
};
|
};
|
||||||
|
|
||||||
private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
|
private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
|
||||||
private bool gridLayoutApplied;
|
|
||||||
|
|
||||||
private DevExpress.Blazor.SizeMode _sizeMode = DevExpress.Blazor.SizeMode.Medium;
|
private SizeMode _sizeMode = SizeMode.Medium;
|
||||||
private static readonly List<DevExpress.Blazor.SizeMode> _sizeModes =
|
private static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();
|
||||||
Enum.GetValues<DevExpress.Blazor.SizeMode>().ToList();
|
|
||||||
|
|
||||||
private string FormatSizeText(DevExpress.Blazor.SizeMode size) => size switch
|
private string FormatSizeText(SizeMode size) => size switch
|
||||||
{
|
{
|
||||||
DevExpress.Blazor.SizeMode.Small => "Klein",
|
SizeMode.Small => "Klein",
|
||||||
DevExpress.Blazor.SizeMode.Medium => "Mittel",
|
SizeMode.Medium => "Mittel",
|
||||||
DevExpress.Blazor.SizeMode.Large => "Groß",
|
SizeMode.Large => "Groß",
|
||||||
_ => size.ToString()
|
_ => size.ToString()
|
||||||
};
|
};
|
||||||
|
|
||||||
private void OnSizeChange(DropDownButtonItemClickEventArgs args)
|
private void OnSizeChange(DropDownButtonItemClickEventArgs args)
|
||||||
{
|
{
|
||||||
_sizeMode = Enum.Parse<DevExpress.Blazor.SizeMode>(args.ItemInfo.Id);
|
_sizeMode = Enum.Parse<SizeMode>(args.ItemInfo.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
|
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||||
await EnsureLayoutUserAsync();
|
layoutUser = await BandLayoutService.EnsureLayoutUserAsync();
|
||||||
await LoadBandLayoutAsync();
|
bandLayout = await BandLayoutService.LoadBandLayoutAsync(LayoutType, LayoutKey, layoutUser, columnLookup);
|
||||||
|
columnBandAssignments = BandLayoutService.BuildAssignmentsFromLayout(bandLayout);
|
||||||
|
ApplyColumnLayoutFromStorage();
|
||||||
|
_sizeMode = bandLayout.SizeMode;
|
||||||
|
UpdateBandOptions();
|
||||||
await LoadCatalogs();
|
await LoadCatalogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,71 +208,6 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetEditContext(EditContext context)
|
|
||||||
{
|
|
||||||
if (editContext == context)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editContext != null)
|
|
||||||
{
|
|
||||||
editContext.OnFieldChanged -= OnEditFieldChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
editContext = context;
|
|
||||||
validationMessageStore = new ValidationMessageStore(editContext);
|
|
||||||
editContext.OnFieldChanged += OnEditFieldChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (validationMessageStore == null || editContext == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.FieldIdentifier.FieldName == nameof(CatalogEditModel.UpdateProcedure))
|
|
||||||
{
|
|
||||||
validationMessageStore.Clear();
|
|
||||||
editContext.NotifyValidationStateChanged();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.FieldIdentifier.FieldName == nameof(CatalogEditModel.CatTitle))
|
|
||||||
{
|
|
||||||
var field = new FieldIdentifier(editContext.Model, nameof(CatalogEditModel.CatTitle));
|
|
||||||
validationMessageStore.Clear(field);
|
|
||||||
editContext.NotifyValidationStateChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetPopupHeaderText(bool isNew)
|
|
||||||
{
|
|
||||||
popupHeaderText = isNew ? "Neu" : "Edit";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
|
|
||||||
{
|
|
||||||
popupHeaderText = e.IsNew ? "Neu" : "Edit";
|
|
||||||
if (e.IsNew)
|
|
||||||
{
|
|
||||||
e.EditModel = new CatalogEditModel { IsNew = true };
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var item = (CatalogReadDto)e.DataItem;
|
|
||||||
e.EditModel = new CatalogEditModel
|
|
||||||
{
|
|
||||||
Guid = item.Guid,
|
|
||||||
CatTitle = item.CatTitle,
|
|
||||||
CatString = item.CatString,
|
|
||||||
UpdateProcedure = 0,
|
|
||||||
OriginalCatTitle = item.CatTitle,
|
|
||||||
IsNew = false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadCatalogs()
|
private async Task LoadCatalogs()
|
||||||
{
|
{
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
@@ -344,11 +228,229 @@ 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)
|
||||||
|
{
|
||||||
|
if (editContext == context) return;
|
||||||
|
if (editContext != null)
|
||||||
|
editContext.OnFieldChanged -= OnEditFieldChanged;
|
||||||
|
editContext = context;
|
||||||
|
validationMessageStore = new ValidationMessageStore(editContext);
|
||||||
|
editContext.OnFieldChanged += OnEditFieldChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (validationMessageStore == null || editContext == null) return;
|
||||||
|
|
||||||
|
if (e.FieldIdentifier.FieldName == nameof(CatalogEditModel.UpdateProcedure))
|
||||||
|
{
|
||||||
|
validationMessageStore.Clear();
|
||||||
|
editContext.NotifyValidationStateChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.FieldIdentifier.FieldName == nameof(CatalogEditModel.CatTitle))
|
||||||
|
{
|
||||||
|
validationMessageStore.Clear(new FieldIdentifier(editContext.Model, nameof(CatalogEditModel.CatTitle)));
|
||||||
|
editContext.NotifyValidationStateChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetPopupHeaderText(bool isNew) => popupHeaderText = isNew ? "Neu" : "Edit";
|
||||||
|
|
||||||
|
private void OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
|
||||||
|
{
|
||||||
|
popupHeaderText = e.IsNew ? "Neu" : "Edit";
|
||||||
|
if (e.IsNew)
|
||||||
|
{
|
||||||
|
e.EditModel = new CatalogEditModel { IsNew = true };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = (CatalogReadDto)e.DataItem;
|
||||||
|
e.EditModel = new CatalogEditModel
|
||||||
|
{
|
||||||
|
Guid = item.Guid,
|
||||||
|
CatTitle = item.CatTitle,
|
||||||
|
CatString = item.CatString,
|
||||||
|
UpdateProcedure = 0,
|
||||||
|
OriginalCatTitle = item.CatTitle,
|
||||||
|
IsNew = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async Task OnEditModelSaving(GridEditModelSavingEventArgs e)
|
private async Task OnEditModelSaving(GridEditModelSavingEventArgs e)
|
||||||
{
|
{
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
infoMessage = null;
|
infoMessage = null;
|
||||||
|
|
||||||
validationMessageStore?.Clear();
|
validationMessageStore?.Clear();
|
||||||
editContext?.NotifyValidationStateChanged();
|
editContext?.NotifyValidationStateChanged();
|
||||||
|
|
||||||
@@ -374,18 +476,12 @@ else
|
|||||||
if (!created.Success || created.Value == null)
|
if (!created.Success || created.Value == null)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(created.Error))
|
if (!string.IsNullOrWhiteSpace(created.Error))
|
||||||
{
|
|
||||||
AddValidationError(editModel, nameof(CatalogEditModel.CatTitle), created.Error);
|
AddValidationError(editModel, nameof(CatalogEditModel.CatTitle), created.Error);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
errorMessage = "Anlegen fehlgeschlagen.";
|
errorMessage = "Anlegen fehlgeschlagen.";
|
||||||
}
|
|
||||||
|
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
infoMessage = "Katalog angelegt.";
|
infoMessage = "Katalog angelegt.";
|
||||||
focusedRowKey = created.Value.Guid;
|
focusedRowKey = created.Value.Guid;
|
||||||
}
|
}
|
||||||
@@ -398,7 +494,6 @@ else
|
|||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
infoMessage = "Katalog aktualisiert.";
|
infoMessage = "Katalog aktualisiert.";
|
||||||
focusedRowKey = editModel.Guid;
|
focusedRowKey = editModel.Guid;
|
||||||
}
|
}
|
||||||
@@ -414,30 +509,20 @@ else
|
|||||||
|
|
||||||
private void AddValidationError(CatalogEditModel editModel, string fieldName, string message)
|
private void AddValidationError(CatalogEditModel editModel, string fieldName, string message)
|
||||||
{
|
{
|
||||||
if (editContext == null || validationMessageStore == null)
|
if (editContext == null || validationMessageStore == null) return;
|
||||||
{
|
validationMessageStore.Add(new FieldIdentifier(editModel, fieldName), message);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var field = new FieldIdentifier(editModel, fieldName);
|
|
||||||
validationMessageStore.Add(field, message);
|
|
||||||
editContext.NotifyValidationStateChanged();
|
editContext.NotifyValidationStateChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ValidateEditModel(CatalogEditModel editModel, bool isNew)
|
private bool ValidateEditModel(CatalogEditModel editModel, bool isNew)
|
||||||
{
|
{
|
||||||
if (isNew)
|
if (isNew) return true;
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editModel.UpdateProcedure == 0 &&
|
if (editModel.UpdateProcedure == 0 &&
|
||||||
!string.Equals(editModel.CatTitle, editModel.OriginalCatTitle, StringComparison.OrdinalIgnoreCase))
|
!string.Equals(editModel.CatTitle, editModel.OriginalCatTitle, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
AddValidationError(editModel, nameof(CatalogEditModel.CatTitle), "Titel kann nicht geändert werden.");
|
AddValidationError(editModel, nameof(CatalogEditModel.CatTitle), "Titel kann nicht geändert werden.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,346 +530,26 @@ else
|
|||||||
{
|
{
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
infoMessage = null;
|
infoMessage = null;
|
||||||
|
|
||||||
var item = (CatalogReadDto)e.DataItem;
|
var item = (CatalogReadDto)e.DataItem;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var deleted = await Api.DeleteAsync(item.Guid);
|
var deleted = await Api.DeleteAsync(item.Guid);
|
||||||
if (!deleted.Success)
|
if (!deleted.Success)
|
||||||
{
|
{
|
||||||
errorMessage = deleted.Error ?? "Löschen fehlgeschlagen.";
|
errorMessage = deleted.Error ?? "Löschen fehlgeschlagen.";
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
infoMessage = "Katalog gelöscht.";
|
||||||
infoMessage = "Katalog gelöscht.";
|
|
||||||
await LoadCatalogs();
|
await LoadCatalogs();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
errorMessage = $"Fehler beim Löschen: {ex.Message}";
|
errorMessage = $"Fehler beim Löschen: {ex.Message}";
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnsureLayoutUserAsync()
|
|
||||||
{
|
|
||||||
layoutUser = await JsRuntime.InvokeAsync<string?>("localStorage.getItem", LayoutUserStorageKey);
|
|
||||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
|
||||||
{
|
|
||||||
layoutUser = Guid.NewGuid().ToString("N");
|
|
||||||
await JsRuntime.InvokeVoidAsync("localStorage.setItem", LayoutUserStorageKey, layoutUser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveLayoutAsync()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
CaptureColumnLayoutFromGrid();
|
|
||||||
|
|
||||||
var layoutData = JsonSerializer.Serialize(bandLayout, jsonOptions);
|
|
||||||
await LayoutApi.UpsertAsync(new LayoutDto
|
|
||||||
{
|
|
||||||
LayoutType = LayoutType,
|
|
||||||
LayoutKey = LayoutKey,
|
|
||||||
UserName = layoutUser,
|
|
||||||
LayoutData = layoutData
|
|
||||||
});
|
|
||||||
infoMessage = "Layout gespeichert.";
|
|
||||||
errorMessage = null;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
errorMessage = $"Layout konnte nicht gespeichert werden: {ex.Message}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CaptureColumnLayoutFromGrid()
|
|
||||||
{
|
|
||||||
if (gridRef == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var layout = gridRef.SaveLayout();
|
|
||||||
bandLayout.GridLayout = layout;
|
|
||||||
|
|
||||||
var orderedColumns = layout.Columns
|
|
||||||
.Where(column => !string.IsNullOrWhiteSpace(column.FieldName))
|
|
||||||
.OrderBy(column => column.VisibleIndex)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
bandLayout.ColumnOrder = orderedColumns
|
|
||||||
.Select(column => column.FieldName)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
bandLayout.ColumnWidths = orderedColumns
|
|
||||||
.Where(column => !string.IsNullOrWhiteSpace(column.Width))
|
|
||||||
.ToDictionary(column => column.FieldName, column => column.Width, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadBandLayoutAsync()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
|
||||||
{
|
|
||||||
bandLayout = new BandLayout();
|
|
||||||
UpdateBandOptions();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var stored = await LayoutApi.GetAsync(LayoutType, LayoutKey, layoutUser);
|
|
||||||
if (stored != null && !string.IsNullOrWhiteSpace(stored.LayoutData))
|
|
||||||
{
|
|
||||||
var parsed = JsonSerializer.Deserialize<BandLayout>(stored.LayoutData, jsonOptions);
|
|
||||||
bandLayout = NormalizeBandLayout(parsed);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bandLayout = new BandLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
columnBandAssignments = BuildAssignmentsFromLayout(bandLayout);
|
|
||||||
ApplyColumnLayoutFromStorage();
|
|
||||||
UpdateBandOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ResetBandLayoutAsync()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await LayoutApi.DeleteAsync(LayoutType, LayoutKey, layoutUser);
|
|
||||||
bandLayout = new BandLayout();
|
|
||||||
columnBandAssignments.Clear();
|
|
||||||
UpdateBandOptions();
|
|
||||||
infoMessage = "Band-Layout zurückgesetzt.";
|
|
||||||
}
|
|
||||||
|
|
||||||
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(column => column.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);
|
|
||||||
var removedColumns = columnBandAssignments.Where(pair => pair.Value == band.Id)
|
|
||||||
.Select(pair => pair.Key)
|
|
||||||
.ToList();
|
|
||||||
foreach (var column in removedColumns)
|
|
||||||
{
|
|
||||||
columnBandAssignments.Remove(column);
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
return columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SyncBandsFromAssignments()
|
|
||||||
{
|
|
||||||
foreach (var band in bandLayout.Bands)
|
|
||||||
{
|
|
||||||
band.Columns = columnDefinitions
|
|
||||||
.Where(column => columnBandAssignments.TryGetValue(column.FieldName, out var bandId) && bandId == band.Id)
|
|
||||||
.Select(column => column.FieldName)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateBandOptions()
|
|
||||||
{
|
|
||||||
bandOptions = new List<BandOption> { new() { Id = string.Empty, Caption = "Ohne Band" } };
|
|
||||||
bandOptions.AddRange(bandLayout.Bands.Select(band => new BandOption { Id = band.Id, Caption = band.Caption }));
|
|
||||||
}
|
|
||||||
|
|
||||||
private BandLayout NormalizeBandLayout(BandLayout? layout)
|
|
||||||
{
|
|
||||||
layout ??= new BandLayout();
|
|
||||||
layout.Bands ??= new List<BandDefinition>();
|
|
||||||
layout.ColumnOrder ??= new List<string>();
|
|
||||||
layout.ColumnWidths ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
foreach (var band in layout.Bands)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(band.Id))
|
|
||||||
{
|
|
||||||
band.Id = Guid.NewGuid().ToString("N");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(band.Caption))
|
|
||||||
{
|
|
||||||
band.Caption = "Band";
|
|
||||||
}
|
|
||||||
|
|
||||||
band.Columns = band.Columns?.Where(columnLookup.ContainsKey).ToList() ?? new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return layout;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Dictionary<string, string> BuildAssignmentsFromLayout(BandLayout layout)
|
|
||||||
{
|
|
||||||
var assignments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
foreach (var band in layout.Bands)
|
|
||||||
{
|
|
||||||
foreach (var column in band.Columns)
|
|
||||||
{
|
|
||||||
assignments[column] = band.Id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return assignments;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RenderFragment RenderColumns() => builder =>
|
|
||||||
{
|
|
||||||
var seq = 0;
|
|
||||||
builder.OpenComponent<DxGridCommandColumn>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Width", "120px");
|
|
||||||
builder.CloseComponent();
|
|
||||||
|
|
||||||
var grouped = bandLayout.Bands.SelectMany(band => band.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
||||||
foreach (var column in columnDefinitions.Where(column => !grouped.Contains(column.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 sealed class BandLayout
|
|
||||||
{
|
|
||||||
public List<BandDefinition> Bands { get; set; } = new();
|
|
||||||
public List<string> ColumnOrder { get; set; } = new();
|
|
||||||
public Dictionary<string, string?> ColumnWidths { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
|
||||||
public GridPersistentLayout? GridLayout { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class BandDefinition
|
|
||||||
{
|
|
||||||
public string Id { get; set; } = string.Empty;
|
|
||||||
public string Caption { get; set; } = string.Empty;
|
|
||||||
public List<string> Columns { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class BandOption
|
|
||||||
{
|
|
||||||
public string Id { get; set; } = string.Empty;
|
|
||||||
public string Caption { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class ColumnDefinition
|
|
||||||
{
|
|
||||||
public string FieldName { get; init; } = string.Empty;
|
|
||||||
public string Caption { get; init; } = string.Empty;
|
|
||||||
public string? Width { get; set; }
|
|
||||||
public string? DisplayFormat { get; init; }
|
|
||||||
public bool ReadOnly { get; init; }
|
|
||||||
public ColumnFilterType FilterType { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum ColumnFilterType
|
|
||||||
{
|
|
||||||
Text,
|
|
||||||
Date
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class CatalogEditModel
|
private sealed class CatalogEditModel
|
||||||
{
|
{
|
||||||
public int Guid { get; set; }
|
public int Guid { get; set; }
|
||||||
@@ -800,10 +565,4 @@ 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 sealed class FilterOperatorOption
|
|
||||||
{
|
|
||||||
public string Value { get; set; } = string.Empty;
|
|
||||||
public string Text { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
3
DbFirst.BlazorWebApp/Components/CatalogsGrid.razor.css
Normal file
3
DbFirst.BlazorWebApp/Components/CatalogsGrid.razor.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.catalog-edit-popup {
|
||||||
|
min-width: 720px;
|
||||||
|
}
|
||||||
@@ -1,68 +1,5 @@
|
|||||||
@using System.Text.Json
|
|
||||||
@using Microsoft.AspNetCore.Components
|
|
||||||
@using Microsoft.AspNetCore.Components.Rendering
|
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
|
||||||
@using DevExpress.Blazor
|
|
||||||
@using DevExpress.Data.Filtering
|
|
||||||
@inject MassDataApiClient Api
|
@inject MassDataApiClient Api
|
||||||
@inject LayoutApiClient LayoutApi
|
@inject BandLayoutService BandLayoutService
|
||||||
@inject IJSRuntime JsRuntime
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.action-panel { margin-bottom: 16px; }
|
|
||||||
.grid-section { margin-top: 12px; }
|
|
||||||
.pager-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 12px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
.page-size-selector {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
}
|
|
||||||
.page-size-label {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.page-size-combo {
|
|
||||||
width: 13ch;
|
|
||||||
min-width: 13ch;
|
|
||||||
max-width: 13ch;
|
|
||||||
}
|
|
||||||
.page-size-combo input {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
.massdata-edit-popup {
|
|
||||||
min-width: 720px;
|
|
||||||
}
|
|
||||||
.band-editor {
|
|
||||||
display: grid;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
.band-controls {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.band-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.band-columns {
|
|
||||||
max-width: 720px;
|
|
||||||
}
|
|
||||||
.loading-container {
|
|
||||||
min-height: 160px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
||||||
{
|
{
|
||||||
@@ -83,12 +20,12 @@ else if (!string.IsNullOrWhiteSpace(infoMessage))
|
|||||||
}
|
}
|
||||||
else if (items.Count == 0)
|
else if (items.Count == 0)
|
||||||
{
|
{
|
||||||
<p>Keine Einträge vorhanden.</p>
|
<p>Keine Einträge vorhanden.</p>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<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>
|
||||||
<DxComboBox Data="@pageSizeOptions"
|
<DxComboBox Data="@pageSizeOptions"
|
||||||
TData="PageSizeOption"
|
TData="PageSizeOption"
|
||||||
TValue="int?"
|
TValue="int?"
|
||||||
@@ -101,9 +38,9 @@ else
|
|||||||
|
|
||||||
<div class="band-editor">
|
<div class="band-editor">
|
||||||
<div class="band-controls">
|
<div class="band-controls">
|
||||||
<DxButton Text="Band hinzufügen" Click="AddBand" />
|
<DxButton Text="Band hinzufügen" Click="AddBand" />
|
||||||
<DxButton Text="Layout speichern" Click="SaveLayoutAsync" Enabled="@CanSaveBandLayout" />
|
<DxButton Text="Layout speichern" Click="SaveLayoutAsync" Enabled="@CanSaveBandLayout" />
|
||||||
<DxButton Text="Band-Layout zurücksetzen" Click="ResetBandLayoutAsync" />
|
<DxButton Text="Layout zurücksetzen" Click="ResetLayoutAsync" />
|
||||||
</div>
|
</div>
|
||||||
@foreach (var band in bandLayout.Bands)
|
@foreach (var band in bandLayout.Bands)
|
||||||
{
|
{
|
||||||
@@ -175,7 +112,9 @@ else
|
|||||||
@RenderColumns()
|
@RenderColumns()
|
||||||
</Columns>
|
</Columns>
|
||||||
<EditFormTemplate Context="editFormContext">
|
<EditFormTemplate Context="editFormContext">
|
||||||
@{ SetEditContext(editFormContext.EditContext); var editModel = (MassDataEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew); }
|
@{
|
||||||
|
SetEditContext(editFormContext.EditContext); var editModel = (MassDataEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew);
|
||||||
|
}
|
||||||
<DxFormLayout ColCount="2">
|
<DxFormLayout ColCount="2">
|
||||||
<DxFormLayoutItem Caption="CustomerName">
|
<DxFormLayoutItem Caption="CustomerName">
|
||||||
<DxTextBox @bind-Text="editModel.CustomerName" Width="100%" />
|
<DxTextBox @bind-Text="editModel.CustomerName" Width="100%" />
|
||||||
@@ -233,13 +172,13 @@ else
|
|||||||
private int? focusedRowKey;
|
private int? focusedRowKey;
|
||||||
private const string LayoutType = "GRID_BANDS";
|
private const string LayoutType = "GRID_BANDS";
|
||||||
private const string LayoutKey = "MassDataGrid";
|
private const string LayoutKey = "MassDataGrid";
|
||||||
private const string LayoutUserStorageKey = "layoutUser";
|
|
||||||
private string? layoutUser;
|
private string? layoutUser;
|
||||||
private BandLayout bandLayout = new();
|
private BandLayout bandLayout = new();
|
||||||
private Dictionary<string, string> columnBandAssignments = new();
|
private Dictionary<string, string> columnBandAssignments = new();
|
||||||
private List<BandOption> bandOptions = new();
|
private List<BandOption> bandOptions = new();
|
||||||
private Dictionary<string, ColumnDefinition> columnLookup = new();
|
private Dictionary<string, ColumnDefinition> columnLookup = new();
|
||||||
private readonly JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web);
|
private bool gridLayoutApplied;
|
||||||
|
|
||||||
private List<ColumnDefinition> columnDefinitions = new()
|
private List<ColumnDefinition> columnDefinitions = new()
|
||||||
{
|
{
|
||||||
new() { FieldName = nameof(MassDataReadDto.Id), Caption = "Id", Width = "90px", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
new() { FieldName = nameof(MassDataReadDto.Id), Caption = "Id", Width = "90px", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
||||||
@@ -251,15 +190,13 @@ else
|
|||||||
new() { FieldName = nameof(MassDataReadDto.ChangedWhen), Caption = "Changed", ReadOnly = true, FilterType = ColumnFilterType.Date }
|
new() { FieldName = nameof(MassDataReadDto.ChangedWhen), Caption = "Changed", ReadOnly = true, FilterType = ColumnFilterType.Date }
|
||||||
};
|
};
|
||||||
|
|
||||||
private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
|
|
||||||
|
|
||||||
private readonly List<PageSizeOption> pageSizeOptions = new()
|
private readonly List<PageSizeOption> pageSizeOptions = new()
|
||||||
{
|
{
|
||||||
new() { Value = 100, Text = "100" },
|
new() { Value = 100, Text = "100" },
|
||||||
new() { Value = 1000, Text = "1.000" },
|
new() { Value = 1000, Text = "1.000" },
|
||||||
new() { Value = 10000, Text = "10.000" },
|
new() { Value = 10000, Text = "10.000" },
|
||||||
new() { Value = 100000, Text = "100.000" },
|
new() { Value = 100000, Text = "100.000" },
|
||||||
new() { Value = null, Text = "Alle" }
|
new() { Value = null, Text = "Alle" }
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly List<ProcedureOption> procedureOptions = new()
|
private readonly List<ProcedureOption> procedureOptions = new()
|
||||||
@@ -267,33 +204,46 @@ else
|
|||||||
new() { Value = 0, Text = "PRMassdata_UpsertByCustomerName" }
|
new() { Value = 0, Text = "PRMassdata_UpsertByCustomerName" }
|
||||||
};
|
};
|
||||||
|
|
||||||
private bool gridLayoutApplied;
|
private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
|
||||||
|
|
||||||
private DevExpress.Blazor.SizeMode _sizeMode = DevExpress.Blazor.SizeMode.Medium;
|
private SizeMode _sizeMode = SizeMode.Medium;
|
||||||
private static readonly List<DevExpress.Blazor.SizeMode> _sizeModes =
|
private static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();
|
||||||
Enum.GetValues<DevExpress.Blazor.SizeMode>().ToList();
|
|
||||||
|
|
||||||
private string FormatSizeText(DevExpress.Blazor.SizeMode size) => size switch
|
private string FormatSizeText(SizeMode size) => size switch
|
||||||
{
|
{
|
||||||
DevExpress.Blazor.SizeMode.Small => "Klein",
|
SizeMode.Small => "Klein",
|
||||||
DevExpress.Blazor.SizeMode.Medium => "Mittel",
|
SizeMode.Medium => "Mittel",
|
||||||
DevExpress.Blazor.SizeMode.Large => "Groß",
|
SizeMode.Large => "Groß",
|
||||||
_ => size.ToString()
|
_ => size.ToString()
|
||||||
};
|
};
|
||||||
|
|
||||||
private void OnSizeChange(DropDownButtonItemClickEventArgs args)
|
private void OnSizeChange(DropDownButtonItemClickEventArgs args)
|
||||||
{
|
{
|
||||||
_sizeMode = Enum.Parse<DevExpress.Blazor.SizeMode>(args.ItemInfo.Id);
|
_sizeMode = Enum.Parse<SizeMode>(args.ItemInfo.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
|
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||||
await EnsureLayoutUserAsync();
|
layoutUser = await BandLayoutService.EnsureLayoutUserAsync();
|
||||||
await LoadBandLayoutAsync();
|
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)
|
||||||
|
{
|
||||||
|
if (!gridLayoutApplied && gridRef != null && bandLayout.GridLayout != null)
|
||||||
|
{
|
||||||
|
gridRef.LoadLayout(bandLayout.GridLayout);
|
||||||
|
gridLayoutApplied = true;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LoadPage(int page)
|
private async Task LoadPage(int page)
|
||||||
{
|
{
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
@@ -304,7 +254,6 @@ else
|
|||||||
var effectivePageSize = pageSize ?? (total == 0 ? 1 : total);
|
var effectivePageSize = pageSize ?? (total == 0 ? 1 : total);
|
||||||
pageCount = Math.Max(1, (int)Math.Ceiling(total / (double)effectivePageSize));
|
pageCount = Math.Max(1, (int)Math.Ceiling(total / (double)effectivePageSize));
|
||||||
pageIndex = Math.Clamp(page, 0, pageCount - 1);
|
pageIndex = Math.Clamp(page, 0, pageCount - 1);
|
||||||
|
|
||||||
var skip = pageSize.HasValue ? pageIndex * pageSize.Value : 0;
|
var skip = pageSize.HasValue ? pageIndex * pageSize.Value : 0;
|
||||||
items = await Api.GetAllAsync(skip, pageSize);
|
items = await Api.GetAllAsync(skip, pageSize);
|
||||||
}
|
}
|
||||||
@@ -320,10 +269,7 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnPageChanged(int index)
|
private async Task OnPageChanged(int index) => await LoadPage(index);
|
||||||
{
|
|
||||||
await LoadPage(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task OnPageSizeChanged(int? size)
|
private async Task OnPageSizeChanged(int? size)
|
||||||
{
|
{
|
||||||
@@ -331,60 +277,15 @@ else
|
|||||||
await LoadPage(0);
|
await LoadPage(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnsureLayoutUserAsync()
|
|
||||||
{
|
|
||||||
layoutUser = await JsRuntime.InvokeAsync<string?>("localStorage.getItem", LayoutUserStorageKey);
|
|
||||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
|
||||||
{
|
|
||||||
layoutUser = Guid.NewGuid().ToString("N");
|
|
||||||
await JsRuntime.InvokeVoidAsync("localStorage.setItem", LayoutUserStorageKey, layoutUser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadBandLayoutAsync()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
|
||||||
{
|
|
||||||
bandLayout = new BandLayout();
|
|
||||||
UpdateBandOptions();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var stored = await LayoutApi.GetAsync(LayoutType, LayoutKey, layoutUser);
|
|
||||||
if (stored != null && !string.IsNullOrWhiteSpace(stored.LayoutData))
|
|
||||||
{
|
|
||||||
var parsed = JsonSerializer.Deserialize<BandLayout>(stored.LayoutData, jsonOptions);
|
|
||||||
bandLayout = NormalizeBandLayout(parsed);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bandLayout = new BandLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
columnBandAssignments = BuildAssignmentsFromLayout(bandLayout);
|
|
||||||
ApplyColumnLayoutFromStorage();
|
|
||||||
UpdateBandOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveLayoutAsync()
|
private async Task SaveLayoutAsync()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
if (string.IsNullOrWhiteSpace(layoutUser))
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CaptureColumnLayoutFromGrid();
|
CaptureColumnLayoutFromGrid();
|
||||||
|
await BandLayoutService.SaveBandLayoutAsync(LayoutType, LayoutKey, layoutUser, bandLayout);
|
||||||
var layoutData = JsonSerializer.Serialize(bandLayout, jsonOptions);
|
|
||||||
await LayoutApi.UpsertAsync(new LayoutDto
|
|
||||||
{
|
|
||||||
LayoutType = LayoutType,
|
|
||||||
LayoutKey = LayoutKey,
|
|
||||||
UserName = layoutUser,
|
|
||||||
LayoutData = layoutData
|
|
||||||
});
|
|
||||||
infoMessage = "Layout gespeichert.";
|
infoMessage = "Layout gespeichert.";
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
}
|
}
|
||||||
@@ -394,42 +295,49 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CaptureColumnLayoutFromGrid()
|
private async Task ResetLayoutAsync()
|
||||||
{
|
|
||||||
if (gridRef == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var layout = gridRef.SaveLayout();
|
|
||||||
bandLayout.GridLayout = layout;
|
|
||||||
|
|
||||||
var orderedColumns = layout.Columns
|
|
||||||
.Where(column => !string.IsNullOrWhiteSpace(column.FieldName))
|
|
||||||
.OrderBy(column => column.VisibleIndex)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
bandLayout.ColumnOrder = orderedColumns
|
|
||||||
.Select(column => column.FieldName)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
bandLayout.ColumnWidths = orderedColumns
|
|
||||||
.Where(column => !string.IsNullOrWhiteSpace(column.Width))
|
|
||||||
.ToDictionary(column => column.FieldName, column => column.Width, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ResetBandLayoutAsync()
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
if (string.IsNullOrWhiteSpace(layoutUser))
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
await LayoutApi.DeleteAsync(LayoutType, LayoutKey, layoutUser);
|
await BandLayoutService.ResetBandLayoutAsync(LayoutType, LayoutKey, layoutUser);
|
||||||
|
|
||||||
bandLayout = new BandLayout();
|
bandLayout = new BandLayout();
|
||||||
columnBandAssignments.Clear();
|
columnBandAssignments.Clear();
|
||||||
UpdateBandOptions();
|
UpdateBandOptions();
|
||||||
infoMessage = "Band-Layout zurückgesetzt.";
|
|
||||||
|
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()
|
private void ApplyColumnLayoutFromStorage()
|
||||||
@@ -437,34 +345,22 @@ else
|
|||||||
foreach (var column in columnDefinitions)
|
foreach (var column in columnDefinitions)
|
||||||
{
|
{
|
||||||
if (bandLayout.ColumnWidths.TryGetValue(column.FieldName, out var width) && !string.IsNullOrWhiteSpace(width))
|
if (bandLayout.ColumnWidths.TryGetValue(column.FieldName, out var width) && !string.IsNullOrWhiteSpace(width))
|
||||||
{
|
|
||||||
column.Width = width;
|
column.Width = width;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||||
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddBand()
|
private void AddBand()
|
||||||
{
|
{
|
||||||
bandLayout.Bands.Add(new BandDefinition
|
bandLayout.Bands.Add(new BandDefinition { Id = Guid.NewGuid().ToString("N"), Caption = "Band" });
|
||||||
{
|
|
||||||
Id = Guid.NewGuid().ToString("N"),
|
|
||||||
Caption = "Band"
|
|
||||||
});
|
|
||||||
UpdateBandOptions();
|
UpdateBandOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveBand(BandDefinition band)
|
private void RemoveBand(BandDefinition band)
|
||||||
{
|
{
|
||||||
bandLayout.Bands.Remove(band);
|
bandLayout.Bands.Remove(band);
|
||||||
var removedColumns = columnBandAssignments.Where(pair => pair.Value == band.Id)
|
foreach (var key in columnBandAssignments.Where(p => p.Value == band.Id).Select(p => p.Key).ToList())
|
||||||
.Select(pair => pair.Key)
|
columnBandAssignments.Remove(key);
|
||||||
.ToList();
|
|
||||||
foreach (var column in removedColumns)
|
|
||||||
{
|
|
||||||
columnBandAssignments.Remove(column);
|
|
||||||
}
|
|
||||||
UpdateBandOptions();
|
UpdateBandOptions();
|
||||||
SyncBandsFromAssignments();
|
SyncBandsFromAssignments();
|
||||||
}
|
}
|
||||||
@@ -478,77 +374,31 @@ else
|
|||||||
private void UpdateColumnBand(string fieldName, string? bandId)
|
private void UpdateColumnBand(string fieldName, string? bandId)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(bandId))
|
if (string.IsNullOrWhiteSpace(bandId))
|
||||||
{
|
|
||||||
columnBandAssignments.Remove(fieldName);
|
columnBandAssignments.Remove(fieldName);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
columnBandAssignments[fieldName] = bandId;
|
columnBandAssignments[fieldName] = bandId;
|
||||||
}
|
|
||||||
|
|
||||||
SyncBandsFromAssignments();
|
SyncBandsFromAssignments();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetColumnBand(string fieldName)
|
private string GetColumnBand(string fieldName)
|
||||||
{
|
=> columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
|
||||||
return columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SyncBandsFromAssignments()
|
private void SyncBandsFromAssignments()
|
||||||
{
|
{
|
||||||
foreach (var band in bandLayout.Bands)
|
foreach (var band in bandLayout.Bands)
|
||||||
{
|
{
|
||||||
band.Columns = columnDefinitions
|
band.Columns = columnDefinitions
|
||||||
.Where(column => columnBandAssignments.TryGetValue(column.FieldName, out var bandId) && bandId == band.Id)
|
.Where(c => columnBandAssignments.TryGetValue(c.FieldName, out var id) && id == band.Id)
|
||||||
.Select(column => column.FieldName)
|
.Select(c => c.FieldName)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateBandOptions()
|
private void UpdateBandOptions()
|
||||||
{
|
{
|
||||||
bandOptions = new List<BandOption> { new() { Id = string.Empty, Caption = "Ohne Band" } };
|
bandOptions = new List<BandOption> { new() { Id = string.Empty, Caption = "Ohne Band" } };
|
||||||
bandOptions.AddRange(bandLayout.Bands.Select(band => new BandOption { Id = band.Id, Caption = band.Caption }));
|
bandOptions.AddRange(bandLayout.Bands.Select(b => new BandOption { Id = b.Id, Caption = b.Caption }));
|
||||||
}
|
|
||||||
|
|
||||||
private Dictionary<string, string> BuildAssignmentsFromLayout(BandLayout layout)
|
|
||||||
{
|
|
||||||
var assignments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
foreach (var band in layout.Bands)
|
|
||||||
{
|
|
||||||
foreach (var column in band.Columns)
|
|
||||||
{
|
|
||||||
assignments[column] = band.Id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return assignments;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BandLayout NormalizeBandLayout(BandLayout? layout)
|
|
||||||
{
|
|
||||||
layout ??= new BandLayout();
|
|
||||||
layout.Bands ??= new List<BandDefinition>();
|
|
||||||
layout.ColumnOrder ??= new List<string>();
|
|
||||||
layout.ColumnWidths ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
foreach (var band in layout.Bands)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(band.Id))
|
|
||||||
{
|
|
||||||
band.Id = Guid.NewGuid().ToString("N");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(band.Caption))
|
|
||||||
{
|
|
||||||
band.Caption = "Band";
|
|
||||||
}
|
|
||||||
|
|
||||||
band.Columns = band.Columns?.Where(columnLookup.ContainsKey).ToList() ?? new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return layout;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private RenderFragment RenderColumns() => builder =>
|
private RenderFragment RenderColumns() => builder =>
|
||||||
@@ -558,18 +408,13 @@ else
|
|||||||
builder.AddAttribute(seq++, "Width", "120px");
|
builder.AddAttribute(seq++, "Width", "120px");
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
|
|
||||||
var grouped = bandLayout.Bands.SelectMany(band => band.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
var grouped = bandLayout.Bands.SelectMany(b => b.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
foreach (var column in columnDefinitions.Where(column => !grouped.Contains(column.FieldName)))
|
foreach (var column in columnDefinitions.Where(c => !grouped.Contains(c.FieldName)))
|
||||||
{
|
|
||||||
BuildDataColumn(builder, ref seq, column);
|
BuildDataColumn(builder, ref seq, column);
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var band in bandLayout.Bands)
|
foreach (var band in bandLayout.Bands)
|
||||||
{
|
{
|
||||||
if (band.Columns.Count == 0)
|
if (band.Columns.Count == 0) continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.OpenComponent<DxGridBandColumn>(seq++);
|
builder.OpenComponent<DxGridBandColumn>(seq++);
|
||||||
builder.AddAttribute(seq++, "Caption", band.Caption);
|
builder.AddAttribute(seq++, "Caption", band.Caption);
|
||||||
@@ -579,9 +424,7 @@ else
|
|||||||
foreach (var columnName in band.Columns)
|
foreach (var columnName in band.Columns)
|
||||||
{
|
{
|
||||||
if (columnLookup.TryGetValue(columnName, out var column))
|
if (columnLookup.TryGetValue(columnName, out var column))
|
||||||
{
|
|
||||||
BuildDataColumn(bandBuilder, ref bandSeq, column);
|
BuildDataColumn(bandBuilder, ref bandSeq, column);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
@@ -594,35 +437,19 @@ else
|
|||||||
builder.AddAttribute(seq++, "FieldName", column.FieldName);
|
builder.AddAttribute(seq++, "FieldName", column.FieldName);
|
||||||
builder.AddAttribute(seq++, "Caption", column.Caption);
|
builder.AddAttribute(seq++, "Caption", column.Caption);
|
||||||
if (!string.IsNullOrWhiteSpace(column.Width))
|
if (!string.IsNullOrWhiteSpace(column.Width))
|
||||||
{
|
|
||||||
builder.AddAttribute(seq++, "Width", column.Width);
|
builder.AddAttribute(seq++, "Width", column.Width);
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(column.DisplayFormat))
|
if (!string.IsNullOrWhiteSpace(column.DisplayFormat))
|
||||||
{
|
|
||||||
builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat);
|
builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat);
|
||||||
}
|
|
||||||
|
|
||||||
if (column.ReadOnly)
|
if (column.ReadOnly)
|
||||||
{
|
|
||||||
builder.AddAttribute(seq++, "ReadOnly", true);
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
}
|
|
||||||
|
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetEditContext(EditContext context)
|
private void SetEditContext(EditContext context)
|
||||||
{
|
{
|
||||||
if (editContext == context)
|
if (editContext == context) return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editContext != null)
|
if (editContext != null)
|
||||||
{
|
|
||||||
editContext.OnFieldChanged -= OnEditFieldChanged;
|
editContext.OnFieldChanged -= OnEditFieldChanged;
|
||||||
}
|
|
||||||
|
|
||||||
editContext = context;
|
editContext = context;
|
||||||
validationMessageStore = new ValidationMessageStore(editContext);
|
validationMessageStore = new ValidationMessageStore(editContext);
|
||||||
editContext.OnFieldChanged += OnEditFieldChanged;
|
editContext.OnFieldChanged += OnEditFieldChanged;
|
||||||
@@ -630,10 +457,7 @@ else
|
|||||||
|
|
||||||
private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
|
private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (validationMessageStore == null || editContext == null)
|
if (validationMessageStore == null || editContext == null) return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.UpdateProcedure))
|
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.UpdateProcedure))
|
||||||
{
|
{
|
||||||
@@ -644,16 +468,12 @@ else
|
|||||||
|
|
||||||
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.CustomerName))
|
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.CustomerName))
|
||||||
{
|
{
|
||||||
var field = new FieldIdentifier(editContext.Model, nameof(MassDataEditModel.CustomerName));
|
validationMessageStore.Clear(new FieldIdentifier(editContext.Model, nameof(MassDataEditModel.CustomerName)));
|
||||||
validationMessageStore.Clear(field);
|
|
||||||
editContext.NotifyValidationStateChanged();
|
editContext.NotifyValidationStateChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetPopupHeaderText(bool isNew)
|
private void SetPopupHeaderText(bool isNew) => popupHeaderText = isNew ? "Neu" : "Edit";
|
||||||
{
|
|
||||||
popupHeaderText = isNew ? "Neu" : "Edit";
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
|
private async Task OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -683,14 +503,13 @@ else
|
|||||||
{
|
{
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
infoMessage = null;
|
infoMessage = null;
|
||||||
|
|
||||||
validationMessageStore?.Clear();
|
validationMessageStore?.Clear();
|
||||||
editContext?.NotifyValidationStateChanged();
|
editContext?.NotifyValidationStateChanged();
|
||||||
|
|
||||||
var editModel = (MassDataEditModel)e.EditModel;
|
var editModel = (MassDataEditModel)e.EditModel;
|
||||||
if (!decimal.TryParse(editModel.AmountText, out var amount))
|
if (!decimal.TryParse(editModel.AmountText, out var amount))
|
||||||
{
|
{
|
||||||
AddValidationError(editModel, nameof(MassDataEditModel.AmountText), "Amount ist ungültig.");
|
AddValidationError(editModel, nameof(MassDataEditModel.AmountText), "Amount ist ungültig.");
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -730,72 +549,19 @@ else
|
|||||||
|
|
||||||
private void AddValidationError(MassDataEditModel editModel, string fieldName, string message)
|
private void AddValidationError(MassDataEditModel editModel, string fieldName, string message)
|
||||||
{
|
{
|
||||||
if (editContext == null || validationMessageStore == null)
|
if (editContext == null || validationMessageStore == null) return;
|
||||||
{
|
validationMessageStore.Add(new FieldIdentifier(editModel, fieldName), message);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var field = new FieldIdentifier(editModel, fieldName);
|
|
||||||
validationMessageStore.Add(field, message);
|
|
||||||
editContext.NotifyValidationStateChanged();
|
editContext.NotifyValidationStateChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task OnDataItemDeleting(GridDataItemDeletingEventArgs e)
|
private Task OnDataItemDeleting(GridDataItemDeletingEventArgs e)
|
||||||
{
|
{
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
infoMessage = "Löschen ist aktuell noch nicht verfügbar.";
|
infoMessage = "Löschen ist aktuell noch nicht verfügbar.";
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if (!gridLayoutApplied && gridRef != null && bandLayout.GridLayout != null)
|
|
||||||
{
|
|
||||||
gridRef.LoadLayout(bandLayout.GridLayout);
|
|
||||||
gridLayoutApplied = true;
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class BandLayout
|
|
||||||
{
|
|
||||||
public List<BandDefinition> Bands { get; set; } = new();
|
|
||||||
public List<string> ColumnOrder { get; set; } = new();
|
|
||||||
public Dictionary<string, string?> ColumnWidths { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
|
||||||
public GridPersistentLayout? GridLayout { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class BandDefinition
|
|
||||||
{
|
|
||||||
public string Id { get; set; } = string.Empty;
|
|
||||||
public string Caption { get; set; } = string.Empty;
|
|
||||||
public List<string> Columns { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class BandOption
|
|
||||||
{
|
|
||||||
public string Id { get; set; } = string.Empty;
|
|
||||||
public string Caption { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class ColumnDefinition
|
|
||||||
{
|
|
||||||
public string FieldName { get; init; } = string.Empty;
|
|
||||||
public string Caption { get; init; } = string.Empty;
|
|
||||||
public string? Width { get; set; }
|
|
||||||
public string? DisplayFormat { get; init; }
|
|
||||||
public bool ReadOnly { get; init; }
|
|
||||||
public ColumnFilterType FilterType { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum ColumnFilterType
|
|
||||||
{
|
|
||||||
Text,
|
|
||||||
Bool,
|
|
||||||
Date
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class MassDataEditModel
|
private sealed class MassDataEditModel
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
@@ -814,21 +580,9 @@ else
|
|||||||
public string Text { get; set; } = string.Empty;
|
public string Text { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class BoolFilterOption
|
|
||||||
{
|
|
||||||
public bool? Value { get; set; }
|
|
||||||
public string Text { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class PageSizeOption
|
private sealed class PageSizeOption
|
||||||
{
|
{
|
||||||
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 sealed class FilterOperatorOption
|
|
||||||
{
|
|
||||||
public string Value { get; set; } = string.Empty;
|
|
||||||
public string Text { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
3
DbFirst.BlazorWebApp/Components/MassDataGrid.razor.css
Normal file
3
DbFirst.BlazorWebApp/Components/MassDataGrid.razor.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.massdata-edit-popup {
|
||||||
|
min-width: 720px;
|
||||||
|
}
|
||||||
@@ -5,46 +5,6 @@
|
|||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@inject DashboardApiClient DashboardApi
|
@inject DashboardApiClient DashboardApi
|
||||||
|
|
||||||
<style>
|
|
||||||
.dashboard-shell {
|
|
||||||
display: flex;
|
|
||||||
gap: 0;
|
|
||||||
min-height: 800px;
|
|
||||||
border: 1px solid #e6e6e6;
|
|
||||||
border-radius: 6px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
.dashboard-nav {
|
|
||||||
width: 220px;
|
|
||||||
border-right: 1px solid #e6e6e6;
|
|
||||||
background: #fafafa;
|
|
||||||
}
|
|
||||||
.dashboard-nav-title {
|
|
||||||
padding: 0.75rem 1rem 0.5rem;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
color: #6c757d;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.dashboard-nav-link {
|
|
||||||
display: block;
|
|
||||||
padding: 0.55rem 1rem;
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.dashboard-nav-link.active {
|
|
||||||
background: #e9ecef;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.dashboard-content {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<PageTitle>Dashboards</PageTitle>
|
<PageTitle>Dashboards</PageTitle>
|
||||||
|
|
||||||
<div class="dashboard-shell">
|
<div class="dashboard-shell">
|
||||||
|
|||||||
42
DbFirst.BlazorWebApp/Components/Pages/Dashboard.razor.css
Normal file
42
DbFirst.BlazorWebApp/Components/Pages/Dashboard.razor.css
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
.dashboard-shell {
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
min-height: 800px;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-nav {
|
||||||
|
width: 220px;
|
||||||
|
border-right: 1px solid #e6e6e6;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-nav-title {
|
||||||
|
padding: 0.75rem 1rem 0.5rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: #6c757d;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-nav-link {
|
||||||
|
display: block;
|
||||||
|
padding: 0.55rem 1rem;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-nav-link.active {
|
||||||
|
background: #e9ecef;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
@using System.Net.Http
|
@using System.Net.Http
|
||||||
@using System.Net.Http.Json
|
@using System.Net.Http.Json
|
||||||
|
@using System.Text.Json
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
|
@using Microsoft.AspNetCore.Components.Rendering
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
@using Microsoft.AspNetCore.Components.Web
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||||
@@ -10,8 +12,9 @@
|
|||||||
@using DbFirst.BlazorWebApp
|
@using DbFirst.BlazorWebApp
|
||||||
@using DbFirst.BlazorWebApp.Components
|
@using DbFirst.BlazorWebApp.Components
|
||||||
@using DbFirst.BlazorWebApp.Models
|
@using DbFirst.BlazorWebApp.Models
|
||||||
|
@using DbFirst.BlazorWebApp.Models.Grid
|
||||||
@using DbFirst.BlazorWebApp.Services
|
@using DbFirst.BlazorWebApp.Services
|
||||||
@using DevExpress.Blazor
|
@using DevExpress.Blazor
|
||||||
@using DevExpress.DashboardBlazor
|
@using DevExpress.DashboardBlazor
|
||||||
@using DevExpress.DashboardWeb
|
@using DevExpress.DashboardWeb
|
||||||
@using DbFirst.BlazorWebApp
|
@using DevExpress.Data.Filtering
|
||||||
@@ -11,9 +11,6 @@
|
|||||||
<PackageReference Include="DevExpress.Blazor.Dashboard" Version="25.2.3" />
|
<PackageReference Include="DevExpress.Blazor.Dashboard" Version="25.2.3" />
|
||||||
<PackageReference Include="DevExpress.Blazor.Themes" Version="25.2.3" />
|
<PackageReference Include="DevExpress.Blazor.Themes" Version="25.2.3" />
|
||||||
<PackageReference Include="DevExpress.Blazor.Themes.Fluent" Version="25.2.3" />
|
<PackageReference Include="DevExpress.Blazor.Themes.Fluent" Version="25.2.3" />
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="DevExpress.Blazor" Version="25.2.3" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.22" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.22" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
43
DbFirst.BlazorWebApp/Models/Grid/BandLayoutModels.cs
Normal file
43
DbFirst.BlazorWebApp/Models/Grid/BandLayoutModels.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using DevExpress.Blazor;
|
||||||
|
|
||||||
|
namespace DbFirst.BlazorWebApp.Models.Grid
|
||||||
|
{
|
||||||
|
public class BandLayout
|
||||||
|
{
|
||||||
|
public List<BandDefinition> Bands { get; set; } = new();
|
||||||
|
public List<string> ColumnOrder { get; set; } = new();
|
||||||
|
public Dictionary<string, string?> ColumnWidths { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
public GridPersistentLayout? GridLayout { get; set; }
|
||||||
|
public SizeMode SizeMode { get; set; } = SizeMode.Medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BandDefinition
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
public string Caption { get; set; } = string.Empty;
|
||||||
|
public List<string> Columns { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BandOption
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
public string Caption { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ColumnDefinition
|
||||||
|
{
|
||||||
|
public string FieldName { get; init; } = string.Empty;
|
||||||
|
public string Caption { get; init; } = string.Empty;
|
||||||
|
public string? Width { get; set; }
|
||||||
|
public string? DisplayFormat { get; init; }
|
||||||
|
public bool ReadOnly { get; init; }
|
||||||
|
public ColumnFilterType FilterType { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ColumnFilterType
|
||||||
|
{
|
||||||
|
Text,
|
||||||
|
Bool,
|
||||||
|
Date
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ builder.Services.AddRazorComponents()
|
|||||||
|
|
||||||
builder.Services.AddDevExpressBlazor(options => options.BootstrapVersion = BootstrapVersion.v5);
|
builder.Services.AddDevExpressBlazor(options => options.BootstrapVersion = BootstrapVersion.v5);
|
||||||
builder.Services.AddScoped<ThemeState>();
|
builder.Services.AddScoped<ThemeState>();
|
||||||
|
builder.Services.AddScoped<BandLayoutService>();
|
||||||
|
|
||||||
var apiBaseUrl = builder.Configuration["ApiBaseUrl"];
|
var apiBaseUrl = builder.Configuration["ApiBaseUrl"];
|
||||||
if (!string.IsNullOrWhiteSpace(apiBaseUrl))
|
if (!string.IsNullOrWhiteSpace(apiBaseUrl))
|
||||||
|
|||||||
103
DbFirst.BlazorWebApp/Services/BandLayoutService.cs
Normal file
103
DbFirst.BlazorWebApp/Services/BandLayoutService.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
using DbFirst.BlazorWebApp.Models;
|
||||||
|
using DbFirst.BlazorWebApp.Models.Grid;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace DbFirst.BlazorWebApp.Services
|
||||||
|
{
|
||||||
|
public class BandLayoutService(LayoutApiClient layoutApi, IJSRuntime jsRuntime)
|
||||||
|
{
|
||||||
|
private const string LayoutUserStorageKey = "layoutUser";
|
||||||
|
private readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);
|
||||||
|
|
||||||
|
public async Task<string> EnsureLayoutUserAsync()
|
||||||
|
{
|
||||||
|
var layoutUser = await jsRuntime.InvokeAsync<string?>("localStorage.getItem", LayoutUserStorageKey);
|
||||||
|
if (string.IsNullOrWhiteSpace(layoutUser))
|
||||||
|
{
|
||||||
|
layoutUser = Guid.NewGuid().ToString("N");
|
||||||
|
await jsRuntime.InvokeVoidAsync("localStorage.setItem", LayoutUserStorageKey, layoutUser);
|
||||||
|
}
|
||||||
|
return layoutUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BandLayout> LoadBandLayoutAsync(
|
||||||
|
string layoutType,
|
||||||
|
string layoutKey,
|
||||||
|
string layoutUser,
|
||||||
|
Dictionary<string, ColumnDefinition> columnLookup)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(layoutUser))
|
||||||
|
return new BandLayout();
|
||||||
|
|
||||||
|
var stored = await layoutApi.GetAsync(layoutType, layoutKey, layoutUser);
|
||||||
|
if (stored != null && !string.IsNullOrWhiteSpace(stored.LayoutData))
|
||||||
|
{
|
||||||
|
var parsed = JsonSerializer.Deserialize<BandLayout>(stored.LayoutData, _jsonOptions);
|
||||||
|
return NormalizeBandLayout(parsed, columnLookup);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BandLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveBandLayoutAsync(
|
||||||
|
string layoutType,
|
||||||
|
string layoutKey,
|
||||||
|
string layoutUser,
|
||||||
|
BandLayout bandLayout)
|
||||||
|
{
|
||||||
|
var layoutData = JsonSerializer.Serialize(bandLayout, _jsonOptions);
|
||||||
|
await layoutApi.UpsertAsync(new LayoutDto
|
||||||
|
{
|
||||||
|
LayoutType = layoutType,
|
||||||
|
LayoutKey = layoutKey,
|
||||||
|
UserName = layoutUser,
|
||||||
|
LayoutData = layoutData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ResetBandLayoutAsync(
|
||||||
|
string layoutType,
|
||||||
|
string layoutKey,
|
||||||
|
string layoutUser)
|
||||||
|
{
|
||||||
|
await layoutApi.DeleteAsync(layoutType, layoutKey, layoutUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, string> BuildAssignmentsFromLayout(BandLayout layout)
|
||||||
|
{
|
||||||
|
var assignments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var band in layout.Bands)
|
||||||
|
{
|
||||||
|
foreach (var column in band.Columns)
|
||||||
|
{
|
||||||
|
assignments[column] = band.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return assignments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BandLayout NormalizeBandLayout(
|
||||||
|
BandLayout? layout,
|
||||||
|
Dictionary<string, ColumnDefinition> columnLookup)
|
||||||
|
{
|
||||||
|
layout ??= new BandLayout();
|
||||||
|
layout.Bands ??= new List<BandDefinition>();
|
||||||
|
layout.ColumnOrder ??= new List<string>();
|
||||||
|
layout.ColumnWidths ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (var band in layout.Bands)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(band.Id))
|
||||||
|
band.Id = Guid.NewGuid().ToString("N");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(band.Caption))
|
||||||
|
band.Caption = "Band";
|
||||||
|
|
||||||
|
band.Columns = band.Columns?.Where(columnLookup.ContainsKey).ToList() ?? new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ a, .btn-link {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
@@ -51,7 +51,7 @@ h1:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.blazor-error-boundary {
|
.blazor-error-boundary {
|
||||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA9NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDg2IDY2LjAxODMgMjYzLjU4NiA2Ni4wMTgzWk0yNjMuNTc2IDg2LjA1NDdDMjYxLjA0OSA4Ni4wNTQ3IDI1OS43ODUgODcuMzAwNSAxNTEuMDIyIDg5Ljc5MjEgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDg2IDY2LjAxODMgMjYzLjU4NiA2Ni4wMTgzWk0yNjMuNTc2IDg2LjA1NDdDMjYxLjA0OSA4Ni4wNTQ3IDI1OS43ODUgODcuMzAwNSAyNTkuNzg2IDg5Ljc5MjEgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||||
padding: 1rem 1rem 1rem 3.7rem;
|
padding: 1rem 1rem 1rem 3.7rem;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
@@ -63,3 +63,76 @@ h1:focus {
|
|||||||
.darker-border-checkbox.form-check-input {
|
.darker-border-checkbox.form-check-input {
|
||||||
border-color: #929292;
|
border-color: #929292;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Grid Band-Editor */
|
||||||
|
.band-editor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.band-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.band-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.band-columns {
|
||||||
|
max-width: 720px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-section {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MassData-spezifisch */
|
||||||
|
.page-size-selector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-size-label {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-size-combo {
|
||||||
|
width: 13ch;
|
||||||
|
min-width: 13ch;
|
||||||
|
max-width: 13ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-size-combo input {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pager-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lade-Spinner */
|
||||||
|
.loading-container {
|
||||||
|
min-height: 160px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user