Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 6m42s
Build and Deploy to Production / build-and-deploy (push) Successful in 6m42s
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user