Compare commits
5 Commits
789066a214
...
dc74d21426
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc74d21426 | ||
|
|
566c3b3276 | ||
|
|
ac84866abe | ||
|
|
d9ce4a5dca | ||
|
|
13617dde87 |
@@ -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 LayoutApiClient LayoutApi
|
||||
@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>
|
||||
@inject BandLayoutService BandLayoutService
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
||||
{
|
||||
@@ -155,7 +100,9 @@ else
|
||||
@RenderColumns()
|
||||
</Columns>
|
||||
<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">
|
||||
<DxFormLayoutItem Caption="Titel">
|
||||
<DxTextBox @bind-Text="editModel.CatTitle" Width="100%" />
|
||||
@@ -197,13 +144,13 @@ else
|
||||
private string popupHeaderText = "Edit";
|
||||
private const string LayoutType = "GRID_BANDS";
|
||||
private const string LayoutKey = "CatalogsGrid";
|
||||
private const string LayoutUserStorageKey = "layoutUser";
|
||||
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 readonly JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web);
|
||||
private bool gridLayoutApplied;
|
||||
|
||||
private List<ColumnDefinition> columnDefinitions = new()
|
||||
{
|
||||
new() { FieldName = nameof(CatalogReadDto.Guid), Caption = "Id", Width = "140px", FilterType = ColumnFilterType.Text },
|
||||
@@ -222,30 +169,32 @@ else
|
||||
};
|
||||
|
||||
private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
|
||||
private bool gridLayoutApplied;
|
||||
|
||||
private DevExpress.Blazor.SizeMode _sizeMode = DevExpress.Blazor.SizeMode.Medium;
|
||||
private static readonly List<DevExpress.Blazor.SizeMode> _sizeModes =
|
||||
Enum.GetValues<DevExpress.Blazor.SizeMode>().ToList();
|
||||
private SizeMode _sizeMode = SizeMode.Medium;
|
||||
private static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();
|
||||
|
||||
private string FormatSizeText(DevExpress.Blazor.SizeMode size) => size switch
|
||||
private string FormatSizeText(SizeMode size) => size switch
|
||||
{
|
||||
DevExpress.Blazor.SizeMode.Small => "Klein",
|
||||
DevExpress.Blazor.SizeMode.Medium => "Mittel",
|
||||
DevExpress.Blazor.SizeMode.Large => "Groß",
|
||||
SizeMode.Small => "Klein",
|
||||
SizeMode.Medium => "Mittel",
|
||||
SizeMode.Large => "Groß",
|
||||
_ => size.ToString()
|
||||
};
|
||||
|
||||
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()
|
||||
{
|
||||
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||
await EnsureLayoutUserAsync();
|
||||
await LoadBandLayoutAsync();
|
||||
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();
|
||||
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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
errorMessage = null;
|
||||
infoMessage = null;
|
||||
|
||||
validationMessageStore?.Clear();
|
||||
editContext?.NotifyValidationStateChanged();
|
||||
|
||||
@@ -374,18 +476,12 @@ else
|
||||
if (!created.Success || created.Value == null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(created.Error))
|
||||
{
|
||||
AddValidationError(editModel, nameof(CatalogEditModel.CatTitle), created.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = "Anlegen fehlgeschlagen.";
|
||||
}
|
||||
|
||||
e.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
infoMessage = "Katalog angelegt.";
|
||||
focusedRowKey = created.Value.Guid;
|
||||
}
|
||||
@@ -398,7 +494,6 @@ else
|
||||
e.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
infoMessage = "Katalog aktualisiert.";
|
||||
focusedRowKey = editModel.Guid;
|
||||
}
|
||||
@@ -414,30 +509,20 @@ else
|
||||
|
||||
private void AddValidationError(CatalogEditModel editModel, string fieldName, string message)
|
||||
{
|
||||
if (editContext == null || validationMessageStore == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var field = new FieldIdentifier(editModel, fieldName);
|
||||
validationMessageStore.Add(field, message);
|
||||
if (editContext == null || validationMessageStore == null) return;
|
||||
validationMessageStore.Add(new FieldIdentifier(editModel, fieldName), message);
|
||||
editContext.NotifyValidationStateChanged();
|
||||
}
|
||||
|
||||
private bool ValidateEditModel(CatalogEditModel editModel, bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isNew) return true;
|
||||
if (editModel.UpdateProcedure == 0 &&
|
||||
!string.Equals(editModel.CatTitle, editModel.OriginalCatTitle, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
AddValidationError(editModel, nameof(CatalogEditModel.CatTitle), "Titel kann nicht geändert werden.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -445,9 +530,7 @@ else
|
||||
{
|
||||
errorMessage = null;
|
||||
infoMessage = null;
|
||||
|
||||
var item = (CatalogReadDto)e.DataItem;
|
||||
|
||||
try
|
||||
{
|
||||
var deleted = await Api.DeleteAsync(item.Guid);
|
||||
@@ -457,350 +540,16 @@ else
|
||||
e.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
infoMessage = "Katalog gelöscht.";
|
||||
await LoadCatalogs();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = $"Fehler beim Löschen: {ex.Message}";
|
||||
errorMessage = $"Fehler beim Löschen: {ex.Message}";
|
||||
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;
|
||||
bandLayout.SizeMode = _sizeMode;
|
||||
|
||||
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();
|
||||
_sizeMode = bandLayout.SizeMode;
|
||||
UpdateBandOptions();
|
||||
}
|
||||
|
||||
private async Task ResetLayoutAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await LayoutApi.DeleteAsync(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 = DevExpress.Blazor.SizeMode.Medium;
|
||||
|
||||
if (gridRef != null)
|
||||
gridRef.LoadLayout(new GridPersistentLayout());
|
||||
gridLayoutApplied = false;
|
||||
|
||||
infoMessage = "Layout zur\u00fcckgesetzt.";
|
||||
errorMessage = null;
|
||||
}
|
||||
|
||||
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; }
|
||||
public DevExpress.Blazor.SizeMode SizeMode { get; set; } = DevExpress.Blazor.SizeMode.Medium;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
public int Guid { get; set; }
|
||||
@@ -816,10 +565,4 @@ else
|
||||
public int Value { get; set; }
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
49
DbFirst.BlazorWebApp/Components/CatalogsGrid.razor.css
Normal file
49
DbFirst.BlazorWebApp/Components/CatalogsGrid.razor.css
Normal file
@@ -0,0 +1,49 @@
|
||||
.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;
|
||||
}
|
||||
@@ -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 LayoutApiClient LayoutApi
|
||||
@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>
|
||||
@inject BandLayoutService BandLayoutService
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
||||
{
|
||||
@@ -175,7 +112,9 @@ else
|
||||
@RenderColumns()
|
||||
</Columns>
|
||||
<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">
|
||||
<DxFormLayoutItem Caption="CustomerName">
|
||||
<DxTextBox @bind-Text="editModel.CustomerName" Width="100%" />
|
||||
@@ -233,13 +172,13 @@ else
|
||||
private int? focusedRowKey;
|
||||
private const string LayoutType = "GRID_BANDS";
|
||||
private const string LayoutKey = "MassDataGrid";
|
||||
private const string LayoutUserStorageKey = "layoutUser";
|
||||
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 readonly JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web);
|
||||
private bool gridLayoutApplied;
|
||||
|
||||
private List<ColumnDefinition> columnDefinitions = new()
|
||||
{
|
||||
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 }
|
||||
};
|
||||
|
||||
private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
|
||||
|
||||
private readonly List<PageSizeOption> pageSizeOptions = new()
|
||||
{
|
||||
new() { Value = 100, Text = "100" },
|
||||
new() { Value = 1000, Text = "1.000" },
|
||||
new() { Value = 10000, Text = "10.000" },
|
||||
new() { Value = 100, Text = "100" },
|
||||
new() { Value = 1000, Text = "1.000" },
|
||||
new() { Value = 10000, Text = "10.000" },
|
||||
new() { Value = 100000, Text = "100.000" },
|
||||
new() { Value = null, Text = "Alle" }
|
||||
new() { Value = null, Text = "Alle" }
|
||||
};
|
||||
|
||||
private readonly List<ProcedureOption> procedureOptions = new()
|
||||
@@ -267,33 +204,46 @@ else
|
||||
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 static readonly List<DevExpress.Blazor.SizeMode> _sizeModes =
|
||||
Enum.GetValues<DevExpress.Blazor.SizeMode>().ToList();
|
||||
private SizeMode _sizeMode = SizeMode.Medium;
|
||||
private static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();
|
||||
|
||||
private string FormatSizeText(DevExpress.Blazor.SizeMode size) => size switch
|
||||
private string FormatSizeText(SizeMode size) => size switch
|
||||
{
|
||||
DevExpress.Blazor.SizeMode.Small => "Klein",
|
||||
DevExpress.Blazor.SizeMode.Medium => "Mittel",
|
||||
DevExpress.Blazor.SizeMode.Large => "Groß",
|
||||
SizeMode.Small => "Klein",
|
||||
SizeMode.Medium => "Mittel",
|
||||
SizeMode.Large => "Groß",
|
||||
_ => size.ToString()
|
||||
};
|
||||
|
||||
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()
|
||||
{
|
||||
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||
await EnsureLayoutUserAsync();
|
||||
await LoadBandLayoutAsync();
|
||||
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();
|
||||
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)
|
||||
{
|
||||
isLoading = true;
|
||||
@@ -304,7 +254,6 @@ else
|
||||
var effectivePageSize = pageSize ?? (total == 0 ? 1 : total);
|
||||
pageCount = Math.Max(1, (int)Math.Ceiling(total / (double)effectivePageSize));
|
||||
pageIndex = Math.Clamp(page, 0, pageCount - 1);
|
||||
|
||||
var skip = pageSize.HasValue ? pageIndex * pageSize.Value : 0;
|
||||
items = await Api.GetAllAsync(skip, pageSize);
|
||||
}
|
||||
@@ -320,10 +269,7 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnPageChanged(int index)
|
||||
{
|
||||
await LoadPage(index);
|
||||
}
|
||||
private async Task OnPageChanged(int index) => await LoadPage(index);
|
||||
|
||||
private async Task OnPageSizeChanged(int? size)
|
||||
{
|
||||
@@ -331,61 +277,15 @@ else
|
||||
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();
|
||||
_sizeMode = bandLayout.SizeMode;
|
||||
UpdateBandOptions();
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
await BandLayoutService.SaveBandLayoutAsync(LayoutType, LayoutKey, layoutUser, bandLayout);
|
||||
infoMessage = "Layout gespeichert.";
|
||||
errorMessage = null;
|
||||
}
|
||||
@@ -395,41 +295,12 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private void CaptureColumnLayoutFromGrid()
|
||||
{
|
||||
if (gridRef == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var layout = gridRef.SaveLayout();
|
||||
bandLayout.GridLayout = layout;
|
||||
bandLayout.SizeMode = _sizeMode;
|
||||
|
||||
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);
|
||||
|
||||
bandLayout.SizeMode = _sizeMode;
|
||||
}
|
||||
|
||||
private async Task ResetLayoutAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await LayoutApi.DeleteAsync(LayoutType, LayoutKey, layoutUser);
|
||||
await BandLayoutService.ResetBandLayoutAsync(LayoutType, LayoutKey, layoutUser);
|
||||
|
||||
bandLayout = new BandLayout();
|
||||
columnBandAssignments.Clear();
|
||||
@@ -439,49 +310,57 @@ else
|
||||
column.Width = null;
|
||||
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
_sizeMode = DevExpress.Blazor.SizeMode.Medium;
|
||||
_sizeMode = SizeMode.Medium;
|
||||
|
||||
if (gridRef != null)
|
||||
gridRef.LoadLayout(new GridPersistentLayout());
|
||||
gridLayoutApplied = false;
|
||||
|
||||
infoMessage = "Layout zur\u00fcckgesetzt.";
|
||||
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(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private void AddBand()
|
||||
{
|
||||
bandLayout.Bands.Add(new BandDefinition
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N"),
|
||||
Caption = "Band"
|
||||
});
|
||||
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);
|
||||
}
|
||||
foreach (var key in columnBandAssignments.Where(p => p.Value == band.Id).Select(p => p.Key).ToList())
|
||||
columnBandAssignments.Remove(key);
|
||||
UpdateBandOptions();
|
||||
SyncBandsFromAssignments();
|
||||
}
|
||||
@@ -495,77 +374,31 @@ else
|
||||
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;
|
||||
}
|
||||
=> 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)
|
||||
.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(band => new BandOption { Id = band.Id, Caption = band.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;
|
||||
bandOptions.AddRange(bandLayout.Bands.Select(b => new BandOption { Id = b.Id, Caption = b.Caption }));
|
||||
}
|
||||
|
||||
private RenderFragment RenderColumns() => builder =>
|
||||
@@ -575,18 +408,13 @@ else
|
||||
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)))
|
||||
{
|
||||
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;
|
||||
}
|
||||
if (band.Columns.Count == 0) continue;
|
||||
|
||||
builder.OpenComponent<DxGridBandColumn>(seq++);
|
||||
builder.AddAttribute(seq++, "Caption", band.Caption);
|
||||
@@ -596,9 +424,7 @@ else
|
||||
foreach (var columnName in band.Columns)
|
||||
{
|
||||
if (columnLookup.TryGetValue(columnName, out var column))
|
||||
{
|
||||
BuildDataColumn(bandBuilder, ref bandSeq, column);
|
||||
}
|
||||
}
|
||||
}));
|
||||
builder.CloseComponent();
|
||||
@@ -611,35 +437,19 @@ else
|
||||
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 == context) return;
|
||||
if (editContext != null)
|
||||
{
|
||||
editContext.OnFieldChanged -= OnEditFieldChanged;
|
||||
}
|
||||
|
||||
editContext = context;
|
||||
validationMessageStore = new ValidationMessageStore(editContext);
|
||||
editContext.OnFieldChanged += OnEditFieldChanged;
|
||||
@@ -647,10 +457,7 @@ else
|
||||
|
||||
private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
|
||||
{
|
||||
if (validationMessageStore == null || editContext == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (validationMessageStore == null || editContext == null) return;
|
||||
|
||||
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.UpdateProcedure))
|
||||
{
|
||||
@@ -661,16 +468,12 @@ else
|
||||
|
||||
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.CustomerName))
|
||||
{
|
||||
var field = new FieldIdentifier(editContext.Model, nameof(MassDataEditModel.CustomerName));
|
||||
validationMessageStore.Clear(field);
|
||||
validationMessageStore.Clear(new FieldIdentifier(editContext.Model, nameof(MassDataEditModel.CustomerName)));
|
||||
editContext.NotifyValidationStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPopupHeaderText(bool isNew)
|
||||
{
|
||||
popupHeaderText = isNew ? "Neu" : "Edit";
|
||||
}
|
||||
private void SetPopupHeaderText(bool isNew) => popupHeaderText = isNew ? "Neu" : "Edit";
|
||||
|
||||
private async Task OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
|
||||
{
|
||||
@@ -700,7 +503,6 @@ else
|
||||
{
|
||||
errorMessage = null;
|
||||
infoMessage = null;
|
||||
|
||||
validationMessageStore?.Clear();
|
||||
editContext?.NotifyValidationStateChanged();
|
||||
|
||||
@@ -747,13 +549,8 @@ else
|
||||
|
||||
private void AddValidationError(MassDataEditModel editModel, string fieldName, string message)
|
||||
{
|
||||
if (editContext == null || validationMessageStore == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var field = new FieldIdentifier(editModel, fieldName);
|
||||
validationMessageStore.Add(field, message);
|
||||
if (editContext == null || validationMessageStore == null) return;
|
||||
validationMessageStore.Add(new FieldIdentifier(editModel, fieldName), message);
|
||||
editContext.NotifyValidationStateChanged();
|
||||
}
|
||||
|
||||
@@ -765,55 +562,6 @@ else
|
||||
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; }
|
||||
public DevExpress.Blazor.SizeMode SizeMode { get; set; } = DevExpress.Blazor.SizeMode.Medium;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
public int Id { get; set; }
|
||||
@@ -832,21 +580,9 @@ else
|
||||
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
|
||||
{
|
||||
public int? Value { get; set; }
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
DbFirst.BlazorWebApp/Components/MassDataGrid.razor.css
Normal file
57
DbFirst.BlazorWebApp/Components/MassDataGrid.razor.css
Normal file
@@ -0,0 +1,57 @@
|
||||
.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;
|
||||
}
|
||||
@@ -5,46 +5,6 @@
|
||||
@inject NavigationManager Navigation
|
||||
@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>
|
||||
|
||||
<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.Json
|
||||
@using System.Text.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Rendering
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@@ -10,8 +12,9 @@
|
||||
@using DbFirst.BlazorWebApp
|
||||
@using DbFirst.BlazorWebApp.Components
|
||||
@using DbFirst.BlazorWebApp.Models
|
||||
@using DbFirst.BlazorWebApp.Models.Grid
|
||||
@using DbFirst.BlazorWebApp.Services
|
||||
@using DevExpress.Blazor
|
||||
@using DevExpress.DashboardBlazor
|
||||
@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.Themes" 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" />
|
||||
</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.AddScoped<ThemeState>();
|
||||
builder.Services.AddScoped<BandLayoutService>();
|
||||
|
||||
var apiBaseUrl = builder.Configuration["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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user