Init
This commit is contained in:
180
Prefab.Web.Client/Pages/Catalog/Products.razor
Normal file
180
Prefab.Web.Client/Pages/Catalog/Products.razor
Normal file
@@ -0,0 +1,180 @@
|
||||
@page "/catalog/products"
|
||||
@inherits ResultComponentBase<ProductListingModel>
|
||||
|
||||
<PageTitle>@PageTitle</PageTitle>
|
||||
|
||||
@if (IsLoading)
|
||||
{
|
||||
<div class="block">
|
||||
<div class="container container--max--xl">
|
||||
<div class="row g-custom-30">
|
||||
<div class="col">
|
||||
<div class="products-view">
|
||||
<TelerikSkeleton Width="100%" Height="240px" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else if (Page.Products.Count == 0)
|
||||
{
|
||||
<div class="page">
|
||||
<div class="page__header">
|
||||
<div class="container container--max--xl">
|
||||
<div class="row g-custom-30">
|
||||
<div class="col">
|
||||
<ol class="page__header-breadcrumbs breadcrumb">
|
||||
<li class="breadcrumb-item"><NavLink href="/">Home</NavLink></li>
|
||||
<li class="breadcrumb-item"><NavLink href="/catalog">Catalog</NavLink></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">@Page.Category.Name</li>
|
||||
</ol>
|
||||
<h1 class="page__header-title decor-header decor-header--align--center">@Page.Category.Name</h1>
|
||||
@if (!string.IsNullOrWhiteSpace(Page.Category.Description))
|
||||
{
|
||||
<p class="text-muted text-center">@Page.Category.Description</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page__body">
|
||||
<div class="block">
|
||||
<div class="container container--max--xl">
|
||||
<div class="row g-custom-30">
|
||||
<div class="col">
|
||||
<div class="alert alert-info mb-0">No products are available in this category yet.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="page">
|
||||
<div class="page__header">
|
||||
<div class="container container--max--xl">
|
||||
<div class="row g-custom-30">
|
||||
<div class="col">
|
||||
<ol class="page__header-breadcrumbs breadcrumb">
|
||||
<li class="breadcrumb-item"><NavLink href="/">Home</NavLink></li>
|
||||
<li class="breadcrumb-item"><NavLink href="/catalog">Catalog</NavLink></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">@Page.Category.Name</li>
|
||||
</ol>
|
||||
<h1 class="page__header-title decor-header decor-header--align--center">@Page.Category.Name</h1>
|
||||
@if (!string.IsNullOrWhiteSpace(Page.Category.Description))
|
||||
{
|
||||
<p class="text-muted text-center">@Page.Category.Description</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page__body">
|
||||
<div class="block">
|
||||
<div class="container container--max--xl">
|
||||
<div class="row g-custom-30">
|
||||
<div class="col-12 col-lg-3 order-1 order-lg-0">
|
||||
<div class="block">
|
||||
<aside class="sidebar">
|
||||
<div class="widget widget--card">
|
||||
<div class="widget__title">Filters</div>
|
||||
<div class="widget__body text-muted">
|
||||
Filter options are coming soon.
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-9">
|
||||
<div class="products-view">
|
||||
<div class="products-view__options view-options">
|
||||
<div class="view-options__layout">
|
||||
<button type="button" class="view-options__layout-button view-options__layout-button--active" disabled aria-disabled="true">
|
||||
<svg width="14px" height="14px">
|
||||
<use xlink:href="images/sprite.svg#grid-14"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="view-options__layout-button" disabled aria-disabled="true">
|
||||
<svg width="14px" height="14px">
|
||||
<use xlink:href="images/sprite.svg#list-14"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="view-options__legend">@LegendText</div>
|
||||
<div class="view-options__divider"></div>
|
||||
<div class="view-options__control">
|
||||
<label class="view-options__control-label" for="view-options-sort">Sort By:</label>
|
||||
<div class="view-options__control-content">
|
||||
<select class="form-select form-select-sm" id="view-options-sort" disabled>
|
||||
<option selected>Default</option>
|
||||
<option>Name (A-Z)</option>
|
||||
<option>Name (Z-A)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-options__control">
|
||||
<label class="view-options__control-label" for="view-options-show">Show:</label>
|
||||
<div class="view-options__control-content">
|
||||
<select class="form-select form-select-sm" id="view-options-show" disabled>
|
||||
<option selected>12</option>
|
||||
<option>24</option>
|
||||
<option>48</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CardGrid View="grid">
|
||||
@foreach (var product in Page.Products)
|
||||
{
|
||||
<CardGridItem>
|
||||
<ProductCard Product="product" Layout="grid" />
|
||||
</CardGridItem>
|
||||
}
|
||||
</CardGrid>
|
||||
|
||||
@if (TotalPages > 1)
|
||||
{
|
||||
<div class="products-view__pagination">
|
||||
<nav aria-label="Product pagination">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="page-item @(HasPreviousPage ? string.Empty : "disabled")">
|
||||
<span class="page-link" aria-label="Previous" aria-disabled="@(!HasPreviousPage)">
|
||||
<svg width="6px" height="9px">
|
||||
<use xlink:href="images/sprite.svg#arrow-left-6x9"></use>
|
||||
</svg>
|
||||
<span class="visually-hidden">Previous</span>
|
||||
</span>
|
||||
</li>
|
||||
@foreach (var pageNumber in VisiblePageNumbers)
|
||||
{
|
||||
var isActive = pageNumber == CurrentPage;
|
||||
<li class="page-item @(isActive ? "active" : string.Empty)">
|
||||
<span class="page-link">@pageNumber</span>
|
||||
</li>
|
||||
}
|
||||
<li class="page-item @(HasNextPage ? string.Empty : "disabled")">
|
||||
<span class="page-link" aria-label="Next" aria-disabled="@(!HasNextPage)">
|
||||
<svg width="6px" height="9px">
|
||||
<use xlink:href="images/sprite.svg#arrow-right-6x9"></use>
|
||||
</svg>
|
||||
<span class="visually-hidden">Next</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
107
Prefab.Web.Client/Pages/Catalog/Products.razor.cs
Normal file
107
Prefab.Web.Client/Pages/Catalog/Products.razor.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Prefab.Web.Client.Models.Catalog;
|
||||
using Prefab.Web.Client.Services;
|
||||
|
||||
namespace Prefab.Web.Client.Pages.Catalog;
|
||||
|
||||
public partial class Products
|
||||
{
|
||||
private string? _currentSlug;
|
||||
|
||||
[Inject]
|
||||
private IProductListingService ProductListingService { get; set; } = null!;
|
||||
|
||||
[SupplyParameterFromQuery(Name = "category-slug")]
|
||||
public string? CategorySlug { get; set; }
|
||||
|
||||
protected bool IsLoading { get; private set; } = true;
|
||||
|
||||
protected ProductListingModel Page { get; private set; } = new();
|
||||
|
||||
protected string PageTitle => string.IsNullOrWhiteSpace(Page.Category.Name)
|
||||
? "Products"
|
||||
: $"{Page.Category.Name} Products";
|
||||
|
||||
protected string LegendText
|
||||
{
|
||||
get
|
||||
{
|
||||
var total = EffectiveTotal;
|
||||
if (total == 0 || Page.Products.Count == 0)
|
||||
{
|
||||
return "Showing 0 products";
|
||||
}
|
||||
|
||||
var pageSize = EffectivePageSize;
|
||||
var startIndex = ((CurrentPage - 1) * pageSize) + 1;
|
||||
var endIndex = Math.Min(total, startIndex + Page.Products.Count - 1);
|
||||
|
||||
return $"Showing {startIndex}-{endIndex} of {total} products";
|
||||
}
|
||||
}
|
||||
|
||||
protected int CurrentPage => Page.Page > 0 ? Page.Page : 1;
|
||||
|
||||
protected int TotalPages => EffectiveTotal == 0 ? 0 : (int)Math.Ceiling(EffectiveTotal / (double)EffectivePageSize);
|
||||
|
||||
protected bool HasPreviousPage => CurrentPage > 1;
|
||||
|
||||
protected bool HasNextPage => TotalPages > CurrentPage;
|
||||
|
||||
protected IReadOnlyList<int> VisiblePageNumbers => BuildVisiblePages();
|
||||
|
||||
private int EffectiveTotal => Page.Total > 0 ? Page.Total : Page.Products.Count;
|
||||
|
||||
private int EffectivePageSize => Page.PageSize > 0 ? Page.PageSize : Math.Max(Page.Products.Count, 1);
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
var normalizedSlug = string.IsNullOrWhiteSpace(CategorySlug)
|
||||
? null
|
||||
: CategorySlug.Trim().ToLowerInvariant();
|
||||
|
||||
if (string.Equals(_currentSlug, normalizedSlug, StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentSlug = normalizedSlug;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(normalizedSlug))
|
||||
{
|
||||
Page = new ProductListingModel();
|
||||
IsLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
IsLoading = true;
|
||||
var result = await Execute(ct => ProductListingService.GetCategoryProducts(normalizedSlug, ct), CancellationToken.None);
|
||||
|
||||
Page = result.IsSuccess && result.Value is { } value
|
||||
? value
|
||||
: new ProductListingModel();
|
||||
|
||||
IsLoading = false;
|
||||
}
|
||||
|
||||
private IReadOnlyList<int> BuildVisiblePages()
|
||||
{
|
||||
if (TotalPages <= 1)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
const int maxPagesToShow = 5;
|
||||
var first = Math.Max(1, CurrentPage - 2);
|
||||
var last = Math.Min(TotalPages, first + maxPagesToShow - 1);
|
||||
first = Math.Max(1, last - maxPagesToShow + 1);
|
||||
|
||||
var pages = new List<int>(last - first + 1);
|
||||
for (var page = first; page <= last; page++)
|
||||
{
|
||||
pages.Add(page);
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user