Added ShowGroupPanel, ShowGroupedColumns, and AllowGroup to DxGrid in CatalogsGrid.razor and MassDataGrid.razor for enhanced data grouping. Also set PagerVisible="false" in MassDataGrid.razor to hide pager controls.
834 lines
27 KiB
Plaintext
834 lines
27 KiB
Plaintext
@using System.Text.Json
|
|
@using Microsoft.AspNetCore.Components
|
|
@using Microsoft.AspNetCore.Components.Rendering
|
|
@using Microsoft.AspNetCore.Components.Forms
|
|
@using DevExpress.Blazor
|
|
@inject CatalogApiClient Api
|
|
@inject LayoutApiClient LayoutApi
|
|
@inject IJSRuntime JsRuntime
|
|
|
|
<style>
|
|
.action-panel { margin-bottom: 16px; }
|
|
.grid-section { margin-top: 12px; }
|
|
.catalog-grid .dxbl-grid-sort-asc,
|
|
.catalog-grid .dxbl-grid-sort-desc {
|
|
display: none;
|
|
}
|
|
.catalog-grid th.dxbl-grid-header-sortable {
|
|
position: relative;
|
|
padding-right: 1.5rem;
|
|
}
|
|
.catalog-grid th.dxbl-grid-header-sortable::before,
|
|
.catalog-grid th.dxbl-grid-header-sortable::after {
|
|
content: "";
|
|
position: absolute;
|
|
right: 0.45rem;
|
|
width: 0.7rem;
|
|
height: 0.7rem;
|
|
background-repeat: no-repeat;
|
|
background-size: 0.7rem 0.7rem;
|
|
opacity: 0.35;
|
|
pointer-events: none;
|
|
}
|
|
.catalog-grid th.dxbl-grid-header-sortable::before {
|
|
top: 38%;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4.957 10.999a1 1 0 0 1-.821-1.571l2.633-3.785a1.5 1.5 0 0 1 2.462 0l2.633 3.785a1 1 0 0 1-.821 1.57H4.957Z' fill='%23888888'/%3E%3C/svg%3E");
|
|
}
|
|
.catalog-grid th.dxbl-grid-header-sortable::after {
|
|
top: 58%;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4.957 5a1 1 0 0 0-.821 1.571l2.633 3.784a1.5 1.5 0 0 0 2.462 0l2.633-3.784A1 1 0 0 0 11.043 5H4.957Z' fill='%23888888'/%3E%3C/svg%3E");
|
|
}
|
|
.catalog-grid th.dxbl-grid-header-sortable[aria-sort="ascending"]::after {
|
|
opacity: 0;
|
|
}
|
|
.catalog-grid th.dxbl-grid-header-sortable[aria-sort="descending"]::before {
|
|
opacity: 0;
|
|
}
|
|
.catalog-grid .filter-search-input input {
|
|
padding-right: 1.75rem;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M9.309 10.016a4.5 4.5 0 1 1 .707-.707l3.838 3.837a.5.5 0 0 1-.708.708L9.31 10.016ZM10 6.5a3.5 3.5 0 1 0-7 0 3.5 3.5 0 0 0 7 0Z' fill='%23666666'/%3E%3C/svg%3E");
|
|
background-repeat: no-repeat;
|
|
background-position: right 0.5rem center;
|
|
background-size: 0.9rem;
|
|
}
|
|
.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;
|
|
}
|
|
</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 (isLoading)
|
|
{
|
|
<p><em>Lade Daten...</em></p>
|
|
}
|
|
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)"
|
|
ShowGroupPanel="true"
|
|
ShowGroupedColumns="true"
|
|
AllowGroup="true"
|
|
ShowFilterRow="true"
|
|
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"
|
|
@ref="gridRef">
|
|
<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 string? errorMessage;
|
|
private string? infoMessage;
|
|
private EditContext? editContext;
|
|
private ValidationMessageStore? validationMessageStore;
|
|
private IGrid? gridRef;
|
|
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;
|
|
|
|
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;
|
|
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.";
|
|
}
|
|
else
|
|
{
|
|
var updated = await Api.UpdateAsync(editModel.Guid, dto);
|
|
if (!updated.Success)
|
|
{
|
|
errorMessage = updated.Error ?? "Aktualisierung fehlgeschlagen.";
|
|
e.Cancel = true;
|
|
return;
|
|
}
|
|
|
|
infoMessage = "Katalog aktualisiert.";
|
|
}
|
|
|
|
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 LoadBandLayoutAsync()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(layoutUser))
|
|
{
|
|
bandLayout = new BandLayout();
|
|
UpdateBandOptions();
|
|
return;
|
|
}
|
|
|
|
var stored = await LayoutApi.GetAsync(LayoutType, LayoutKey, layoutUser);
|
|
if (stored != null && !string.IsNullOrWhiteSpace(stored.LayoutData))
|
|
{
|
|
var parsed = JsonSerializer.Deserialize<BandLayout>(stored.LayoutData, jsonOptions);
|
|
bandLayout = NormalizeBandLayout(parsed);
|
|
}
|
|
else
|
|
{
|
|
bandLayout = new BandLayout();
|
|
}
|
|
|
|
columnBandAssignments = BuildAssignmentsFromLayout(bandLayout);
|
|
ApplyColumnLayoutFromStorage();
|
|
UpdateBandOptions();
|
|
}
|
|
|
|
private async Task SaveLayoutAsync()
|
|
{
|
|
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 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 ApplyBandOrderingFromColumnOrder()
|
|
{
|
|
if (bandLayout.ColumnOrder.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var bandById = bandLayout.Bands.ToDictionary(band => band.Id, StringComparer.OrdinalIgnoreCase);
|
|
var orderedBandIds = new List<string>();
|
|
var orderedColumnsByBand = bandLayout.Bands.ToDictionary(
|
|
band => band.Id,
|
|
_ => new List<string>(),
|
|
StringComparer.OrdinalIgnoreCase);
|
|
|
|
foreach (var field in bandLayout.ColumnOrder)
|
|
{
|
|
if (columnBandAssignments.TryGetValue(field, out var bandId) && bandById.ContainsKey(bandId))
|
|
{
|
|
if (!orderedBandIds.Contains(bandId, StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
orderedBandIds.Add(bandId);
|
|
}
|
|
|
|
orderedColumnsByBand[bandId].Add(field);
|
|
}
|
|
}
|
|
|
|
foreach (var band in bandLayout.Bands)
|
|
{
|
|
var orderedColumns = orderedColumnsByBand[band.Id];
|
|
orderedColumns.AddRange(band.Columns.Where(column => !orderedColumns.Contains(column, StringComparer.OrdinalIgnoreCase)));
|
|
band.Columns = orderedColumns;
|
|
}
|
|
|
|
if (orderedBandIds.Count > 0)
|
|
{
|
|
bandLayout.Bands = orderedBandIds
|
|
.Select(id => bandById[id])
|
|
.Concat(bandLayout.Bands.Where(band => !orderedBandIds.Contains(band.Id, StringComparer.OrdinalIgnoreCase)))
|
|
.ToList();
|
|
}
|
|
}
|
|
|
|
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 RenderFragment? BuildFilterTemplate(ColumnDefinition column)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
private RenderFragment? BuildTextFilterTemplate()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
private RenderFragment? BuildDateFilterTemplate()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|