From 46773d2505c86f2c7e4010441e14f6cd70f269e5 Mon Sep 17 00:00:00 2001 From: "Amir.H Khademi" Date: Wed, 18 Dec 2024 02:14:07 +0330 Subject: [PATCH] Add sub-product management feature Introduced a new feature for managing sub-products within the application. This includes: - Added a new tab panel for "Sub Products" in `ProductActionDialogBox.razor`. - Created `SubProductActionDialogBox` component for sub-product creation and editing. - Updated `ProductActionDialogBoxViewModel` to handle sub-product operations. - Modified `Address` class and `IProductRestApi` interface to support sub-products. - Added `SubProductController` for API requests related to sub-products. - Introduced command and query classes for sub-product operations. - Created `SubProductSDto` for sub-product data transfer. - Added `SubProduct` class inheriting from `Product` with additional properties. - Introduced `ProductDiversity` and `ColorDiversity` enums. - Added `SubProductMapper` for mapping between `SubProduct` and `SubProductSDto`. - Implemented command handlers for sub-product CRUD operations. - Added migration script `20241217212716_AddSubProduct` to update the database schema. - Updated `ProductController`, `SiteMapService`, and `Product` class to support sub-products. - Added `SubProductActionDialogBox.razor` for sub-product UI management. --- .../Dialogs/ProductActionDialogBox.razor | 80 ++++++++- .../Dialogs/ProductActionDialogBox.razor.cs | 101 ++++++++++- .../Dialogs/SubProductActionDialogBox.razor | 107 +++++++++++ .../SubProductActionDialogBox.razor.cs | 168 ++++++++++++++++++ Netina.AdminPanel.PWA/Models/Address.cs | 2 + .../Services/RestServices/IProductRestApi.cs | 3 + 6 files changed, 450 insertions(+), 11 deletions(-) create mode 100644 Netina.AdminPanel.PWA/Dialogs/SubProductActionDialogBox.razor create mode 100644 Netina.AdminPanel.PWA/Dialogs/SubProductActionDialogBox.razor.cs diff --git a/Netina.AdminPanel.PWA/Dialogs/ProductActionDialogBox.razor b/Netina.AdminPanel.PWA/Dialogs/ProductActionDialogBox.razor index 86932c1..8b06d24 100644 --- a/Netina.AdminPanel.PWA/Dialogs/ProductActionDialogBox.razor +++ b/Netina.AdminPanel.PWA/Dialogs/ProductActionDialogBox.razor @@ -12,10 +12,10 @@
- اطلاعات کلی اطلاعات کلی محصول را به دقت وارد کنید + @@ -177,6 +177,78 @@
+ + +
+ + + + + + + زیر محصولات + می توانید زیر محصولات مورد نظر را وارد کنید + + + + + + افزودن + + + + + + + + + + + + + + + ویرایش + حذف + + + + + + + + + + + + + +
+
+ @@ -215,8 +287,8 @@ SortMode="@SortMode.None" Groupable="false"> - - + + @@ -268,7 +340,7 @@ Size="@Size.Small" Variant="@Variant.Outlined" Color="@Color.Error" - OnClick="() => ViewModel.Faqs.Remove(item.Key)"/> + OnClick="() => ViewModel.Faqs.Remove(item.Key)" /> @item.Key diff --git a/Netina.AdminPanel.PWA/Dialogs/ProductActionDialogBox.razor.cs b/Netina.AdminPanel.PWA/Dialogs/ProductActionDialogBox.razor.cs index 0ea2c4d..d164d4d 100644 --- a/Netina.AdminPanel.PWA/Dialogs/ProductActionDialogBox.razor.cs +++ b/Netina.AdminPanel.PWA/Dialogs/ProductActionDialogBox.razor.cs @@ -1,4 +1,9 @@ -using Netina.Domain.Entities.Seo; +using Microsoft.AspNetCore.Components.Web; +using MudBlazor; +using Netina.AdminPanel.PWA.Services.RestServices; +using Netina.Domain.Dtos.LargDtos; +using Netina.Domain.Entities.Products; +using Netina.Domain.Entities.Seo; namespace Netina.AdminPanel.PWA.Dialogs; @@ -68,15 +73,17 @@ public class ProductActionDialogBoxViewModel : BaseViewModel IsAmountType = value; IsPercentType = value; } - else if(!IsAmountType) + else if (!IsAmountType) IsPercentType = true; } } public string SpecificationTitle = string.Empty; public string SpecificationValue = string.Empty; - public readonly ObservableCollection Specifications = new ObservableCollection(); - public readonly ObservableCollection Files = new ObservableCollection(); + public readonly ObservableCollection Specifications = []; + public readonly ObservableCollection Files = []; + public readonly ObservableCollection SubProducts = []; + public override async Task InitializeAsync() @@ -126,6 +133,8 @@ public class ProductActionDialogBoxViewModel : BaseViewModel StartDate = Discount.StartDate; IsSpecialOffer = true; } + + await FetchSubProducts(); } catch (ApiException ex) { @@ -148,6 +157,13 @@ public class ProductActionDialogBoxViewModel : BaseViewModel }; } + private async Task FetchSubProducts() + { + SubProducts.Clear(); + var subProducts = await _restWrapper.ProductRestApi.GetSubProductsAsync(PageDto.Id); + subProducts.ForEach(s => SubProducts.Add(s)); + } + public async Task SubmitEditAsync() { try @@ -217,6 +233,7 @@ public class ProductActionDialogBoxViewModel : BaseViewModel IsProcessing = false; } } + public async Task SubmitCreateAsync() { try @@ -305,7 +322,7 @@ public class ProductActionDialogBoxViewModel : BaseViewModel } } - private List _productCategories = new List(); + private List _productCategories = []; public ProductCategorySDto? SelectedCategory; public async Task> SearchProductCategory(string category) { @@ -332,7 +349,7 @@ public class ProductActionDialogBoxViewModel : BaseViewModel } } - private List _brands = new List(); + private List _brands = []; public BrandSDto? SelectedBrand; public async Task> SearchBrand(string brand) { @@ -379,7 +396,7 @@ public class ProductActionDialogBoxViewModel : BaseViewModel } } - public readonly ObservableCollection MetaTags = new(); + public readonly ObservableCollection MetaTags = []; public string MetaTagType { get; set; } = string.Empty; public string MetaTagValue { get; set; } = string.Empty; public void AddMetaTag() @@ -437,4 +454,74 @@ public class ProductActionDialogBoxViewModel : BaseViewModel { Files.Remove(file); } + + public async Task AddSubProduct(MouseEventArgs obj) + { + DialogOptions maxWidth = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, DisableBackdropClick = true }; + var parameters = new DialogParameters + { + { x => x.ProductId, PageDto.Id }, + { x => x.ProductName, PageDto.PersianName } + }; + var dialog = await _dialogService.ShowAsync($"افزودن زیر محصول به {PageDto.PersianName}", parameters, maxWidth); + var result = await dialog.Result; + if (!result.Canceled) + { + await FetchSubProducts(); + } + } + + public async Task EditSubProduct(SubProductSDto subProduct) + { + DialogOptions maxWidth = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, DisableBackdropClick = true }; + var parameters = new DialogParameters + { + { x => x.ProductId, PageDto.Id }, + { x => x.ProductName, PageDto.PersianName }, + { x => x.SubProduct , subProduct} + }; + var dialog = await _dialogService.ShowAsync($"افزودن زیر محصول به {PageDto.PersianName}", parameters, maxWidth); + var result = await dialog.Result; + if (!result.Canceled) + { + await FetchSubProducts(); + } + } + + public async Task DeleteSubProduct(SubProductSDto subProduct) + { + var reference = await _dialogService.ShowQuestionDialog($"آیا از حذف زیر محصول اطمینان دارید ?"); + var result = await reference.Result; + if (!result.Canceled) + { + + try + { + + IsProcessing = true; + var token = await _userUtility.GetBearerTokenAsync(); + if (token == null) + throw new AppException("Token is null"); + await _restWrapper.CrudDtoApiRest(Address.SubProductController) + .Delete(subProduct.Id, token); + _snackbar.Add("حذف زیر محصول با موفقیت انجام شد", Severity.Success); + SubProducts.Remove(subProduct); + } + catch (ApiException ex) + { + var exe = await ex.GetContentAsAsync(); + _snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error); + } + catch (Exception e) + { + _snackbar.Add(e.Message, Severity.Error); + } + finally + { + + IsProcessing = false; + } + } + } + } \ No newline at end of file diff --git a/Netina.AdminPanel.PWA/Dialogs/SubProductActionDialogBox.razor b/Netina.AdminPanel.PWA/Dialogs/SubProductActionDialogBox.razor new file mode 100644 index 0000000..2e05d42 --- /dev/null +++ b/Netina.AdminPanel.PWA/Dialogs/SubProductActionDialogBox.razor @@ -0,0 +1,107 @@ +@inject ISnackbar Snackbar +@inject IRestWrapper RestWrapper +@inject IUserUtility UserUtility +@inject IDialogService DialogService + + + + + + + + + + اطلاعات کلی + اطلاعات کلی زیر محصول را به دقت وارد کنید + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @if (ViewModel.IsEditing) + { + + } + else + { + + } + + بستن + + + +@code { + + [CascadingParameter] + MudDialogInstance MudDialog { get; set; } + + [Parameter] + public SubProductSDto? SubProduct { get; set; } + + [Parameter] + public string ProductName { get; set; } = string.Empty; + + [Parameter] + public Guid ProductId { get; set; } + + + public SubProductActionDialogBoxViewModel ViewModel { get; set; } + + protected override async Task OnInitializedAsync() + { + if (ProductId == default || string.IsNullOrEmpty(ProductName)) + return; + + ViewModel = SubProduct == null ? new SubProductActionDialogBoxViewModel(Snackbar, RestWrapper, UserUtility, DialogService, MudDialog, ProductId, ProductName) : + new SubProductActionDialogBoxViewModel(Snackbar, RestWrapper, UserUtility, DialogService, MudDialog, ProductId, ProductName, SubProduct); + await ViewModel.InitializeAsync(); + await base.OnInitializedAsync(); + } +} \ No newline at end of file diff --git a/Netina.AdminPanel.PWA/Dialogs/SubProductActionDialogBox.razor.cs b/Netina.AdminPanel.PWA/Dialogs/SubProductActionDialogBox.razor.cs new file mode 100644 index 0000000..3885a37 --- /dev/null +++ b/Netina.AdminPanel.PWA/Dialogs/SubProductActionDialogBox.razor.cs @@ -0,0 +1,168 @@ +namespace Netina.AdminPanel.PWA.Dialogs; + +public class SubProductActionDialogBoxViewModel : BaseViewModel +{ + private readonly ISnackbar _snackbar; + private readonly IRestWrapper _restWrapper; + private readonly IUserUtility _userUtility; + private readonly IDialogService _dialogService; + private readonly MudDialogInstance _mudDialog; + public SubProductActionDialogBoxViewModel(ISnackbar snackbar, + IRestWrapper restWrapper, + IUserUtility userUtility, + IDialogService dialogService, + MudDialogInstance mudDialog, + Guid productId, + string productName) : base(userUtility) + { + ProductId = productId; + ProductName = productName; + _snackbar = snackbar; + _restWrapper = restWrapper; + _userUtility = userUtility; + _dialogService = dialogService; + _mudDialog = mudDialog; + } + public SubProductActionDialogBoxViewModel(ISnackbar snackbar, + IRestWrapper restWrapper, + IUserUtility userUtility, + IDialogService dialogService, + MudDialogInstance mudDialog, + Guid productId, + string productName, + SubProductSDto subProduct) : base(userUtility) + { + _snackbar = snackbar; + _restWrapper = restWrapper; + _userUtility = userUtility; + _dialogService = dialogService; + _mudDialog = mudDialog; + ProductId = productId; + ProductName = productName; + SubProduct = subProduct; + PageDto = subProduct; + } + private SubProductSDto? _subProduct = null; + public SubProductSDto? SubProduct + { + get => _subProduct; + set + { + _subProduct = value; + if (_subProduct != null) + { + IsEditing = true; + } + } + } + public Guid ProductId { get; } + public string ProductName { get; } = string.Empty; + public void Cancel() => _mudDialog.Cancel(); + public bool IsEditing = false; + + public async Task SubmitCreateAsync() + { + try + { + if (ProductId == default) + throw new AppException("کالای ارسالی اشتباه است"); + + if (PageDto.Diversity == ProductDiversity.None) + throw new AppException("لطفا نوع تنوع را وارد کنید"); + + if (PageDto.DiversityValue.IsNullOrEmpty()) + throw new AppException("لطفا تنوع را وارد کنید"); + + PageDto.DiversityDescription = $"{PageDto.Diversity.ToDisplay()} {PageDto.DiversityValue}"; + + IsProcessing = true; + var token = await _userUtility.GetBearerTokenAsync(); + if (token == null) + throw new AppException("Token is null"); + var request = new CreateSubProductCommand( + ProductId, + PageDto.Diversity, + PageDto.DiversityValue, + PageDto.DiversityDescription, + PageDto.PersianName, + PageDto.Cost, + PageDto.PackingCost, + PageDto.Stock, + PageDto.HasExpressDelivery, + PageDto.MaxOrderCount, + new List()); + + await _restWrapper.CrudApiRest(Address.SubProductController).Create(request, token); + _mudDialog.Close(DialogResult.Ok(true)); + } + catch (ApiException ex) + { + var exe = await ex.GetContentAsAsync(); + _snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error); + } + catch (Exception e) + { + _snackbar.Add(e.Message, Severity.Error); + } + finally + { + IsProcessing = false; + } + } + + public async Task SubmitEditAsync() + { + try + { + if (SubProduct == null) + throw new AppException("محصول به درستی ارسال نشده است"); + + if (ProductId == default) + throw new AppException("کالای ارسالی اشتباه است"); + + if (PageDto.Diversity == ProductDiversity.None) + throw new AppException("لطفا نوع تنوع را وارد کنید"); + + if (PageDto.DiversityValue.IsNullOrEmpty()) + throw new AppException("لطفا تنوع را وارد کنید"); + + PageDto.DiversityDescription = $"{PageDto.Diversity.ToDisplay()} {PageDto.DiversityValue}"; + IsProcessing = true; + var token = await _userUtility.GetBearerTokenAsync(); + if (token == null) + throw new AppException("Token is null"); + var request = new UpdateSubProductCommand( + PageDto.Id, + PageDto.ParentId, + PageDto.Diversity, + PageDto.DiversityValue, + PageDto.DiversityDescription, + PageDto.PersianName, + PageDto.Cost, + PageDto.PackingCost, + PageDto.Stock, + PageDto.HasExpressDelivery, + PageDto.MaxOrderCount, + new List()); + + await _restWrapper.CrudApiRest(Address.SubProductController).Update(request, token); + _mudDialog.Close(); + } + catch (ApiException ex) + { + var exe = await ex.GetContentAsAsync(); + _snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error); + _mudDialog.Cancel(); + } + catch (Exception e) + { + _snackbar.Add(e.Message, Severity.Error); + _mudDialog.Cancel(); + } + finally + { + + IsProcessing = false; + } + } +} \ No newline at end of file diff --git a/Netina.AdminPanel.PWA/Models/Address.cs b/Netina.AdminPanel.PWA/Models/Address.cs index b62c0fc..152a55a 100644 --- a/Netina.AdminPanel.PWA/Models/Address.cs +++ b/Netina.AdminPanel.PWA/Models/Address.cs @@ -9,6 +9,8 @@ public static class Address public static string ProductCategoryController = $"/product/category"; public static string ProductController = $"/product"; public static string BrandController = $"/brand"; + + public static string SubProductController = $"/sub/product"; public static string FileController => $"/file"; public static string BlogController => $"/blog"; public static string BlogCategoryController => $"/blog/category"; diff --git a/Netina.AdminPanel.PWA/Services/RestServices/IProductRestApi.cs b/Netina.AdminPanel.PWA/Services/RestServices/IProductRestApi.cs index fb16565..f3f322d 100644 --- a/Netina.AdminPanel.PWA/Services/RestServices/IProductRestApi.cs +++ b/Netina.AdminPanel.PWA/Services/RestServices/IProductRestApi.cs @@ -12,6 +12,9 @@ public interface IProductRestApi [Get("/{productId}")] Task ReadOne(Guid productId); + [Get("/{productId}/sub")] + Task> GetSubProductsAsync(Guid productId); + [Get("")] Task ReadAll([Query] string productName, [Header("Authorization")] string authorization);