Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 6m42s

This commit is contained in:
masoodafar-web
2026-02-18 01:19:34 +03:30
5 changed files with 150 additions and 34 deletions
@@ -64,7 +64,7 @@
<MudText Class="mt-2 mud-text-secondary">در حال بارگذاری...</MudText>
</MudStack>
}
else if (_result.Products.Count == 0)
else if (_products.Count == 0)
{
<MudAlert Severity="Severity.Info" Class="my-4">محصولی یافت نشد.</MudAlert>
}
@@ -72,7 +72,7 @@
{
<!-- Products Grid -->
<MudGrid Spacing="1">
@foreach (var p in _result.Products)
@foreach (var p in _products)
{
<MudItem xs="6" sm="6" md="3"
onclick="@(() => NavigateToProduct(p.Id))">
@@ -119,15 +119,29 @@
}
</MudGrid>
<!-- Pagination -->
@if (_result.TotalPages > 1)
@* Lazy Load — بارگذاری بیشتر *@
@if (_loadingMore)
{
<div class="d-flex justify-center mt-4">
<MudPagination Count="@_result.TotalPages" Selected="@_currentPage"
SelectedChanged="OnPageChanged"
Color="Color.Primary" Variant="Variant.Filled"
BoundaryCount="1" MiddleCount="3" />
<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>
@@ -13,9 +13,12 @@ public partial class Products : ComponentBase, IDisposable
private long? _selectedCategoryId;
private int _currentPage = 1;
private bool _loading;
private bool _loadingMore;
private bool _hasMore = true;
private int _totalCount;
private const int PageSize = 12;
private DiscountProductListResult _result = new(new(), 0, 0, 1);
private List<DiscountProductCard> _products = new();
private List<DiscountCategoryNode> _categories = new();
protected override async Task OnInitializedAsync()
@@ -27,45 +30,66 @@ public partial class Products : ComponentBase, IDisposable
var productsTask = ProductService.GetProductsAsync(page: 1, pageSize: PageSize);
await Task.WhenAll(categoriesTask, productsTask);
_categories = categoriesTask.Result;
_result = productsTask.Result;
var result = productsTask.Result;
_products = result.Products;
_totalCount = result.TotalCount;
_hasMore = result.CurrentPage < result.TotalPages;
_loading = false;
}
private async Task LoadProductsAsync()
private async Task LoadInitial()
{
_loading = true;
_currentPage = 1;
_products.Clear();
StateHasChanged();
_result = await ProductService.GetProductsAsync(
var result = await ProductService.GetProductsAsync(
page: 1,
pageSize: PageSize,
search: string.IsNullOrWhiteSpace(_search) ? null : _search,
categoryId: _selectedCategoryId);
_products = result.Products;
_totalCount = result.TotalCount;
_hasMore = result.CurrentPage < result.TotalPages;
_loading = false;
StateHasChanged();
}
private async Task LoadMore()
{
if (_loadingMore || !_hasMore) return;
_loadingMore = true;
StateHasChanged();
_currentPage++;
var result = await ProductService.GetProductsAsync(
page: _currentPage,
pageSize: PageSize,
search: string.IsNullOrWhiteSpace(_search) ? null : _search,
categoryId: _selectedCategoryId);
_loading = false;
_products.AddRange(result.Products);
_hasMore = result.CurrentPage < result.TotalPages;
_loadingMore = false;
StateHasChanged();
}
private async Task SearchProducts() => await LoadProductsAsync();
private async Task SearchProducts() => await LoadInitial();
private async Task OnSearchKeyUp(KeyboardEventArgs e)
{
if (e.Key == "Enter")
{
_currentPage = 1;
await LoadProductsAsync();
await LoadInitial();
}
}
private async Task OnCategoryChanged(long? value)
{
_selectedCategoryId = value;
_currentPage = 1;
await LoadProductsAsync();
}
private async Task OnPageChanged(int page)
{
_currentPage = page;
await LoadProductsAsync();
await LoadInitial();
}
private async Task AddToCart(DiscountProductCard p)
@@ -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()
@@ -26,6 +26,13 @@ public record Product(
= [];
}
public record ProductListResult(
List<Product> Products,
int TotalCount,
int TotalPages,
int CurrentPage,
bool HasNext);
public record ProductGalleryImage(
long ProductGalleryId,
long ProductImageId,
@@ -59,11 +66,24 @@ public class ProductService
}
public async Task<List<Product>> GetProductsAsync(string? query = null, long? categoryId = null, string? sortBy = null)
{
var result = await GetProductsPagedAsync(query: query, categoryId: categoryId, sortBy: sortBy, page: 1, pageSize: 500);
return result.Products;
}
public async Task<ProductListResult> GetProductsPagedAsync(
string? query = null, long? categoryId = null, string? sortBy = null,
int page = 1, int pageSize = 12)
{
try
{
var request = new GetAllProductsByFilterRequest
{
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
{
PageNumber = page,
PageSize = pageSize
},
Filter = new GetAllProductsByFilterFilter
{
Title = query ?? string.Empty,
@@ -84,7 +104,12 @@ public class ProductService
}
var resp = await _client.GetAllProductsByFilterAsync(request);
return MapAndCache(resp.Models);
var products = MapAndCache(resp.Models);
var totalCount = (int)(resp.MetaData?.TotalCount ?? 0);
var totalPages = (int)(resp.MetaData?.TotalPage ?? 0);
var hasNext = resp.MetaData?.HasNext ?? false;
return new ProductListResult(products, totalCount, totalPages, page, hasNext);
}
catch
{
@@ -97,7 +122,8 @@ public class ProductService
p.Description.Contains(q, StringComparison.OrdinalIgnoreCase));
}
return list.OrderBy(p => p.Id).ToList();
var all = list.OrderBy(p => p.Id).ToList();
return new ProductListResult(all, all.Count, 1, 1, false);
}
}