diff --git a/src/FrontOffice.Main/Pages/Store/ProductDetail.razor b/src/FrontOffice.Main/Pages/Store/ProductDetail.razor index 29f6126..9a2705c 100644 --- a/src/FrontOffice.Main/Pages/Store/ProductDetail.razor +++ b/src/FrontOffice.Main/Pages/Store/ProductDetail.razor @@ -52,6 +52,26 @@ else @_product.Title @_product.Description + @if (!string.IsNullOrWhiteSpace(_product.FullInformation)) + { + + @((MarkupString)_product.FullInformation) + + } + @if (_categoryPaths.Count > 0) + { + + دسته‌بندی‌ها + + @foreach (var categoryPath in _categoryPaths) + { + + @GetCategoryLabel(categoryPath) + + } + + + } @FormatPrice(_product.Price) diff --git a/src/FrontOffice.Main/Pages/Store/ProductDetail.razor.cs b/src/FrontOffice.Main/Pages/Store/ProductDetail.razor.cs index 31aa369..7c93d80 100644 --- a/src/FrontOffice.Main/Pages/Store/ProductDetail.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/ProductDetail.razor.cs @@ -19,6 +19,7 @@ public partial class ProductDetail : ComponentBase, IDisposable private const int MinQty = 1; private const int MaxQty = 20; private IReadOnlyList _galleryItems = Array.Empty(); + private IReadOnlyList _categoryPaths = Array.Empty(); private ProductGalleryImage? _selectedGalleryImage; private long TotalPrice => (_product?.Price ?? 0) * _qty; private bool HasDiscount => _product is { Discount: > 0 and < 100 }; @@ -36,6 +37,13 @@ public partial class ProductDetail : ComponentBase, IDisposable protected override async Task OnParametersSetAsync() { + + } + + protected override async Task OnInitializedAsync() + { + Cart.OnChange += HandleCartChanged; + _loading = true; _product = await ProductService.GetByIdAsync(id); _loading = false; @@ -43,20 +51,20 @@ public partial class ProductDetail : ComponentBase, IDisposable { _galleryItems = BuildGalleryItems(_product); _selectedGalleryImage = _galleryItems.FirstOrDefault(); + _categoryPaths = _product.Categories; _qty = Math.Clamp(CurrentCartItem?.Quantity ?? _qty, MinQty, MaxQty); } else { _galleryItems = Array.Empty(); + _categoryPaths = Array.Empty(); } StateHasChanged(); + await base.OnInitializedAsync(); } - protected override void OnInitialized() - { - Cart.OnChange += HandleCartChanged; - } + private async Task AddToCart() { @@ -163,4 +171,13 @@ public partial class ProductDetail : ComponentBase, IDisposable if (_selectedGalleryImage == item) return; _selectedGalleryImage = item; } + + private void NavigateToCategory(ProductCategoryPathInfo path) + { + var target = $"{RouteConstants.Store.Products}?category={path.CategoryId}"; + Navigation.NavigateTo(target); + } + + private string GetCategoryLabel(ProductCategoryPathInfo path) + => path.DisplayLabel; } \ No newline at end of file diff --git a/src/FrontOffice.Main/Utilities/ProductService.cs b/src/FrontOffice.Main/Utilities/ProductService.cs index 1173fb1..468c6de 100644 --- a/src/FrontOffice.Main/Utilities/ProductService.cs +++ b/src/FrontOffice.Main/Utilities/ProductService.cs @@ -1,4 +1,6 @@ using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; using FrontOffice.BFF.Products.Protobuf.Protos.Products; namespace FrontOffice.Main.Utilities; @@ -7,13 +9,18 @@ public record Product( long Id, string Title, string Description, + string FullInformation, string ImageUrl, long Price, - int Discount = 0, + int Discount = 1, int Rate = 0, int RemainingCount = 0) { - public IReadOnlyList Gallery { get; init; } = Array.Empty(); + public IReadOnlyList Gallery { get; init; } + = []; + + public IReadOnlyList Categories { get; init; } + = []; } public record ProductGalleryImage( @@ -23,16 +30,28 @@ public record ProductGalleryImage( string ImageUrl, string ThumbnailUrl); +public record ProductCategoryNodeInfo( + long Id, + string Title, + long? ParentId); + +public record ProductCategoryPathInfo( + long CategoryId, + string Title, + IReadOnlyList Nodes) +{ + public string DisplayLabel => string.Join(" › ", Nodes.Select(node => node.Title)); + public ProductCategoryNodeInfo Leaf => Nodes.Last(); +} + public class ProductService { - private readonly ConcurrentDictionary _cache = new(); private readonly ProductsContract.ProductsContractClient _client; public ProductService(ProductsContract.ProductsContractClient client) { _client = client; - } public async Task> GetProductsAsync(string? query = null) @@ -57,8 +76,11 @@ public class ProductService if (!string.IsNullOrWhiteSpace(query)) { var q = query.Trim(); - list = list.Where(p => p.Title.Contains(q, StringComparison.OrdinalIgnoreCase) || p.Description.Contains(q, StringComparison.OrdinalIgnoreCase)); + list = list.Where(p => + p.Title.Contains(q, StringComparison.OrdinalIgnoreCase) || + p.Description.Contains(q, StringComparison.OrdinalIgnoreCase)); } + return list.OrderBy(p => p.Id).ToList(); } } @@ -84,7 +106,8 @@ public class ProductService } } - private List MapAndCache(Google.Protobuf.Collections.RepeatedField models) + private List MapAndCache( + Google.Protobuf.Collections.RepeatedField models) { var list = new List(); foreach (var m in models) @@ -93,15 +116,20 @@ public class ProductService Id: m.Id, Title: m.Title ?? string.Empty, Description: m.Description ?? string.Empty, + FullInformation: m.FullInformation ?? string.Empty, ImageUrl: string.IsNullOrWhiteSpace(m.ImagePath) ? string.Empty : UrlUtility.DownloadUrl + m.ImagePath, Price: m.Price, Discount: m.Discount, Rate: m.Rate, RemainingCount: m.RemainingCount ); - _cache[p.Id] = p; + if (_cache.All(a => a.Key != p.Id)) + _cache[p.Id] = p; + + list.Add(p); } + return list; } @@ -120,18 +148,53 @@ public class ProductService Id: model.Id, Title: model.Title ?? string.Empty, Description: model.Description ?? string.Empty, + FullInformation: model.FullInformation ?? string.Empty, ImageUrl: BuildUrl(model.ImagePath), Price: model.Price, Discount: model.Discount, Rate: model.Rate, RemainingCount: model.RemainingCount) { - Gallery = gallery + Gallery = gallery, + Categories = MapCategoryPaths(model.Categories) }; _cache[product.Id] = product; return product; } - private static string BuildUrl(string? path) => string.IsNullOrWhiteSpace(path) ? string.Empty : UrlUtility.DownloadUrl + path; -} + private static string BuildUrl(string? path) => + string.IsNullOrWhiteSpace(path) ? string.Empty : UrlUtility.DownloadUrl + path; + + private static IReadOnlyList MapCategoryPaths(IEnumerable? categories) + { + if (categories is null) + { + return Array.Empty(); + } + + var result = new List(); + foreach (var category in categories) + { + var nodes = category.Path + .Select(node => new ProductCategoryNodeInfo( + Id: node.Id, + Title: node.Title ?? string.Empty, + ParentId: node.ParentId + )) + .ToList(); + + if (nodes.Count == 0) + { + continue; + } + + result.Add(new ProductCategoryPathInfo( + CategoryId: category.CategoryId, + Title: category.Title ?? string.Empty, + Nodes: nodes)); + } + + return result; + } +} \ No newline at end of file diff --git a/src/FrontOffice.Main/appsettings.json b/src/FrontOffice.Main/appsettings.json index 4760087..9384d8e 100644 --- a/src/FrontOffice.Main/appsettings.json +++ b/src/FrontOffice.Main/appsettings.json @@ -1,6 +1,6 @@ { - "GwUrl": "https://fogw.kbs1.ir", - //"GwUrl": "https://localhost:34781", +// "GwUrl": "https://fogw.kbs1.ir", + "GwUrl": "https://localhost:34781", "DownloadUrl": "https://dl.afrino.co", "EncryptionSettings": { "Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=",