feat: lazy load products in both stores (infinite scroll)
Build and Deploy to Kubernetes / build-and-deploy (push) Has been cancelled

- ProductService: add GetProductsPagedAsync with PaginationState support
- Store/Products: replace load-all with page-by-page lazy loading (12 per page)
- DiscountStore/Products: replace pagination with lazy load (load more button)
- Both stores show 'load more' button + loading indicator + 'all shown' text
This commit is contained in:
masoodafar-web
2026-02-18 01:15:48 +03:30
parent 22203fd8f4
commit bed1ad7c66
5 changed files with 150 additions and 34 deletions
@@ -94,7 +94,7 @@
{
<MudText Typo="Typo.body2" Class="mud-text-secondary">
<MudIcon Icon="@Icons.Material.Filled.Inventory2" Size="Size.Small" Class="me-1"/>
@(_products.Count) محصول
@(_totalCount) محصول
</MudText>
}
</MudItem>
@@ -159,5 +159,30 @@
</MudItem>
}
</MudGrid>
@* Lazy Load — بارگذاری بیشتر *@
@if (_loadingMore)
{
<MudStack AlignItems="AlignItems.Center" Class="py-4">
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Indeterminate="true"/>
<MudText Typo="Typo.caption" Class="mud-text-secondary">بارگذاری بیشتر...</MudText>
</MudStack>
}
else if (_hasMore)
{
<div class="d-flex justify-center py-4">
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.ExpandMore"
OnClick="LoadMore">
نمایش محصولات بیشتر
</MudButton>
</div>
}
else if (_products.Count > 0)
{
<MudText Typo="Typo.caption" Align="Align.Center" Class="py-4 mud-text-secondary">
همه @(_products.Count) محصول نمایش داده شد
</MudText>
}
}
</MudContainer>
@@ -24,6 +24,11 @@ public partial class Products : ComponentBase, IDisposable
private string _query = string.Empty;
private bool _loading;
private bool _loadingMore;
private bool _hasMore = true;
private int _currentPage = 1;
private int _totalCount;
private const int PageSize = 12;
private List<Product> _products = new();
private long? _activeCategoryId;
private string? _activeCategoryTitle;
@@ -36,7 +41,7 @@ public partial class Products : ComponentBase, IDisposable
await Cart.EnsureInitializedAsync();
Cart.OnChange += StateHasChanged;
Navigation.LocationChanged += HandleLocationChanged;
await Load();
await LoadInitial();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -49,18 +54,40 @@ public partial class Products : ComponentBase, IDisposable
await base.OnAfterRenderAsync(firstRender);
}
private async Task Load()
private async Task LoadInitial()
{
_loading = true;
_currentPage = 1;
_products.Clear();
UpdateCategoryFilterFromUri();
var sortBy = GetSortByValue();
_products = await ProductService.GetProductsAsync(_query, _activeCategoryId, sortBy);
var result = await ProductService.GetProductsPagedAsync(_query, _activeCategoryId, sortBy, _currentPage, PageSize);
_products = result.Products;
_hasMore = result.HasNext;
_totalCount = result.TotalCount;
_activeCategoryTitle = _activeCategoryId is { } categoryId
? (await CategoryService.GetByIdAsync(categoryId))?.Title
: null;
_loading = false;
}
private async Task LoadMore()
{
if (_loadingMore || !_hasMore) return;
_loadingMore = true;
StateHasChanged();
_currentPage++;
var sortBy = GetSortByValue();
var result = await ProductService.GetProductsPagedAsync(_query, _activeCategoryId, sortBy, _currentPage, PageSize);
_products.AddRange(result.Products);
_hasMore = result.HasNext;
_loadingMore = false;
StateHasChanged();
}
private string GetSortByValue()
{
return _sortOption switch
@@ -75,12 +102,12 @@ public partial class Products : ComponentBase, IDisposable
private async Task OnSortChanged()
{
await Load();
await LoadInitial();
}
private async Task OnQueryChanged(KeyboardEventArgs _)
{
await Load();
await LoadInitial();
}
private async Task AddToCart(Product p)
@@ -98,7 +125,7 @@ public partial class Products : ComponentBase, IDisposable
private void HandleLocationChanged(object? sender, LocationChangedEventArgs args)
{
_ = InvokeAsync(Load);
_ = InvokeAsync(LoadInitial);
}
private void UpdateCategoryFilterFromUri()