- Switch to custom edit model (MassDataEditModel) for popup editing, enabling granular field validation and UI control - Replace default editors with explicit DxTextBox/DxCheckBox bindings - Add AmountText field for string input and validation; validate and convert in OnEditModelSaving - Implement duplicate customer check via new GetByCustomerNameAsync API method - Show ValidationSummary in popup; manage validation state with ValidationMessageStore and EditContext - Make popup header and width dynamic; show procedure ComboBox only for existing records - Restore "New" button in grid command column - Refactor CatalogsGrid to handle validation clearing in OnEditFieldChanged instead of OnTitleChanged - General improvements to real-time validation feedback
368 lines
13 KiB
Plaintext
368 lines
13 KiB
Plaintext
@using Microsoft.AspNetCore.Components.Forms
|
|
@inject CatalogApiClient Api
|
|
|
|
<style>
|
|
.action-panel { margin-bottom: 16px; }
|
|
.grid-section { margin-top: 12px; }
|
|
.catalog-edit-popup {
|
|
min-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="grid-section">
|
|
<DxGrid Data="@items"
|
|
TItem="CatalogReadDto"
|
|
KeyFieldName="@nameof(CatalogReadDto.Guid)"
|
|
ShowFilterRow="true"
|
|
PageSize="10"
|
|
CssClass="mb-4 catalog-grid"
|
|
EditMode="GridEditMode.PopupEditForm"
|
|
PopupEditFormCssClass="catalog-edit-popup"
|
|
PopupEditFormHeaderText="@popupHeaderText"
|
|
CustomizeEditModel="OnCustomizeEditModel"
|
|
EditModelSaving="OnEditModelSaving"
|
|
DataItemDeleting="OnDataItemDeleting">
|
|
<Columns>
|
|
<DxGridCommandColumn Width="120px" />
|
|
<DxGridDataColumn FieldName="@nameof(CatalogReadDto.Guid)" Caption="Id" Width="140px" SortIndex="0" SortOrder="GridColumnSortOrder.Ascending">
|
|
<FilterRowCellTemplate Context="filter">
|
|
<DxTextBox Text="@(filter.FilterRowValue?.ToString())"
|
|
TextChanged="@(value => filter.FilterRowValue = value)"
|
|
CssClass="filter-search-input" />
|
|
</FilterRowCellTemplate>
|
|
</DxGridDataColumn>
|
|
<DxGridDataColumn FieldName="@nameof(CatalogReadDto.CatTitle)" Caption="Titel">
|
|
<FilterRowCellTemplate Context="filter">
|
|
<DxTextBox Text="@(filter.FilterRowValue as string)"
|
|
TextChanged="@(value => filter.FilterRowValue = value)"
|
|
CssClass="filter-search-input" />
|
|
</FilterRowCellTemplate>
|
|
</DxGridDataColumn>
|
|
<DxGridDataColumn FieldName="@nameof(CatalogReadDto.CatString)" Caption="String">
|
|
<FilterRowCellTemplate Context="filter">
|
|
<DxTextBox Text="@(filter.FilterRowValue as string)"
|
|
TextChanged="@(value => filter.FilterRowValue = value)"
|
|
CssClass="filter-search-input" />
|
|
</FilterRowCellTemplate>
|
|
</DxGridDataColumn>
|
|
<DxGridDataColumn FieldName="@nameof(CatalogReadDto.AddedWho)" Caption="Angelegt von" ReadOnly="true">
|
|
<FilterRowCellTemplate Context="filter">
|
|
<DxTextBox Text="@(filter.FilterRowValue as string)"
|
|
TextChanged="@(value => filter.FilterRowValue = value)"
|
|
CssClass="filter-search-input" />
|
|
</FilterRowCellTemplate>
|
|
</DxGridDataColumn>
|
|
<DxGridDataColumn FieldName="@nameof(CatalogReadDto.AddedWhen)" Caption="Angelegt am" ReadOnly="true">
|
|
<FilterRowCellTemplate Context="filter">
|
|
<DxDateEdit Date="@((DateTime?)filter.FilterRowValue)"
|
|
DateChanged="@((DateTime? value) => { filter.FilterRowValue = value; })"
|
|
Width="100%" />
|
|
</FilterRowCellTemplate>
|
|
</DxGridDataColumn>
|
|
<DxGridDataColumn FieldName="@nameof(CatalogReadDto.ChangedWho)" Caption="Geändert von" ReadOnly="true">
|
|
<FilterRowCellTemplate Context="filter">
|
|
<DxTextBox Text="@(filter.FilterRowValue as string)"
|
|
TextChanged="@(value => filter.FilterRowValue = value)"
|
|
CssClass="filter-search-input" />
|
|
</FilterRowCellTemplate>
|
|
</DxGridDataColumn>
|
|
<DxGridDataColumn FieldName="@nameof(CatalogReadDto.ChangedWhen)" Caption="Geändert am" ReadOnly="true">
|
|
<FilterRowCellTemplate Context="filter">
|
|
<DxDateEdit Date="@((DateTime?)filter.FilterRowValue)"
|
|
DateChanged="@((DateTime? value) => { filter.FilterRowValue = value; })"
|
|
Width="100%" />
|
|
</FilterRowCellTemplate>
|
|
</DxGridDataColumn>
|
|
</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 string popupHeaderText = "Edit";
|
|
|
|
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 LoadCatalogs();
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
}
|