Introduced font size adjustment for grids via dropdown toolbar and CSS variable. Added JavaScript for dynamic font size changes. Replaced German umlauts with HTML entities for proper rendering. Refactored grid code and improved error/info message display. Enhances accessibility and user experience.
810 lines
26 KiB
Plaintext
810 lines
26 KiB
Plaintext
@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>
|
|
|
|
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
|
{
|
|
<div class="alert alert-danger" role="alert">@errorMessage</div>
|
|
}
|
|
else if (!string.IsNullOrWhiteSpace(infoMessage))
|
|
{
|
|
<div class="alert alert-success" role="alert">@infoMessage</div>
|
|
}
|
|
|
|
@if (!hasLoaded || isLoading)
|
|
{
|
|
<div class="loading-container">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Lade...</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
else if (items.Count == 0)
|
|
{
|
|
<p>Keine Einträge vorhanden.</p>
|
|
}
|
|
else
|
|
{
|
|
<div class="band-editor">
|
|
<div class="band-controls">
|
|
<DxButton Text="Band hinzufügen" Click="AddBand" />
|
|
<DxButton Text="Layout speichern" Click="SaveLayoutAsync" Enabled="@CanSaveBandLayout" />
|
|
<DxButton Text="Band-Layout zurücksetzen" Click="ResetBandLayoutAsync" />
|
|
</div>
|
|
@foreach (var band in bandLayout.Bands)
|
|
{
|
|
<div class="band-row">
|
|
<DxTextBox Text="@band.Caption" TextChanged="@(value => UpdateBandCaption(band, value))" />
|
|
<DxButton Text="Entfernen" Click="@(() => RemoveBand(band))" />
|
|
</div>
|
|
}
|
|
<DxFormLayout CssClass="band-columns" ColCount="2">
|
|
@foreach (var column in columnDefinitions)
|
|
{
|
|
<DxFormLayoutItem Caption="@column.Caption">
|
|
<DxComboBox Data="@bandOptions"
|
|
TData="BandOption"
|
|
TValue="string"
|
|
TextFieldName="Caption"
|
|
ValueFieldName="Id"
|
|
Value="@GetColumnBand(column.FieldName)"
|
|
ValueChanged="@(value => UpdateColumnBand(column.FieldName, value))"
|
|
Width="100%" />
|
|
</DxFormLayoutItem>
|
|
}
|
|
</DxFormLayout>
|
|
</div>
|
|
|
|
<div class="grid-section">
|
|
<DxGrid Data="@items"
|
|
TItem="CatalogReadDto"
|
|
KeyFieldName="@nameof(CatalogReadDto.Guid)"
|
|
SizeMode="@_sizeMode"
|
|
ShowGroupPanel="true"
|
|
ShowGroupedColumns="true"
|
|
AllowGroup="true"
|
|
FilterMenuButtonDisplayMode="GridFilterMenuButtonDisplayMode.Always"
|
|
AllowColumnResize="true"
|
|
ColumnResizeMode="GridColumnResizeMode.ColumnsContainer"
|
|
AllowColumnReorder="true"
|
|
PageSize="10"
|
|
CssClass="mb-4 catalog-grid"
|
|
EditMode="GridEditMode.PopupEditForm"
|
|
PopupEditFormCssClass="catalog-edit-popup"
|
|
PopupEditFormHeaderText="@popupHeaderText"
|
|
CustomizeEditModel="OnCustomizeEditModel"
|
|
EditModelSaving="OnEditModelSaving"
|
|
DataItemDeleting="OnDataItemDeleting"
|
|
FocusedRowEnabled="true"
|
|
@bind-FocusedRowKey="focusedRowKey"
|
|
@ref="gridRef">
|
|
<ToolbarTemplate>
|
|
<DxToolbar>
|
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Right">
|
|
<Template Context="_">
|
|
<DxDropDownButton Text="@FormatSizeText(_sizeMode)"
|
|
RenderStyle="ButtonRenderStyle.Secondary"
|
|
RenderStyleMode="ButtonRenderStyleMode.Text"
|
|
ItemClick="OnSizeChange">
|
|
<Items>
|
|
@foreach (var size in _sizeModes)
|
|
{
|
|
<DxDropDownButtonItem Text="@FormatSizeText(size)" Id="@size.ToString()" />
|
|
}
|
|
</Items>
|
|
</DxDropDownButton>
|
|
</Template>
|
|
</DxToolbarItem>
|
|
</DxToolbar>
|
|
</ToolbarTemplate>
|
|
<Columns>
|
|
@RenderColumns()
|
|
</Columns>
|
|
<EditFormTemplate Context="editFormContext">
|
|
@{ SetEditContext(editFormContext.EditContext); var editModel = (CatalogEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew); }
|
|
<DxFormLayout ColCount="2">
|
|
<DxFormLayoutItem Caption="Titel">
|
|
<DxTextBox @bind-Text="editModel.CatTitle" Width="100%" />
|
|
</DxFormLayoutItem>
|
|
<DxFormLayoutItem Caption="Kennung">
|
|
<DxTextBox @bind-Text="editModel.CatString" Width="100%" />
|
|
</DxFormLayoutItem>
|
|
@if (!editModel.IsNew)
|
|
{
|
|
<DxFormLayoutItem Caption="Update-Prozedur">
|
|
<DxComboBox Data="@procedureOptions"
|
|
TData="ProcedureOption"
|
|
TValue="int"
|
|
TextFieldName="Text"
|
|
ValueFieldName="Value"
|
|
@bind-Value="editModel.UpdateProcedure"
|
|
Width="100%" />
|
|
</DxFormLayoutItem>
|
|
}
|
|
<DxFormLayoutItem ColSpanMd="12">
|
|
<ValidationSummary />
|
|
</DxFormLayoutItem>
|
|
</DxFormLayout>
|
|
</EditFormTemplate>
|
|
</DxGrid>
|
|
</div>
|
|
}
|
|
|
|
@code {
|
|
private List<CatalogReadDto> items = new();
|
|
private bool isLoading;
|
|
private bool hasLoaded;
|
|
private string? errorMessage;
|
|
private string? infoMessage;
|
|
private EditContext? editContext;
|
|
private ValidationMessageStore? validationMessageStore;
|
|
private IGrid? gridRef;
|
|
private int? focusedRowKey;
|
|
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 List<ColumnDefinition> columnDefinitions = new()
|
|
{
|
|
new() { FieldName = nameof(CatalogReadDto.Guid), Caption = "Id", Width = "140px", FilterType = ColumnFilterType.Text },
|
|
new() { FieldName = nameof(CatalogReadDto.CatTitle), Caption = "Titel", FilterType = ColumnFilterType.Text },
|
|
new() { FieldName = nameof(CatalogReadDto.CatString), Caption = "String", FilterType = ColumnFilterType.Text },
|
|
new() { FieldName = nameof(CatalogReadDto.AddedWho), Caption = "Angelegt von", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
|
new() { FieldName = nameof(CatalogReadDto.AddedWhen), Caption = "Angelegt am", ReadOnly = true, FilterType = ColumnFilterType.Date },
|
|
new() { FieldName = nameof(CatalogReadDto.ChangedWho), Caption = "Geändert von", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
|
new() { FieldName = nameof(CatalogReadDto.ChangedWhen), Caption = "Geändert am", ReadOnly = true, FilterType = ColumnFilterType.Date }
|
|
};
|
|
|
|
private readonly List<ProcedureOption> procedureOptions = new()
|
|
{
|
|
new() { Value = 0, Text = "PRTBMY_CATALOG_UPDATE" },
|
|
new() { Value = 1, Text = "PRTBMY_CATALOG_SAVE" }
|
|
};
|
|
|
|
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 string FormatSizeText(DevExpress.Blazor.SizeMode size) => size switch
|
|
{
|
|
DevExpress.Blazor.SizeMode.Small => "Klein",
|
|
DevExpress.Blazor.SizeMode.Medium => "Mittel",
|
|
DevExpress.Blazor.SizeMode.Large => "Groß",
|
|
_ => size.ToString()
|
|
};
|
|
|
|
private void OnSizeChange(DropDownButtonItemClickEventArgs args)
|
|
{
|
|
_sizeMode = Enum.Parse<DevExpress.Blazor.SizeMode>(args.ItemInfo.Id);
|
|
}
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
|
|
await EnsureLayoutUserAsync();
|
|
await LoadBandLayoutAsync();
|
|
await LoadCatalogs();
|
|
}
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (!gridLayoutApplied && gridRef != null && bandLayout.GridLayout != null)
|
|
{
|
|
gridRef.LoadLayout(bandLayout.GridLayout);
|
|
gridLayoutApplied = true;
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
}
|
|
|
|
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;
|
|
errorMessage = null;
|
|
try
|
|
{
|
|
items = await Api.GetAllAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = $"Kataloge konnten nicht geladen werden: {ex.Message}";
|
|
}
|
|
finally
|
|
{
|
|
isLoading = false;
|
|
hasLoaded = true;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private async Task OnEditModelSaving(GridEditModelSavingEventArgs e)
|
|
{
|
|
errorMessage = null;
|
|
infoMessage = null;
|
|
|
|
validationMessageStore?.Clear();
|
|
editContext?.NotifyValidationStateChanged();
|
|
|
|
var editModel = (CatalogEditModel)e.EditModel;
|
|
if (!ValidateEditModel(editModel, e.IsNew))
|
|
{
|
|
e.Cancel = true;
|
|
return;
|
|
}
|
|
|
|
var dto = new CatalogWriteDto
|
|
{
|
|
CatTitle = editModel.CatTitle,
|
|
CatString = editModel.CatString,
|
|
UpdateProcedure = editModel.UpdateProcedure
|
|
};
|
|
|
|
try
|
|
{
|
|
if (e.IsNew)
|
|
{
|
|
var created = await Api.CreateAsync(dto);
|
|
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;
|
|
}
|
|
else
|
|
{
|
|
var updated = await Api.UpdateAsync(editModel.Guid, dto);
|
|
if (!updated.Success)
|
|
{
|
|
errorMessage = updated.Error ?? "Aktualisierung fehlgeschlagen.";
|
|
e.Cancel = true;
|
|
return;
|
|
}
|
|
|
|
infoMessage = "Katalog aktualisiert.";
|
|
focusedRowKey = editModel.Guid;
|
|
}
|
|
|
|
await LoadCatalogs();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = $"Fehler beim Speichern: {ex.Message}";
|
|
e.Cancel = true;
|
|
}
|
|
}
|
|
|
|
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);
|
|
editContext.NotifyValidationStateChanged();
|
|
}
|
|
|
|
private bool ValidateEditModel(CatalogEditModel editModel, bool isNew)
|
|
{
|
|
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;
|
|
}
|
|
|
|
private async Task OnDataItemDeleting(GridDataItemDeletingEventArgs e)
|
|
{
|
|
errorMessage = null;
|
|
infoMessage = null;
|
|
|
|
var item = (CatalogReadDto)e.DataItem;
|
|
|
|
try
|
|
{
|
|
var deleted = await Api.DeleteAsync(item.Guid);
|
|
if (!deleted.Success)
|
|
{
|
|
errorMessage = deleted.Error ?? "Löschen fehlgeschlagen.";
|
|
e.Cancel = true;
|
|
return;
|
|
}
|
|
|
|
infoMessage = "Katalog gelöscht.";
|
|
await LoadCatalogs();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
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;
|
|
|
|
var orderedColumns = layout.Columns
|
|
.Where(column => !string.IsNullOrWhiteSpace(column.FieldName))
|
|
.OrderBy(column => column.VisibleIndex)
|
|
.ToList();
|
|
|
|
bandLayout.ColumnOrder = orderedColumns
|
|
.Select(column => column.FieldName)
|
|
.ToList();
|
|
|
|
bandLayout.ColumnWidths = orderedColumns
|
|
.Where(column => !string.IsNullOrWhiteSpace(column.Width))
|
|
.ToDictionary(column => column.FieldName, column => column.Width, StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private async Task LoadBandLayoutAsync()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(layoutUser))
|
|
{
|
|
bandLayout = new BandLayout();
|
|
UpdateBandOptions();
|
|
return;
|
|
}
|
|
|
|
var stored = await LayoutApi.GetAsync(LayoutType, LayoutKey, layoutUser);
|
|
if (stored != null && !string.IsNullOrWhiteSpace(stored.LayoutData))
|
|
{
|
|
var parsed = JsonSerializer.Deserialize<BandLayout>(stored.LayoutData, jsonOptions);
|
|
bandLayout = NormalizeBandLayout(parsed);
|
|
}
|
|
else
|
|
{
|
|
bandLayout = new BandLayout();
|
|
}
|
|
|
|
columnBandAssignments = BuildAssignmentsFromLayout(bandLayout);
|
|
ApplyColumnLayoutFromStorage();
|
|
UpdateBandOptions();
|
|
}
|
|
|
|
private async Task ResetBandLayoutAsync()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(layoutUser))
|
|
{
|
|
return;
|
|
}
|
|
|
|
await LayoutApi.DeleteAsync(LayoutType, LayoutKey, layoutUser);
|
|
bandLayout = new BandLayout();
|
|
columnBandAssignments.Clear();
|
|
UpdateBandOptions();
|
|
infoMessage = "Band-Layout zurückgesetzt.";
|
|
}
|
|
|
|
private void ApplyColumnLayoutFromStorage()
|
|
{
|
|
foreach (var column in columnDefinitions)
|
|
{
|
|
if (bandLayout.ColumnWidths.TryGetValue(column.FieldName, out var width) && !string.IsNullOrWhiteSpace(width))
|
|
{
|
|
column.Width = width;
|
|
}
|
|
}
|
|
|
|
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private void AddBand()
|
|
{
|
|
bandLayout.Bands.Add(new BandDefinition
|
|
{
|
|
Id = Guid.NewGuid().ToString("N"),
|
|
Caption = "Band"
|
|
});
|
|
UpdateBandOptions();
|
|
}
|
|
|
|
private void RemoveBand(BandDefinition band)
|
|
{
|
|
bandLayout.Bands.Remove(band);
|
|
var removedColumns = columnBandAssignments.Where(pair => pair.Value == band.Id)
|
|
.Select(pair => pair.Key)
|
|
.ToList();
|
|
foreach (var column in removedColumns)
|
|
{
|
|
columnBandAssignments.Remove(column);
|
|
}
|
|
UpdateBandOptions();
|
|
SyncBandsFromAssignments();
|
|
}
|
|
|
|
private void UpdateBandCaption(BandDefinition band, string value)
|
|
{
|
|
band.Caption = value;
|
|
UpdateBandOptions();
|
|
}
|
|
|
|
private void UpdateColumnBand(string fieldName, string? bandId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(bandId))
|
|
{
|
|
columnBandAssignments.Remove(fieldName);
|
|
}
|
|
else
|
|
{
|
|
columnBandAssignments[fieldName] = bandId;
|
|
}
|
|
|
|
SyncBandsFromAssignments();
|
|
}
|
|
|
|
private string GetColumnBand(string fieldName)
|
|
{
|
|
return columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
|
|
}
|
|
|
|
private void SyncBandsFromAssignments()
|
|
{
|
|
foreach (var band in bandLayout.Bands)
|
|
{
|
|
band.Columns = columnDefinitions
|
|
.Where(column => columnBandAssignments.TryGetValue(column.FieldName, out var bandId) && bandId == band.Id)
|
|
.Select(column => column.FieldName)
|
|
.ToList();
|
|
}
|
|
|
|
StateHasChanged();
|
|
}
|
|
|
|
private void UpdateBandOptions()
|
|
{
|
|
bandOptions = new List<BandOption> { new() { Id = string.Empty, Caption = "Ohne Band" } };
|
|
bandOptions.AddRange(bandLayout.Bands.Select(band => new BandOption { Id = band.Id, Caption = band.Caption }));
|
|
}
|
|
|
|
private BandLayout NormalizeBandLayout(BandLayout? layout)
|
|
{
|
|
layout ??= new BandLayout();
|
|
layout.Bands ??= new List<BandDefinition>();
|
|
layout.ColumnOrder ??= new List<string>();
|
|
layout.ColumnWidths ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var band in layout.Bands)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(band.Id))
|
|
{
|
|
band.Id = Guid.NewGuid().ToString("N");
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(band.Caption))
|
|
{
|
|
band.Caption = "Band";
|
|
}
|
|
|
|
band.Columns = band.Columns?.Where(columnLookup.ContainsKey).ToList() ?? new List<string>();
|
|
}
|
|
|
|
return layout;
|
|
}
|
|
|
|
private Dictionary<string, string> BuildAssignmentsFromLayout(BandLayout layout)
|
|
{
|
|
var assignments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var band in layout.Bands)
|
|
{
|
|
foreach (var column in band.Columns)
|
|
{
|
|
assignments[column] = band.Id;
|
|
}
|
|
}
|
|
|
|
return assignments;
|
|
}
|
|
|
|
private RenderFragment RenderColumns() => builder =>
|
|
{
|
|
var seq = 0;
|
|
builder.OpenComponent<DxGridCommandColumn>(seq++);
|
|
builder.AddAttribute(seq++, "Width", "120px");
|
|
builder.CloseComponent();
|
|
|
|
var grouped = bandLayout.Bands.SelectMany(band => band.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var column in columnDefinitions.Where(column => !grouped.Contains(column.FieldName)))
|
|
{
|
|
BuildDataColumn(builder, ref seq, column);
|
|
}
|
|
|
|
foreach (var band in bandLayout.Bands)
|
|
{
|
|
if (band.Columns.Count == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
builder.OpenComponent<DxGridBandColumn>(seq++);
|
|
builder.AddAttribute(seq++, "Caption", band.Caption);
|
|
builder.AddAttribute(seq++, "Columns", (RenderFragment)(bandBuilder =>
|
|
{
|
|
var bandSeq = 0;
|
|
foreach (var columnName in band.Columns)
|
|
{
|
|
if (columnLookup.TryGetValue(columnName, out var column))
|
|
{
|
|
BuildDataColumn(bandBuilder, ref bandSeq, column);
|
|
}
|
|
}
|
|
}));
|
|
builder.CloseComponent();
|
|
}
|
|
};
|
|
|
|
private void BuildDataColumn(RenderTreeBuilder builder, ref int seq, ColumnDefinition column)
|
|
{
|
|
builder.OpenComponent<DxGridDataColumn>(seq++);
|
|
builder.AddAttribute(seq++, "FieldName", column.FieldName);
|
|
builder.AddAttribute(seq++, "Caption", column.Caption);
|
|
if (!string.IsNullOrWhiteSpace(column.Width))
|
|
{
|
|
builder.AddAttribute(seq++, "Width", column.Width);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(column.DisplayFormat))
|
|
{
|
|
builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat);
|
|
}
|
|
|
|
if (column.ReadOnly)
|
|
{
|
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
|
}
|
|
|
|
builder.CloseComponent();
|
|
}
|
|
|
|
private sealed class BandLayout
|
|
{
|
|
public List<BandDefinition> Bands { get; set; } = new();
|
|
public List<string> ColumnOrder { get; set; } = new();
|
|
public Dictionary<string, string?> ColumnWidths { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
|
public GridPersistentLayout? GridLayout { get; set; }
|
|
}
|
|
|
|
private sealed class BandDefinition
|
|
{
|
|
public string Id { get; set; } = string.Empty;
|
|
public string Caption { get; set; } = string.Empty;
|
|
public List<string> Columns { get; set; } = new();
|
|
}
|
|
|
|
private sealed class BandOption
|
|
{
|
|
public string Id { get; set; } = string.Empty;
|
|
public string Caption { get; set; } = string.Empty;
|
|
}
|
|
|
|
private sealed class ColumnDefinition
|
|
{
|
|
public string FieldName { get; init; } = string.Empty;
|
|
public string Caption { get; init; } = string.Empty;
|
|
public string? Width { get; set; }
|
|
public string? DisplayFormat { get; init; }
|
|
public bool ReadOnly { get; init; }
|
|
public ColumnFilterType FilterType { get; init; }
|
|
}
|
|
|
|
private enum ColumnFilterType
|
|
{
|
|
Text,
|
|
Date
|
|
}
|
|
|
|
private sealed class CatalogEditModel
|
|
{
|
|
public int Guid { get; set; }
|
|
public string CatTitle { get; set; } = string.Empty;
|
|
public string CatString { get; set; } = string.Empty;
|
|
public int UpdateProcedure { get; set; }
|
|
public string OriginalCatTitle { get; set; } = string.Empty;
|
|
public bool IsNew { get; set; }
|
|
}
|
|
|
|
private sealed class ProcedureOption
|
|
{
|
|
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;
|
|
}
|
|
}
|