Update product service and detail view with gallery support
This commit is contained in:
@@ -14,9 +14,8 @@
|
|||||||
<PackageReference Include="Foursat.FrontOffice.BFF.User.Protobuf" Version="0.0.116" />
|
<PackageReference Include="Foursat.FrontOffice.BFF.User.Protobuf" Version="0.0.116" />
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.UserAddress.Protobuf" Version="0.0.114" />
|
<PackageReference Include="Foursat.FrontOffice.BFF.UserAddress.Protobuf" Version="0.0.114" />
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.UserOrder.Protobuf" Version="0.0.114" />
|
<PackageReference Include="Foursat.FrontOffice.BFF.UserOrder.Protobuf" Version="0.0.114" />
|
||||||
<PackageReference Include="FrontOffice.BFF.Products.Protobuf" Version="0.0.11" />
|
<PackageReference Include="Foursat.FrontOffice.BFF.ShopingCart.Protobuf" Version="0.0.15" />
|
||||||
<PackageReference Include="FrontOffice.BFF.ShopingCart.Protobuf" Version="0.0.15" />
|
<PackageReference Include="Foursat.FrontOffice.BFF.UserWallet.Protobuf" Version="0.0.12" />
|
||||||
<PackageReference Include="FrontOffice.BFF.UserWallet.Protobuf" Version="0.0.11" />
|
|
||||||
<PackageReference Include="MudBlazor" Version="8.14.0" />
|
<PackageReference Include="MudBlazor" Version="8.14.0" />
|
||||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||||
<PackageReference Include="Mapster" Version="7.4.0" />
|
<PackageReference Include="Mapster" Version="7.4.0" />
|
||||||
@@ -27,6 +26,10 @@
|
|||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="../../../FrontOffice.BFF/src/Protobufs/FrontOffice.BFF.Products.Protobuf/FrontOffice.BFF.Products.Protobuf.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Components" />
|
<Folder Include="Components" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
{
|
{
|
||||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||||
<MudStack AlignItems="AlignItems.Center">
|
<MudStack AlignItems="AlignItems.Center">
|
||||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
|
<MudProgressCircular Color="Color.Primary" Indeterminate="true"/>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,9 @@ else if (_product is null)
|
|||||||
{
|
{
|
||||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||||
<MudAlert Severity="Severity.Warning">محصول یافت نشد.</MudAlert>
|
<MudAlert Severity="Severity.Warning">محصول یافت نشد.</MudAlert>
|
||||||
<MudButton Class="mt-2" Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Products)">بازگشت</MudButton>
|
<MudButton Class="mt-2" Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack"
|
||||||
|
OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Products)">بازگشت
|
||||||
|
</MudButton>
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -24,27 +26,48 @@ else
|
|||||||
<MudGrid Spacing="3">
|
<MudGrid Spacing="3">
|
||||||
<MudItem xs="12" md="6">
|
<MudItem xs="12" md="6">
|
||||||
<MudPaper Class="rounded-lg pa-2">
|
<MudPaper Class="rounded-lg pa-2">
|
||||||
<MudImage Src="@_product.ImageUrl" Alt="@_product.Title" Style="width:100%" Class="rounded-lg" />
|
@if (!string.IsNullOrWhiteSpace(MainImageUrl))
|
||||||
|
{
|
||||||
|
<div class="rounded-lg" style="width:100%;height:360px;background-image: url('@MainImageUrl');background-size: cover; background-position: center;border-radius: 0.5rem;color: transparent;" >@MainImageAlt</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Width="100%" Height="360px" />
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (_galleryItems.Count > 1)
|
||||||
|
{
|
||||||
|
<MudStack Row="true" Spacing="1" Class="mt-3" Style="overflow-x:auto;flex-wrap:wrap;">
|
||||||
|
@foreach (var item in _galleryItems)
|
||||||
|
{
|
||||||
|
<MudButton Variant="Variant.Text" Class="p-0" Style="min-width:0;" OnClick="() => SelectGalleryImage(item)">
|
||||||
|
<MudImage Src="@GetThumbnailUrl(item)" Alt="@item.Title" Class="rounded-sm" Style="@GetThumbnailStyle(item)" />
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="6">
|
<MudItem xs="12" md="6">
|
||||||
<MudStack Spacing="2">
|
<MudStack Spacing="2">
|
||||||
<MudText Typo="Typo.h4">@_product.Title</MudText>
|
<MudText Typo="Typo.h4">@_product.Title</MudText>
|
||||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">@_product.Description</MudText>
|
<MudText Typo="Typo.body1" Class="mud-text-secondary">@_product.Description</MudText>
|
||||||
<MudDivider Class="my-2" />
|
<MudDivider Class="my-2"/>
|
||||||
<MudText Typo="Typo.h5" Color="Color.Primary">@FormatPrice(_product.Price)</MudText>
|
<MudText Typo="Typo.h5" Color="Color.Primary">@FormatPrice(_product.Price)</MudText>
|
||||||
|
|
||||||
<!-- Desktop/tablet actions -->
|
<!-- Desktop/tablet actions -->
|
||||||
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
<MudNumericField T="int" @bind-Value="_qty" Min="1" Max="20" Immediate="true" HideSpinButtons="true" Style="max-width:120px" />
|
<MudNumericField T="int" @bind-Value="_qty" Min="@MinQty" Max="@MaxQty" Immediate="true"
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.AddShoppingCart" OnClick="AddToCart">افزودن به سبد</MudButton>
|
HideSpinButtons="true" Style="max-width:120px"/>
|
||||||
<MudButton Variant="Variant.Outlined" Color="Color.Secondary" OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Cart)">مشاهده سبد</MudButton>
|
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||||
|
StartIcon="@Icons.Material.Filled.AddShoppingCart" OnClick="AddToCart">افزودن به
|
||||||
|
سبد
|
||||||
|
</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudHidden>
|
</MudHidden>
|
||||||
|
|
||||||
<!-- Mobile actions: stacked and full-width -->
|
<!-- Mobile actions use sticky bar rendered below -->
|
||||||
|
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
</MudItem>
|
</MudItem>
|
||||||
@@ -52,11 +75,56 @@ else
|
|||||||
</MudGrid>
|
</MudGrid>
|
||||||
|
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
<MudHidden Breakpoint="Breakpoint.MdAndUp" Style="position: absolute; bottom: 0; left: 0; width: 100%">
|
<MudHidden Breakpoint="Breakpoint.MdAndUp">
|
||||||
<MudStack Spacing="2 " Class="d-flex flex-row-">
|
<MudPaper Class="mud-width-full mud-elevation-3 pa-3"
|
||||||
<MudNumericField T="int" @bind-Value="_qty" Min="1" Max="20" Immediate="true" HideSpinButtons="true" Class="w-100-mobile" />
|
Style="position:sticky;bottom:0;left:0;z-index:9;border-top-left-radius:1rem;border-top-right-radius:1rem;">
|
||||||
<MudButton Class="w-100-mobile" Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.AddShoppingCart" OnClick="AddToCart">افزودن به سبد</MudButton>
|
|
||||||
<MudButton Class="w-100-mobile" Variant="Variant.Outlined" Color="Color.Secondary" OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Cart)">مشاهده سبد</MudButton>
|
<MudGrid Class="align-center" Justify="Justify.SpaceBetween">
|
||||||
|
<MudItem xs="6">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
@if (HasDiscount && OriginalPrice is not null)
|
||||||
|
{
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
<MudText Typo="Typo.caption"
|
||||||
|
Class="mud-text-secondary mud-line-through">@FormatPrice(OriginalPrice.Value)</MudText>
|
||||||
|
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled" Size="Size.Small"
|
||||||
|
Label="true">@($"٪{_product!.Discount}")</MudChip>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
}
|
||||||
|
<MudText Typo="Typo.h6" Color="Color.Primary">@FormatPrice(TotalPrice)</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
@if (IsInCart)
|
||||||
|
{
|
||||||
|
<MudItem xs="5" Class="">
|
||||||
|
<MudPaper Class="mud-width-full d-flex align-center justify-space-between px-2"
|
||||||
|
Elevation="1" Style="border-radius:1rem;">
|
||||||
|
<MudIconButton
|
||||||
|
Icon="@(CurrentCartQuantity == 1 ? Icons.Material.Filled.Delete : Icons.Material.Filled.Remove)"
|
||||||
|
Color="Color.Error" Variant="Variant.Text" OnClick="RemoveFromCart"/>
|
||||||
|
<MudText Typo="Typo.subtitle2" Class="mud-font-weight-bold">@CurrentCartQuantity</MudText>
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Add" Color="Color.Primary"
|
||||||
|
Variant="Variant.Text" Disabled="@(CurrentCartQuantity >= MaxQty)"
|
||||||
|
OnClick="AddToCart"/>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudItem xs="6" >
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
|
||||||
|
<MudButton Variant="Variant.Filled" Size="Size.Large" Color="Color.Error" Class="mud-width-full"
|
||||||
|
StartIcon="@Icons.Material.Filled.AddShoppingCart" OnClick="AddToCart">
|
||||||
|
افزودن به سبد
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
|
||||||
|
</MudPaper>
|
||||||
</MudHidden>
|
</MudHidden>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using FrontOffice.Main.Utilities;
|
using FrontOffice.Main.Utilities;
|
||||||
|
|
||||||
namespace FrontOffice.Main.Pages.Store;
|
namespace FrontOffice.Main.Pages.Store;
|
||||||
|
|
||||||
public partial class ProductDetail : ComponentBase
|
public partial class ProductDetail : ComponentBase, IDisposable
|
||||||
{
|
{
|
||||||
[Inject] private ProductService ProductService { get; set; } = default!;
|
[Inject] private ProductService ProductService { get; set; } = default!;
|
||||||
[Inject] private CartService Cart { get; set; } = default!;
|
[Inject] private CartService Cart { get; set; } = default!;
|
||||||
@@ -13,19 +16,151 @@ public partial class ProductDetail : ComponentBase
|
|||||||
private Product? _product;
|
private Product? _product;
|
||||||
private bool _loading;
|
private bool _loading;
|
||||||
private int _qty = 1;
|
private int _qty = 1;
|
||||||
|
private const int MinQty = 1;
|
||||||
|
private const int MaxQty = 20;
|
||||||
|
private IReadOnlyList<ProductGalleryImage> _galleryItems = Array.Empty<ProductGalleryImage>();
|
||||||
|
private ProductGalleryImage? _selectedGalleryImage;
|
||||||
|
private long TotalPrice => (_product?.Price ?? 0) * _qty;
|
||||||
|
private bool HasDiscount => _product is { Discount: > 0 and < 100 };
|
||||||
|
|
||||||
|
private long? OriginalPrice => HasDiscount && _product is not null
|
||||||
|
? (long)Math.Round(_product.Price / (1 - (_product.Discount / 100m)))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
private CartItem? CurrentCartItem => _product is null
|
||||||
|
? null
|
||||||
|
: Cart.Items.FirstOrDefault(i => i.ProductId == _product.Id);
|
||||||
|
|
||||||
|
private bool IsInCart => CurrentCartItem is not null;
|
||||||
|
private int CurrentCartQuantity => CurrentCartItem?.Quantity ?? 0;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
_loading = true;
|
_loading = true;
|
||||||
_product = await ProductService.GetByIdAsync(id);
|
_product = await ProductService.GetByIdAsync(id);
|
||||||
_loading = false;
|
_loading = false;
|
||||||
|
if (_product is not null)
|
||||||
|
{
|
||||||
|
_galleryItems = BuildGalleryItems(_product);
|
||||||
|
_selectedGalleryImage = _galleryItems.FirstOrDefault();
|
||||||
|
_qty = Math.Clamp(CurrentCartItem?.Quantity ?? _qty, MinQty, MaxQty);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_galleryItems = Array.Empty<ProductGalleryImage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
Cart.OnChange += HandleCartChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddToCart()
|
private async Task AddToCart()
|
||||||
{
|
{
|
||||||
if (_product is null) return;
|
if (_product is null) return;
|
||||||
await Cart.Add(_product, _qty);
|
await Cart.Add(_product, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemoveFromCart()
|
||||||
|
{
|
||||||
|
if (_product is null) return;
|
||||||
|
_qty--;
|
||||||
|
await Cart.UpdateQuantity(CurrentCartItem.ProductId, _qty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IncreaseLocalQty()
|
||||||
|
{
|
||||||
|
if (_qty < MaxQty)
|
||||||
|
{
|
||||||
|
_qty++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DecreaseLocalQty()
|
||||||
|
{
|
||||||
|
if (_qty > MinQty)
|
||||||
|
{
|
||||||
|
_qty--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// private async Task IncreaseCartQuantityAsync()
|
||||||
|
// {
|
||||||
|
// if (_product is null || CurrentCartItem is null) return;
|
||||||
|
// var target = Math.Min(CurrentCartItem.Quantity + 1, MaxQty);
|
||||||
|
// if (target != CurrentCartItem.Quantity)
|
||||||
|
// {
|
||||||
|
// await Cart.UpdateQuantity(_product.Id, target);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private async Task RemoveFromCartAsync()
|
||||||
|
// {
|
||||||
|
// if (CurrentCartItem is null) return;
|
||||||
|
// await Cart.Remove(CurrentCartItem.cartId);
|
||||||
|
// }
|
||||||
|
|
||||||
|
private void HandleCartChanged()
|
||||||
|
{
|
||||||
|
if (_product is null) return;
|
||||||
|
_qty = Math.Clamp(CurrentCartItem?.Quantity ?? MinQty, MinQty, MaxQty);
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Cart.OnChange -= HandleCartChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
||||||
|
|
||||||
|
private static IReadOnlyList<ProductGalleryImage> BuildGalleryItems(Product product)
|
||||||
|
{
|
||||||
|
if (product.Gallery is { Count: > 0 })
|
||||||
|
{
|
||||||
|
var result = new List<ProductGalleryImage>();
|
||||||
|
result.Add(new ProductGalleryImage(0, 0, product.Title, product.ImageUrl, product.ImageUrl));
|
||||||
|
result.AddRange(product.Gallery.Where(x => !string.IsNullOrWhiteSpace(x.ImageUrl)).ToList());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(product.ImageUrl))
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
new ProductGalleryImage(0, 0, product.Title, product.ImageUrl, product.ImageUrl)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.Empty<ProductGalleryImage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string MainImageUrl => _selectedGalleryImage?.ImageUrl
|
||||||
|
?? _galleryItems.FirstOrDefault()?.ImageUrl
|
||||||
|
?? _product?.ImageUrl
|
||||||
|
?? string.Empty;
|
||||||
|
|
||||||
|
private string MainImageAlt => _selectedGalleryImage?.Title ?? _product?.Title ?? "تصویر محصول";
|
||||||
|
|
||||||
|
private string GetThumbnailUrl(ProductGalleryImage item)
|
||||||
|
=> string.IsNullOrWhiteSpace(item.ThumbnailUrl) ? item.ImageUrl : item.ThumbnailUrl;
|
||||||
|
|
||||||
|
private string GetThumbnailBorder(ProductGalleryImage item)
|
||||||
|
=> item == _selectedGalleryImage ? "2px solid rgba(30, 136, 229, 0.85)" : "1px solid rgba(0,0,0,0.12)";
|
||||||
|
|
||||||
|
private string GetThumbnailShadow(ProductGalleryImage item)
|
||||||
|
=> item == _selectedGalleryImage ? "0 0 0 2px rgba(30, 136, 229, 0.25)" : "none";
|
||||||
|
|
||||||
|
private string GetThumbnailStyle(ProductGalleryImage item)
|
||||||
|
=>
|
||||||
|
$"width:76px;height:76px;object-fit:cover;border:{GetThumbnailBorder(item)};box-shadow:{GetThumbnailShadow(item)};border-radius:0.5rem;";
|
||||||
|
|
||||||
|
private void SelectGalleryImage(ProductGalleryImage item)
|
||||||
|
{
|
||||||
|
if (_selectedGalleryImage == item) return;
|
||||||
|
_selectedGalleryImage = item;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,18 +11,20 @@ public record Product(
|
|||||||
long Price,
|
long Price,
|
||||||
int Discount = 0,
|
int Discount = 0,
|
||||||
int Rate = 0,
|
int Rate = 0,
|
||||||
int RemainingCount = 0);
|
int RemainingCount = 0)
|
||||||
|
{
|
||||||
|
public IReadOnlyList<ProductGalleryImage> Gallery { get; init; } = Array.Empty<ProductGalleryImage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ProductGalleryImage(
|
||||||
|
long ProductGalleryId,
|
||||||
|
long ProductImageId,
|
||||||
|
string Title,
|
||||||
|
string ImageUrl,
|
||||||
|
string ThumbnailUrl);
|
||||||
|
|
||||||
public class ProductService
|
public class ProductService
|
||||||
{
|
{
|
||||||
private static readonly List<Product> _seed = new()
|
|
||||||
{
|
|
||||||
new(1, "ماسک پزشکی سهلایه", "ماسک سهلایه با فیلتراسیون بالا مناسب برای محیطهای عمومی.", "/images/store/mask.jpg", 49000),
|
|
||||||
new(2, "دستکش لاتکس", "دستکش لاتکس مناسب معاینات پزشکی، بدون پودر.", "/images/store/gloves.jpg", 89000),
|
|
||||||
new(3, "محلول ضدعفونی کننده", "محلول ضدعفونی بر پایه الکل ۷۰٪ مناسب دست و سطوح.", "/images/store/sanitizer.jpg", 69000),
|
|
||||||
new(4, "تبسنج دیجیتال", "تبسنج دیجیتال با دقت بالا و نمایشگر LCD.", "/images/store/thermometer.jpg", 299000),
|
|
||||||
new(5, "فشارسنج دیجیتال", "فشارسنج بازویی دیجیتال با حافظه داخلی.", "/images/store/bp-monitor.jpg", 1259000)
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<long, Product> _cache = new();
|
private readonly ConcurrentDictionary<long, Product> _cache = new();
|
||||||
private readonly ProductsContract.ProductsContractClient _client;
|
private readonly ProductsContract.ProductsContractClient _client;
|
||||||
@@ -30,7 +32,7 @@ public class ProductService
|
|||||||
public ProductService(ProductsContract.ProductsContractClient client)
|
public ProductService(ProductsContract.ProductsContractClient client)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
foreach (var p in _seed) _cache[p.Id] = p;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Product>> GetProductsAsync(string? query = null)
|
public async Task<List<Product>> GetProductsAsync(string? query = null)
|
||||||
@@ -67,10 +69,13 @@ public class ProductService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// No single-get endpoint exposed: fetch list and pick
|
var resp = await _client.GetProductsAsync(new GetProductsRequest { Id = id });
|
||||||
var resp = await _client.GetAllProductsByFilterAsync(new GetAllProductsByFilterRequest { Filter = new GetAllProductsByFilterFilter() });
|
if (resp == null)
|
||||||
var list = MapAndCache(resp.Models);
|
{
|
||||||
return list.FirstOrDefault(x => x.Id == id);
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MapAndCache(resp);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -99,4 +104,34 @@ public class ProductService
|
|||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Product MapAndCache(GetProductsResponse model)
|
||||||
|
{
|
||||||
|
var gallery = model.Gallery
|
||||||
|
.Select(item => new ProductGalleryImage(
|
||||||
|
ProductGalleryId: item.ProductGalleryId,
|
||||||
|
ProductImageId: item.ProductImageId,
|
||||||
|
Title: item.Title ?? string.Empty,
|
||||||
|
ImageUrl: BuildUrl(item.ImagePath),
|
||||||
|
ThumbnailUrl: BuildUrl(item.ImageThumbnailPath)))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var product = new Product(
|
||||||
|
Id: model.Id,
|
||||||
|
Title: model.Title ?? string.Empty,
|
||||||
|
Description: model.Description ?? string.Empty,
|
||||||
|
ImageUrl: BuildUrl(model.ImagePath),
|
||||||
|
Price: model.Price,
|
||||||
|
Discount: model.Discount,
|
||||||
|
Rate: model.Rate,
|
||||||
|
RemainingCount: model.RemainingCount)
|
||||||
|
{
|
||||||
|
Gallery = gallery
|
||||||
|
};
|
||||||
|
|
||||||
|
_cache[product.Id] = product;
|
||||||
|
return product;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildUrl(string? path) => string.IsNullOrWhiteSpace(path) ? string.Empty : UrlUtility.DownloadUrl + path;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user