- Add sidebar dashboard navigation and support multiple dashboards - Extract catalog grid/form logic to reusable CatalogsGrid component - Add CatalogApiClient and DTOs for catalog CRUD operations - Define dashboards with JSON data sources (Default, CatalogsGrid) - Update configuration for dashboard and API endpoints - Improve styling and imports for modularity and maintainability
287 lines
11 KiB
Plaintext
287 lines
11 KiB
Plaintext
@inject CatalogApiClient Api
|
|
|
|
<style>
|
|
.action-panel { margin-bottom: 16px; }
|
|
.grid-section { margin-top: 12px; }
|
|
.catalog-grid th.dxbl-grid-header-sortable {
|
|
position: relative;
|
|
padding-right: 1.5rem;
|
|
}
|
|
.catalog-grid th.dxbl-grid-header-sortable:not(:has(.dxbl-grid-sort-asc)):not(:has(.dxbl-grid-sort-desc))::before,
|
|
.catalog-grid th.dxbl-grid-header-sortable:not(:has(.dxbl-grid-sort-asc)):not(:has(.dxbl-grid-sort-desc))::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:not(:has(.dxbl-grid-sort-asc)):not(:has(.dxbl-grid-sort-desc))::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:not(:has(.dxbl-grid-sort-asc)):not(:has(.dxbl-grid-sort-desc))::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 .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;
|
|
}
|
|
</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>
|
|
}
|
|
|
|
<div class="mb-3">
|
|
<DxButton RenderStyle="ButtonRenderStyle.Primary" Click="@StartCreate">Neuen Eintrag anlegen</DxButton>
|
|
</div>
|
|
|
|
@if (showForm)
|
|
{
|
|
<div class="action-panel">
|
|
<EditForm Model="formModel" OnValidSubmit="HandleSubmit" Context="editCtx">
|
|
<DxFormLayout ColCount="2">
|
|
<DxFormLayoutItem Caption="Titel" Context="itemCtx">
|
|
<DxTextBox @bind-Text="formModel.CatTitle" Enabled="@(isEditing ? formModel.UpdateProcedure != 0 : true)" />
|
|
</DxFormLayoutItem>
|
|
<DxFormLayoutItem Caption="Kennung" Context="itemCtx">
|
|
<DxTextBox @bind-Text="formModel.CatString" />
|
|
</DxFormLayoutItem>
|
|
@if (isEditing)
|
|
{
|
|
<DxFormLayoutItem Caption="Update-Prozedur" Context="itemCtx">
|
|
<DxComboBox Data="@procedureOptions"
|
|
TextFieldName="Text"
|
|
ValueFieldName="Value"
|
|
@bind-Value="formModel.UpdateProcedure" />
|
|
</DxFormLayoutItem>
|
|
}
|
|
<DxFormLayoutItem Caption=" " Context="itemCtx">
|
|
<DxStack Orientation="Orientation.Horizontal" Spacing="8">
|
|
<DxButton RenderStyle="ButtonRenderStyle.Success" ButtonType="ButtonType.Submit" SubmitFormOnClick="true" Context="btnCtx">@((isEditing ? "Speichern" : "Anlegen"))</DxButton>
|
|
<DxButton RenderStyle="ButtonRenderStyle.Secondary" Click="@CancelEdit" Context="btnCtx">Abbrechen</DxButton>
|
|
</DxStack>
|
|
</DxFormLayoutItem>
|
|
</DxFormLayout>
|
|
</EditForm>
|
|
</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">
|
|
<Columns>
|
|
<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">
|
|
<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" />
|
|
<DxGridDataColumn FieldName="@nameof(CatalogReadDto.ChangedWho)" Caption="Geändert von">
|
|
<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" />
|
|
<DxGridDataColumn Caption="" Width="220px" AllowSort="false">
|
|
<CellDisplayTemplate Context="cell">
|
|
@{ var item = (CatalogReadDto)cell.DataItem; }
|
|
<div style="white-space: nowrap;">
|
|
<DxButton RenderStyle="ButtonRenderStyle.Secondary" Size="ButtonSize.Small" Click="@(() => StartEdit(item))">Bearbeiten</DxButton>
|
|
<DxButton RenderStyle="ButtonRenderStyle.Danger" Size="ButtonSize.Small" Click="@(() => DeleteCatalog(item.Guid))">Löschen</DxButton>
|
|
</div>
|
|
</CellDisplayTemplate>
|
|
</DxGridDataColumn>
|
|
</Columns>
|
|
</DxGrid>
|
|
</div>
|
|
}
|
|
|
|
@code {
|
|
private List<CatalogReadDto> items = new();
|
|
private CatalogWriteDto formModel = new();
|
|
private int editingId;
|
|
private bool isLoading;
|
|
private bool isEditing;
|
|
private bool showForm;
|
|
private string? errorMessage;
|
|
private string? infoMessage;
|
|
|
|
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 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 void StartCreate()
|
|
{
|
|
formModel = new CatalogWriteDto();
|
|
editingId = 0;
|
|
isEditing = false;
|
|
showForm = true;
|
|
infoMessage = null;
|
|
errorMessage = null;
|
|
}
|
|
|
|
private void StartEdit(CatalogReadDto item)
|
|
{
|
|
formModel = new CatalogWriteDto
|
|
{
|
|
CatTitle = item.CatTitle,
|
|
CatString = item.CatString,
|
|
UpdateProcedure = 0
|
|
};
|
|
editingId = item.Guid;
|
|
isEditing = true;
|
|
showForm = true;
|
|
infoMessage = null;
|
|
errorMessage = null;
|
|
}
|
|
|
|
private async Task HandleSubmit()
|
|
{
|
|
errorMessage = null;
|
|
infoMessage = null;
|
|
|
|
try
|
|
{
|
|
if (isEditing)
|
|
{
|
|
var updated = await Api.UpdateAsync(editingId, formModel);
|
|
if (!updated.Success)
|
|
{
|
|
errorMessage = updated.Error ?? "Aktualisierung fehlgeschlagen.";
|
|
return;
|
|
}
|
|
|
|
infoMessage = "Katalog aktualisiert.";
|
|
}
|
|
else
|
|
{
|
|
var created = await Api.CreateAsync(formModel);
|
|
if (!created.Success || created.Value == null)
|
|
{
|
|
errorMessage = created.Error ?? "Anlegen fehlgeschlagen.";
|
|
return;
|
|
}
|
|
|
|
infoMessage = "Katalog angelegt.";
|
|
}
|
|
|
|
showForm = false;
|
|
await LoadCatalogs();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = $"Fehler beim Speichern: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
private void CancelEdit()
|
|
{
|
|
showForm = false;
|
|
infoMessage = null;
|
|
errorMessage = null;
|
|
}
|
|
|
|
private async Task DeleteCatalog(int id)
|
|
{
|
|
errorMessage = null;
|
|
infoMessage = null;
|
|
|
|
try
|
|
{
|
|
var deleted = await Api.DeleteAsync(id);
|
|
if (!deleted.Success)
|
|
{
|
|
errorMessage = deleted.Error ?? "Löschen fehlgeschlagen.";
|
|
return;
|
|
}
|
|
|
|
infoMessage = "Katalog gelöscht.";
|
|
await LoadCatalogs();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = $"Fehler beim Löschen: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
private sealed class ProcedureOption
|
|
{
|
|
public int Value { get; set; }
|
|
public string Text { get; set; } = string.Empty;
|
|
}
|
|
}
|