diff --git a/src/RookieShop.ApiService/Endpoints/Reports/BestSellerProducts.cs b/src/RookieShop.ApiService/Endpoints/Reports/BestSellerProducts.cs index 86f450af..406b2755 100644 --- a/src/RookieShop.ApiService/Endpoints/Reports/BestSellerProducts.cs +++ b/src/RookieShop.ApiService/Endpoints/Reports/BestSellerProducts.cs @@ -1,6 +1,6 @@ using MediatR; using Microsoft.AspNetCore.Http.HttpResults; -using RookieShop.Application.Reports.DTOs; +using RookieShop.ApiService.ViewModels.Reports; using RookieShop.Application.Reports.Queries.BestSellerProducts; using RookieShop.Infrastructure.Endpoints.Abstractions; using RookieShop.Infrastructure.RateLimiter; @@ -8,24 +8,26 @@ namespace RookieShop.ApiService.Endpoints.Reports; public sealed class BestSellerProducts(ISender sender) - : IEndpoint>, BestSellerProductsRequest> + : IEndpoint>, BestSellerProductsRequest> { public void MapEndpoint(IEndpointRouteBuilder app) => app.MapGet("/reports/best-seller-products", async (int top) => await HandleAsync(new(top))) - .Produces>>() + .Produces>>() .WithTags(nameof(Reports)) .WithName("Best Seller Products") .MapToApiVersion(new(1, 0)) .RequirePerUserRateLimit(); - public async Task>> HandleAsync(BestSellerProductsRequest request, + public async Task>> HandleAsync(BestSellerProductsRequest request, CancellationToken cancellationToken = default) { BestSellerProductsQuery query = new(request.Top); - var result = await sender.Send(query, cancellationToken); + var data = await sender.Send(query, cancellationToken); - return TypedResults.Ok(result.Value.ToList()); + var result = data.Value.ToBestSellerProductsVm(); + + return TypedResults.Ok(result); } } \ No newline at end of file diff --git a/src/RookieShop.ApiService/Program.cs b/src/RookieShop.ApiService/Program.cs index 4ae561b1..c05244ec 100644 --- a/src/RookieShop.ApiService/Program.cs +++ b/src/RookieShop.ApiService/Program.cs @@ -29,6 +29,7 @@ .AddRouting(options => options.LowercaseUrls = true) .AddSingleton(new JsonSerializerOptions { + WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true }); diff --git a/src/RookieShop.ApiService/ViewModels/Reports/BestSellerProductsVm.cs b/src/RookieShop.ApiService/ViewModels/Reports/BestSellerProductsVm.cs new file mode 100644 index 00000000..7b4bbcbd --- /dev/null +++ b/src/RookieShop.ApiService/ViewModels/Reports/BestSellerProductsVm.cs @@ -0,0 +1,11 @@ +using RookieShop.Domain.Entities.ProductAggregator.ValueObjects; + +namespace RookieShop.ApiService.ViewModels.Reports; + +public sealed record BestSellerProductsVm( + Guid ProductId, + string? ProductName, + int TotalSoldQuantity, + ProductPrice? Price, + string? ImageUrl +); \ No newline at end of file diff --git a/src/RookieShop.ApiService/ViewModels/Reports/DtoToViewModelMapper.cs b/src/RookieShop.ApiService/ViewModels/Reports/DtoToViewModelMapper.cs new file mode 100644 index 00000000..924e419f --- /dev/null +++ b/src/RookieShop.ApiService/ViewModels/Reports/DtoToViewModelMapper.cs @@ -0,0 +1,24 @@ +using System.Text.Json; +using RookieShop.Application.Reports.DTOs; +using RookieShop.Domain.Entities.ProductAggregator.ValueObjects; + +namespace RookieShop.ApiService.ViewModels.Reports; + +public static class DtoToViewModelMapper +{ + public static BestSellerProductsVm ToBestSellerProductsVm(this BestSellerProductsDto bestSellerProductsDto) + { + var price = JsonSerializer.Deserialize(bestSellerProductsDto.Price); + + return new( + bestSellerProductsDto.ProductId, + bestSellerProductsDto.ProductName, + bestSellerProductsDto.TotalSoldQuantity, + price, + bestSellerProductsDto.ImageUrl + ); + } + + public static List ToBestSellerProductsVm(this IEnumerable bestSellerProductsDtos) => + bestSellerProductsDtos.Select(ToBestSellerProductsVm).ToList(); +} \ No newline at end of file diff --git a/src/RookieShop.Application/Reports/DTOs/BestSellerProductsDto.cs b/src/RookieShop.Application/Reports/DTOs/BestSellerProductsDto.cs index 9dd91324..507973dc 100644 --- a/src/RookieShop.Application/Reports/DTOs/BestSellerProductsDto.cs +++ b/src/RookieShop.Application/Reports/DTOs/BestSellerProductsDto.cs @@ -1,10 +1,13 @@ -namespace RookieShop.Application.Reports.DTOs; +using System.Text.Json.Serialization; + +namespace RookieShop.Application.Reports.DTOs; public sealed class BestSellerProductsDto { public Guid ProductId { get; set; } public string? ProductName { get; set; } public int TotalSoldQuantity { get; set; } + [JsonRequired] public string Price { get; set; } = string.Empty; public string? ImageUrl { get; set; } } \ No newline at end of file diff --git a/ui/storefront/Areas/Basket/Views/Shared/Components/CheckOut/Default.cshtml b/ui/storefront/Areas/Basket/Views/Shared/Components/CheckOut/Default.cshtml index acc65dc6..0d4b1313 100644 --- a/ui/storefront/Areas/Basket/Views/Shared/Components/CheckOut/Default.cshtml +++ b/ui/storefront/Areas/Basket/Views/Shared/Components/CheckOut/Default.cshtml @@ -128,7 +128,7 @@ Checkout - Paste + Paste Smart Paste \ No newline at end of file diff --git a/ui/storefront/Areas/Product/Models/Products/ProductPrice.cs b/ui/storefront/Areas/Product/Models/Products/ProductPrice.cs index b64e4d66..b0dec8f2 100644 --- a/ui/storefront/Areas/Product/Models/Products/ProductPrice.cs +++ b/ui/storefront/Areas/Product/Models/Products/ProductPrice.cs @@ -1,7 +1,12 @@ -namespace RookieShop.Storefront.Areas.Product.Models.Products; +using System.Text.Json.Serialization; + +namespace RookieShop.Storefront.Areas.Product.Models.Products; public sealed class ProductPrice { + [JsonPropertyName("price")] public decimal Price { get; set; } + + [JsonPropertyName("priceSale")] public decimal? PriceSale { get; set; } } \ No newline at end of file diff --git a/ui/storefront/Models/Report/BestSellerProduct.cs b/ui/storefront/Models/Report/BestSellerProduct.cs deleted file mode 100644 index 6a2264a3..00000000 --- a/ui/storefront/Models/Report/BestSellerProduct.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace RookieShop.Storefront.Models.Report; - -public class BestSellerProduct -{ - [JsonPropertyName("productId")] public Guid ProductId { get; set; } - - [JsonPropertyName("productName")] public string? ProductName { get; set; } - - [JsonPropertyName("totalSoldQuantity")] - public int TotalSoldQuantity { get; set; } - - [JsonPropertyName("imageUrl")] public string? ImageUrl { get; set; } -} \ No newline at end of file diff --git a/ui/storefront/Models/Report/BestSellerProductResponse.cs b/ui/storefront/Models/Report/BestSellerProductResponse.cs deleted file mode 100644 index dcf385e1..00000000 --- a/ui/storefront/Models/Report/BestSellerProductResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace RookieShop.Storefront.Models.Report; - -public sealed class BestSellerProductResponse : BestSellerProduct -{ - [JsonPropertyName("price")] - public string Price { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/ui/storefront/Models/Report/BestSellerProductViewModel.cs b/ui/storefront/Models/Report/BestSellerProductViewModel.cs index 20d62e08..d4d620ea 100644 --- a/ui/storefront/Models/Report/BestSellerProductViewModel.cs +++ b/ui/storefront/Models/Report/BestSellerProductViewModel.cs @@ -1,8 +1,19 @@ -using RookieShop.Storefront.Areas.Product.Models.Products; +using System.Text.Json.Serialization; +using RookieShop.Storefront.Areas.Product.Models.Products; namespace RookieShop.Storefront.Models.Report; -public sealed class BestSellerProductViewModel : BestSellerProduct +public sealed class BestSellerProductViewModel { + [JsonPropertyName("productId")] public Guid ProductId { get; set; } + + [JsonPropertyName("productName")] public string? ProductName { get; set; } + + [JsonPropertyName("totalSoldQuantity")] + public int TotalSoldQuantity { get; set; } + + [JsonPropertyName("imageUrl")] public string? ImageUrl { get; set; } + + [JsonPropertyName("price")] public ProductPrice? Price { get; set; } } \ No newline at end of file diff --git a/ui/storefront/Services/IReportService.cs b/ui/storefront/Services/IReportService.cs index 2b4f15b2..5f5808f4 100644 --- a/ui/storefront/Services/IReportService.cs +++ b/ui/storefront/Services/IReportService.cs @@ -6,5 +6,5 @@ namespace RookieShop.Storefront.Services; public interface IReportService { [Get("/reports/best-seller-products")] - Task > GetBestSellerProductsAsync([Query] BestSellerProductFilterParams filterParams); + Task > GetBestSellerProductsAsync([Query] BestSellerProductFilterParams filterParams); } \ No newline at end of file diff --git a/ui/storefront/Views/Shared/Components/FeatureProducts/Default.cshtml b/ui/storefront/Views/Shared/Components/FeatureProducts/Default.cshtml index 1c645f42..493d84f8 100644 --- a/ui/storefront/Views/Shared/Components/FeatureProducts/Default.cshtml +++ b/ui/storefront/Views/Shared/Components/FeatureProducts/Default.cshtml @@ -1,4 +1,6 @@ -@model List +@using System.Globalization +@using Microsoft.AspNetCore.Mvc.TagHelpers +@model List @if (Model.Count == 0) { @@ -21,19 +23,19 @@ else
- - Purchase - + + @item.Price?.PriceSale?.ToString("C", CultureInfo.CreateSpecificCulture("en-US")) +
} diff --git a/ui/storefront/Views/Shared/Components/FeatureProducts/FeatureProductsViewComponent.cs b/ui/storefront/Views/Shared/Components/FeatureProducts/FeatureProductsViewComponent.cs index 4c904bed..e98fac6d 100644 --- a/ui/storefront/Views/Shared/Components/FeatureProducts/FeatureProductsViewComponent.cs +++ b/ui/storefront/Views/Shared/Components/FeatureProducts/FeatureProductsViewComponent.cs @@ -1,7 +1,4 @@ -using System.Text.Json; -using Microsoft.AspNetCore.Mvc; -using RookieShop.Storefront.Areas.Product.Models.Products; -using RookieShop.Storefront.Models.Report; +using Microsoft.AspNetCore.Mvc; using RookieShop.Storefront.Services; namespace RookieShop.Storefront.Views.Shared.Components.FeatureProducts; @@ -13,15 +10,6 @@ public async Task InvokeAsync() { var data = await reportService.GetBestSellerProductsAsync(new()); - var result = data.Select(x => new BestSellerProductViewModel - { - ProductId = x.ProductId, - ProductName = x.ProductName, - TotalSoldQuantity = x.TotalSoldQuantity, - Price = JsonSerializer.Deserialize(x.Price), - ImageUrl = x.ImageUrl - }).ToList(); - - return View(result); + return View(data); } } \ No newline at end of file