Refactored CatalogsGrid.razor to inherit from BandGridBase<CatalogReadDto>, moving band layout and grid logic into the base class. Updated UI to use new async methods for saving and resetting the layout with user feedback. Removed redundant local fields and methods, centralizing layout management and improving maintainability and code reuse. Error and info messages are now shown after layout operations.
394 lines
15 KiB
Plaintext
394 lines
15 KiB
Plaintext
@inherits BandGridBase<CatalogReadDto>
|
|
@inject CatalogApiClient Api
|
|
|
|
@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">
|
|
<button class="band-editor-toggle" @onclick="() => bandEditorExpanded = !bandEditorExpanded">
|
|
<span class="band-editor-toggle-icon @(bandEditorExpanded ? "expanded" : "")">►</span>
|
|
<span>Layout</span>
|
|
</button>
|
|
@if (bandEditorExpanded)
|
|
{
|
|
<div class="band-editor-body">
|
|
<div class="band-controls">
|
|
<DxButton Text="Band hinzufügen" Click="AddBand" />
|
|
<DxButton Text="Layout speichern" Click="SaveLayoutWithFeedbackAsync" Enabled="@CanSaveBandLayout" />
|
|
<DxButton Text="Layout zurücksetzen" Click="ResetLayoutWithFeedbackAsync" />
|
|
</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>
|
|
|
|
<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 int? focusedRowKey;
|
|
private string popupHeaderText = "Edit";
|
|
|
|
protected override string LayoutKey => "CatalogsGrid";
|
|
|
|
protected override List<ColumnDefinition> ColumnDefinitions { get; } = 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" }
|
|
};
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await InitializeBandLayoutAsync();
|
|
await LoadCatalogs();
|
|
}
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
await ApplyGridLayoutAfterRenderAsync();
|
|
}
|
|
|
|
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 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();
|
|
|
|
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;
|
|
validationMessageStore.Add(new FieldIdentifier(editModel, fieldName), 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 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 async Task SaveLayoutWithFeedbackAsync()
|
|
{
|
|
try
|
|
{
|
|
await SaveLayoutAsync();
|
|
infoMessage = "Layout gespeichert.";
|
|
errorMessage = null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = $"Layout konnte nicht gespeichert werden: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
private async Task ResetLayoutWithFeedbackAsync()
|
|
{
|
|
await ResetLayoutAsync();
|
|
infoMessage = "Layout zurückgesetzt.";
|
|
errorMessage = null;
|
|
}
|
|
} |