diff --git a/DbFirst.BlazorWebApp/Components/CatalogsGrid.razor b/DbFirst.BlazorWebApp/Components/CatalogsGrid.razor index 0400eff..c425798 100644 --- a/DbFirst.BlazorWebApp/Components/CatalogsGrid.razor +++ b/DbFirst.BlazorWebApp/Components/CatalogsGrid.razor @@ -1,6 +1,5 @@ @inject CatalogApiClient Api -@inject LayoutApiClient LayoutApi -@inject IJSRuntime JsRuntime +@inject BandLayoutService BandLayoutService @if (!string.IsNullOrWhiteSpace(errorMessage)) { @@ -101,7 +100,9 @@ else @RenderColumns() - @{ SetEditContext(editFormContext.EditContext); var editModel = (CatalogEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew); } + @{ + SetEditContext(editFormContext.EditContext); var editModel = (CatalogEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew); + } @@ -143,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 columnBandAssignments = new(); private List bandOptions = new(); private Dictionary columnLookup = new(); - private readonly JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web); + private bool gridLayoutApplied; + private List columnDefinitions = new() { new() { FieldName = nameof(CatalogReadDto.Guid), Caption = "Id", Width = "140px", FilterType = ColumnFilterType.Text }, @@ -168,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 _sizeModes = - Enum.GetValues().ToList(); + private SizeMode _sizeMode = SizeMode.Medium; + private static readonly List _sizeModes = Enum.GetValues().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(args.ItemInfo.Id); + _sizeMode = Enum.Parse(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(); } @@ -205,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; @@ -290,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 { 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(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(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(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(); @@ -320,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; } @@ -344,7 +494,6 @@ else e.Cancel = true; return; } - infoMessage = "Katalog aktualisiert."; focusedRowKey = editModel.Guid; } @@ -360,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; } @@ -391,9 +530,7 @@ else { errorMessage = null; infoMessage = null; - var item = (CatalogReadDto)e.DataItem; - try { var deleted = await Api.DeleteAsync(item.Guid); @@ -403,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("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(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 { 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(); - layout.ColumnOrder ??= new List(); - layout.ColumnWidths ??= new Dictionary(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(); - } - - return layout; - } - - private Dictionary BuildAssignmentsFromLayout(BandLayout layout) - { - var assignments = new Dictionary(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(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(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(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 Bands { get; set; } = new(); - public List ColumnOrder { get; set; } = new(); - public Dictionary 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 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; } @@ -762,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; - } -} +} \ No newline at end of file diff --git a/DbFirst.BlazorWebApp/Components/MassDataGrid.razor b/DbFirst.BlazorWebApp/Components/MassDataGrid.razor index d2f281f..acbccae 100644 --- a/DbFirst.BlazorWebApp/Components/MassDataGrid.razor +++ b/DbFirst.BlazorWebApp/Components/MassDataGrid.razor @@ -1,6 +1,5 @@ @inject MassDataApiClient Api -@inject LayoutApiClient LayoutApi -@inject IJSRuntime JsRuntime +@inject BandLayoutService BandLayoutService @if (!string.IsNullOrWhiteSpace(errorMessage)) { @@ -113,7 +112,9 @@ else @RenderColumns() - @{ SetEditContext(editFormContext.EditContext); var editModel = (MassDataEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew); } + @{ + SetEditContext(editFormContext.EditContext); var editModel = (MassDataEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew); + } @@ -171,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 columnBandAssignments = new(); private List bandOptions = new(); private Dictionary columnLookup = new(); - private readonly JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web); + private bool gridLayoutApplied; + private List columnDefinitions = new() { new() { FieldName = nameof(MassDataReadDto.Id), Caption = "Id", Width = "90px", ReadOnly = true, FilterType = ColumnFilterType.Text }, @@ -189,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 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 procedureOptions = new() @@ -205,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 _sizeModes = - Enum.GetValues().ToList(); + private SizeMode _sizeMode = SizeMode.Medium; + private static readonly List _sizeModes = Enum.GetValues().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(args.ItemInfo.Id); + _sizeMode = Enum.Parse(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; @@ -242,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); } @@ -258,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) { @@ -269,61 +277,15 @@ else await LoadPage(0); } - private async Task EnsureLayoutUserAsync() - { - layoutUser = await JsRuntime.InvokeAsync("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(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; } @@ -333,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(); @@ -377,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(); } @@ -433,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 { new() { Id = string.Empty, Caption = "Ohne Band" } }; - bandOptions.AddRange(bandLayout.Bands.Select(band => new BandOption { Id = band.Id, Caption = band.Caption })); - } - - private Dictionary BuildAssignmentsFromLayout(BandLayout layout) - { - var assignments = new Dictionary(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(); - layout.ColumnOrder ??= new List(); - layout.ColumnWidths ??= new Dictionary(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(); - } - - return layout; + bandOptions.AddRange(bandLayout.Bands.Select(b => new BandOption { Id = b.Id, Caption = b.Caption })); } private RenderFragment RenderColumns() => builder => @@ -513,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(seq++); builder.AddAttribute(seq++, "Caption", band.Caption); @@ -534,9 +424,7 @@ else foreach (var columnName in band.Columns) { if (columnLookup.TryGetValue(columnName, out var column)) - { BuildDataColumn(bandBuilder, ref bandSeq, column); - } } })); builder.CloseComponent(); @@ -549,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; @@ -585,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)) { @@ -599,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) { @@ -638,7 +503,6 @@ else { errorMessage = null; infoMessage = null; - validationMessageStore?.Clear(); editContext?.NotifyValidationStateChanged(); @@ -685,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(); } @@ -703,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 Bands { get; set; } = new(); - public List ColumnOrder { get; set; } = new(); - public Dictionary 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 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; } @@ -770,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; - } -} +} \ No newline at end of file diff --git a/DbFirst.BlazorWebApp/Components/_Imports.razor b/DbFirst.BlazorWebApp/Components/_Imports.razor index 01e886f..8c2fedc 100644 --- a/DbFirst.BlazorWebApp/Components/_Imports.razor +++ b/DbFirst.BlazorWebApp/Components/_Imports.razor @@ -1,5 +1,6 @@ @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 @@ -11,10 +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 DevExpress.Data.Filtering -@using DbFirst.BlazorWebApp -@using System.Text.Json \ No newline at end of file +@using DevExpress.Data.Filtering \ No newline at end of file diff --git a/DbFirst.BlazorWebApp/Models/Grid/BandLayoutModels.cs b/DbFirst.BlazorWebApp/Models/Grid/BandLayoutModels.cs new file mode 100644 index 0000000..468ca1a --- /dev/null +++ b/DbFirst.BlazorWebApp/Models/Grid/BandLayoutModels.cs @@ -0,0 +1,43 @@ +using DevExpress.Blazor; + +namespace DbFirst.BlazorWebApp.Models.Grid +{ + public class BandLayout + { + public List Bands { get; set; } = new(); + public List ColumnOrder { get; set; } = new(); + public Dictionary 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 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 + } +} \ No newline at end of file diff --git a/DbFirst.BlazorWebApp/Program.cs b/DbFirst.BlazorWebApp/Program.cs index 86dc0f1..e06db92 100644 --- a/DbFirst.BlazorWebApp/Program.cs +++ b/DbFirst.BlazorWebApp/Program.cs @@ -10,6 +10,7 @@ builder.Services.AddRazorComponents() builder.Services.AddDevExpressBlazor(options => options.BootstrapVersion = BootstrapVersion.v5); builder.Services.AddScoped(); +builder.Services.AddScoped(); var apiBaseUrl = builder.Configuration["ApiBaseUrl"]; if (!string.IsNullOrWhiteSpace(apiBaseUrl)) diff --git a/DbFirst.BlazorWebApp/Services/BandLayoutService.cs b/DbFirst.BlazorWebApp/Services/BandLayoutService.cs new file mode 100644 index 0000000..4e1ae96 --- /dev/null +++ b/DbFirst.BlazorWebApp/Services/BandLayoutService.cs @@ -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 EnsureLayoutUserAsync() + { + var layoutUser = await jsRuntime.InvokeAsync("localStorage.getItem", LayoutUserStorageKey); + if (string.IsNullOrWhiteSpace(layoutUser)) + { + layoutUser = Guid.NewGuid().ToString("N"); + await jsRuntime.InvokeVoidAsync("localStorage.setItem", LayoutUserStorageKey, layoutUser); + } + return layoutUser; + } + + public async Task LoadBandLayoutAsync( + string layoutType, + string layoutKey, + string layoutUser, + Dictionary 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(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 BuildAssignmentsFromLayout(BandLayout layout) + { + var assignments = new Dictionary(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 columnLookup) + { + layout ??= new BandLayout(); + layout.Bands ??= new List(); + layout.ColumnOrder ??= new List(); + layout.ColumnWidths ??= new Dictionary(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(); + } + + return layout; + } + } +} \ No newline at end of file