Add user-selectable page size to MassData grid
Users can now choose how many records to display per page in the MassData grid (100, 1,000, 10,000, 100,000, or all). The backend and API client are updated to support nullable skip/take parameters, allowing "all" records to be fetched when desired. The pager and page count calculations are updated to reflect the selected page size, and the pager is hidden if only one page exists. Additional UI and CSS changes provide a combo box for page size selection. The API controller now treats take <= 0 as "no limit."
This commit is contained in:
@@ -27,7 +27,12 @@ public class MassDataController : ControllerBase
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<IEnumerable<MassDataReadDto>>> GetAll([FromQuery] int? skip, [FromQuery] int? take, CancellationToken cancellationToken)
|
public async Task<ActionResult<IEnumerable<MassDataReadDto>>> GetAll([FromQuery] int? skip, [FromQuery] int? take, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var resolvedTake = take is null or <= 0 ? 200 : take;
|
int? resolvedTake = take;
|
||||||
|
if (resolvedTake is <= 0)
|
||||||
|
{
|
||||||
|
resolvedTake = null;
|
||||||
|
}
|
||||||
|
|
||||||
var result = await _mediator.Send(new GetAllMassDataQuery(skip, resolvedTake), cancellationToken);
|
var result = await _mediator.Send(new GetAllMassDataQuery(skip, resolvedTake), cancellationToken);
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,23 @@
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
.page-size-selector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
.page-size-label {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.page-size-combo {
|
||||||
|
width: 13ch;
|
||||||
|
min-width: 13ch;
|
||||||
|
max-width: 13ch;
|
||||||
|
}
|
||||||
|
.page-size-combo input {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
.massdata-grid .dxbl-grid-sort-asc,
|
.massdata-grid .dxbl-grid-sort-asc,
|
||||||
.massdata-grid .dxbl-grid-sort-desc {
|
.massdata-grid .dxbl-grid-sort-desc {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -75,6 +92,18 @@ else if (items.Count == 0)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
<div class="mb-3 page-size-selector">
|
||||||
|
<span class="page-size-label">Datensätze je Seite:</span>
|
||||||
|
<DxComboBox Data="@pageSizeOptions"
|
||||||
|
TData="PageSizeOption"
|
||||||
|
TValue="int?"
|
||||||
|
TextFieldName="Text"
|
||||||
|
ValueFieldName="Value"
|
||||||
|
Value="@pageSize"
|
||||||
|
ValueChanged="OnPageSizeChanged"
|
||||||
|
CssClass="page-size-combo" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid-section">
|
<div class="grid-section">
|
||||||
<DxGrid Data="@items"
|
<DxGrid Data="@items"
|
||||||
TItem="MassDataReadDto"
|
TItem="MassDataReadDto"
|
||||||
@@ -83,10 +112,9 @@ else
|
|||||||
ShowGroupPanel="true"
|
ShowGroupPanel="true"
|
||||||
AllowColumnResize="true"
|
AllowColumnResize="true"
|
||||||
PagerVisible="false"
|
PagerVisible="false"
|
||||||
PageSize="100"
|
PageSize="@(pageSize ?? 100)"
|
||||||
CssClass="mb-3 massdata-grid"
|
CssClass="mb-3 massdata-grid"
|
||||||
EditMode="GridEditMode.PopupEditForm"
|
EditMode="GridEditMode.PopupEditForm"
|
||||||
PopupEditFormCssClass="massdata-edit-popup"
|
|
||||||
PopupEditFormHeaderText="@popupHeaderText"
|
PopupEditFormHeaderText="@popupHeaderText"
|
||||||
CustomizeEditModel="OnCustomizeEditModel"
|
CustomizeEditModel="OnCustomizeEditModel"
|
||||||
EditModelSaving="OnEditModelSaving"
|
EditModelSaving="OnEditModelSaving"
|
||||||
@@ -182,24 +210,36 @@ else
|
|||||||
</EditFormTemplate>
|
</EditFormTemplate>
|
||||||
</DxGrid>
|
</DxGrid>
|
||||||
|
|
||||||
<div class="pager-container">
|
@if (pageCount > 1)
|
||||||
<DxPager PageCount="@pageCount" ActivePageIndex="@pageIndex" ActivePageIndexChanged="OnPageChanged" />
|
{
|
||||||
</div>
|
<div class="pager-container">
|
||||||
|
<DxPager PageCount="@pageCount" ActivePageIndex="@pageIndex" ActivePageIndexChanged="OnPageChanged" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private const int PageSize = 100;
|
|
||||||
private List<MassDataReadDto> items = new();
|
private List<MassDataReadDto> items = new();
|
||||||
private bool isLoading;
|
private bool isLoading;
|
||||||
private string? errorMessage;
|
private string? errorMessage;
|
||||||
private string? infoMessage;
|
private string? infoMessage;
|
||||||
private int pageIndex;
|
private int pageIndex;
|
||||||
private int pageCount = 1;
|
private int pageCount = 1;
|
||||||
|
private int? pageSize = 100;
|
||||||
private string popupHeaderText = "Edit";
|
private string popupHeaderText = "Edit";
|
||||||
private EditContext? editContext;
|
private EditContext? editContext;
|
||||||
private ValidationMessageStore? validationMessageStore;
|
private ValidationMessageStore? validationMessageStore;
|
||||||
|
|
||||||
|
private readonly List<PageSizeOption> pageSizeOptions = new()
|
||||||
|
{
|
||||||
|
new() { Value = 100, Text = "100" },
|
||||||
|
new() { Value = 1000, Text = "1.000" },
|
||||||
|
new() { Value = 10000, Text = "10.000" },
|
||||||
|
new() { Value = 100000, Text = "100.000" },
|
||||||
|
new() { Value = null, Text = "Alle" }
|
||||||
|
};
|
||||||
|
|
||||||
private readonly List<BoolFilterOption> statusFilterOptions = new()
|
private readonly List<BoolFilterOption> statusFilterOptions = new()
|
||||||
{
|
{
|
||||||
new() { Value = null, Text = "Alle" },
|
new() { Value = null, Text = "Alle" },
|
||||||
@@ -224,11 +264,12 @@ else
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var total = await Api.GetCountAsync();
|
var total = await Api.GetCountAsync();
|
||||||
pageCount = Math.Max(1, (int)Math.Ceiling(total / (double)PageSize));
|
var effectivePageSize = pageSize ?? (total == 0 ? 1 : total);
|
||||||
|
pageCount = Math.Max(1, (int)Math.Ceiling(total / (double)effectivePageSize));
|
||||||
pageIndex = Math.Clamp(page, 0, pageCount - 1);
|
pageIndex = Math.Clamp(page, 0, pageCount - 1);
|
||||||
|
|
||||||
var skip = pageIndex * PageSize;
|
var skip = pageSize.HasValue ? pageIndex * pageSize.Value : 0;
|
||||||
items = await Api.GetAllAsync(skip, PageSize);
|
items = await Api.GetAllAsync(skip, pageSize);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -246,6 +287,12 @@ else
|
|||||||
await LoadPage(index);
|
await LoadPage(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OnPageSizeChanged(int? size)
|
||||||
|
{
|
||||||
|
pageSize = size;
|
||||||
|
await LoadPage(0);
|
||||||
|
}
|
||||||
|
|
||||||
private void SetEditContext(EditContext context)
|
private void SetEditContext(EditContext context)
|
||||||
{
|
{
|
||||||
if (editContext == context)
|
if (editContext == context)
|
||||||
@@ -405,4 +452,10 @@ else
|
|||||||
public bool? Value { get; set; }
|
public bool? Value { get; set; }
|
||||||
public string Text { get; set; } = string.Empty;
|
public string Text { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class PageSizeOption
|
||||||
|
{
|
||||||
|
public int? Value { get; set; }
|
||||||
|
public string Text { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,20 @@ public class MassDataApiClient
|
|||||||
return result ?? 0;
|
return result ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<MassDataReadDto>> GetAllAsync(int skip, int take)
|
public async Task<List<MassDataReadDto>> GetAllAsync(int? skip, int? take)
|
||||||
{
|
{
|
||||||
var result = await _httpClient.GetFromJsonAsync<List<MassDataReadDto>>($"{Endpoint}?skip={skip}&take={take}");
|
var query = new List<string>();
|
||||||
|
if (skip.HasValue)
|
||||||
|
{
|
||||||
|
query.Add($"skip={skip.Value}");
|
||||||
|
}
|
||||||
|
if (take.HasValue)
|
||||||
|
{
|
||||||
|
query.Add($"take={take.Value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = query.Count == 0 ? Endpoint : $"{Endpoint}?{string.Join("&", query)}";
|
||||||
|
var result = await _httpClient.GetFromJsonAsync<List<MassDataReadDto>>(url);
|
||||||
return result ?? new List<MassDataReadDto>();
|
return result ?? new List<MassDataReadDto>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,23 @@
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
.page-size-selector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
.page-size-label {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.page-size-combo {
|
||||||
|
width: 13ch;
|
||||||
|
min-width: 13ch;
|
||||||
|
max-width: 13ch;
|
||||||
|
}
|
||||||
|
.page-size-combo input {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
.massdata-grid .dxbl-grid-sort-asc,
|
.massdata-grid .dxbl-grid-sort-asc,
|
||||||
.massdata-grid .dxbl-grid-sort-desc {
|
.massdata-grid .dxbl-grid-sort-desc {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -75,6 +92,18 @@ else if (items.Count == 0)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
<div class="mb-3 page-size-selector">
|
||||||
|
<span class="page-size-label">Datensätze je Seite:</span>
|
||||||
|
<DxComboBox Data="@pageSizeOptions"
|
||||||
|
TData="PageSizeOption"
|
||||||
|
TValue="int?"
|
||||||
|
TextFieldName="Text"
|
||||||
|
ValueFieldName="Value"
|
||||||
|
Value="@pageSize"
|
||||||
|
ValueChanged="OnPageSizeChanged"
|
||||||
|
CssClass="page-size-combo" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid-section">
|
<div class="grid-section">
|
||||||
<DxGrid Data="@items"
|
<DxGrid Data="@items"
|
||||||
TItem="MassDataReadDto"
|
TItem="MassDataReadDto"
|
||||||
@@ -83,10 +112,9 @@ else
|
|||||||
ShowGroupPanel="true"
|
ShowGroupPanel="true"
|
||||||
AllowColumnResize="true"
|
AllowColumnResize="true"
|
||||||
PagerVisible="false"
|
PagerVisible="false"
|
||||||
PageSize="100"
|
PageSize="@(pageSize ?? 100)"
|
||||||
CssClass="mb-3 massdata-grid"
|
CssClass="mb-3 massdata-grid"
|
||||||
EditMode="GridEditMode.PopupEditForm"
|
EditMode="GridEditMode.PopupEditForm"
|
||||||
PopupEditFormCssClass="massdata-edit-popup"
|
|
||||||
PopupEditFormHeaderText="@popupHeaderText"
|
PopupEditFormHeaderText="@popupHeaderText"
|
||||||
CustomizeEditModel="OnCustomizeEditModel"
|
CustomizeEditModel="OnCustomizeEditModel"
|
||||||
EditModelSaving="OnEditModelSaving"
|
EditModelSaving="OnEditModelSaving"
|
||||||
@@ -182,20 +210,23 @@ else
|
|||||||
</EditFormTemplate>
|
</EditFormTemplate>
|
||||||
</DxGrid>
|
</DxGrid>
|
||||||
|
|
||||||
<div class="pager-container">
|
@if (pageCount > 1)
|
||||||
<DxPager PageCount="@pageCount" ActivePageIndex="@pageIndex" ActivePageIndexChanged="OnPageChanged" />
|
{
|
||||||
</div>
|
<div class="pager-container">
|
||||||
|
<DxPager PageCount="@pageCount" ActivePageIndex="@pageIndex" ActivePageIndexChanged="OnPageChanged" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private const int PageSize = 100;
|
|
||||||
private List<MassDataReadDto> items = new();
|
private List<MassDataReadDto> items = new();
|
||||||
private bool isLoading;
|
private bool isLoading;
|
||||||
private string? errorMessage;
|
private string? errorMessage;
|
||||||
private string? infoMessage;
|
private string? infoMessage;
|
||||||
private int pageIndex;
|
private int pageIndex;
|
||||||
private int pageCount = 1;
|
private int pageCount = 1;
|
||||||
|
private int? pageSize = 100;
|
||||||
private string popupHeaderText = "Edit";
|
private string popupHeaderText = "Edit";
|
||||||
private EditContext? editContext;
|
private EditContext? editContext;
|
||||||
private ValidationMessageStore? validationMessageStore;
|
private ValidationMessageStore? validationMessageStore;
|
||||||
@@ -212,6 +243,15 @@ else
|
|||||||
new() { Value = 0, Text = "PRMassdata_UpsertByCustomerName" }
|
new() { Value = 0, Text = "PRMassdata_UpsertByCustomerName" }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly List<PageSizeOption> pageSizeOptions = new()
|
||||||
|
{
|
||||||
|
new() { Value = 100, Text = "100" },
|
||||||
|
new() { Value = 1000, Text = "1.000" },
|
||||||
|
new() { Value = 10000, Text = "10.000" },
|
||||||
|
new() { Value = 100000, Text = "100.000" },
|
||||||
|
new() { Value = null, Text = "Alle" }
|
||||||
|
};
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
await LoadPage(0);
|
await LoadPage(0);
|
||||||
@@ -224,11 +264,12 @@ else
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var total = await Api.GetCountAsync();
|
var total = await Api.GetCountAsync();
|
||||||
pageCount = Math.Max(1, (int)Math.Ceiling(total / (double)PageSize));
|
var effectivePageSize = pageSize ?? (total == 0 ? 1 : total);
|
||||||
|
pageCount = Math.Max(1, (int)Math.Ceiling(total / (double)effectivePageSize));
|
||||||
pageIndex = Math.Clamp(page, 0, pageCount - 1);
|
pageIndex = Math.Clamp(page, 0, pageCount - 1);
|
||||||
|
|
||||||
var skip = pageIndex * PageSize;
|
var skip = pageSize.HasValue ? pageIndex * pageSize.Value : 0;
|
||||||
items = await Api.GetAllAsync(skip, PageSize);
|
items = await Api.GetAllAsync(skip, pageSize);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -246,6 +287,12 @@ else
|
|||||||
await LoadPage(index);
|
await LoadPage(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OnPageSizeChanged(int? size)
|
||||||
|
{
|
||||||
|
pageSize = size;
|
||||||
|
await LoadPage(0);
|
||||||
|
}
|
||||||
|
|
||||||
private void SetEditContext(EditContext context)
|
private void SetEditContext(EditContext context)
|
||||||
{
|
{
|
||||||
if (editContext == context)
|
if (editContext == context)
|
||||||
@@ -405,4 +452,10 @@ else
|
|||||||
public bool? Value { get; set; }
|
public bool? Value { get; set; }
|
||||||
public string Text { get; set; } = string.Empty;
|
public string Text { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class PageSizeOption
|
||||||
|
{
|
||||||
|
public int? Value { get; set; }
|
||||||
|
public string Text { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,20 @@ public class MassDataApiClient
|
|||||||
return result ?? 0;
|
return result ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<MassDataReadDto>> GetAllAsync(int skip, int take)
|
public async Task<List<MassDataReadDto>> GetAllAsync(int? skip, int? take)
|
||||||
{
|
{
|
||||||
var result = await _httpClient.GetFromJsonAsync<List<MassDataReadDto>>($"{Endpoint}?skip={skip}&take={take}");
|
var query = new List<string>();
|
||||||
|
if (skip.HasValue)
|
||||||
|
{
|
||||||
|
query.Add($"skip={skip.Value}");
|
||||||
|
}
|
||||||
|
if (take.HasValue)
|
||||||
|
{
|
||||||
|
query.Add($"take={take.Value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = query.Count == 0 ? Endpoint : $"{Endpoint}?{string.Join("&", query)}";
|
||||||
|
var result = await _httpClient.GetFromJsonAsync<List<MassDataReadDto>>(url);
|
||||||
return result ?? new List<MassDataReadDto>();
|
return result ?? new List<MassDataReadDto>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user