Merge remote-tracking branch 'origin/subProduct'

master
Amir Hossein Khademi 2025-03-27 13:29:53 +03:30
commit 7614b2d102
6 changed files with 450 additions and 11 deletions

View File

@ -12,10 +12,10 @@
<MudTabPanel class="h-full" Text="اطلاعات کلی" Icon="@Icons.Material.Outlined.Info"> <MudTabPanel class="h-full" Text="اطلاعات کلی" Icon="@Icons.Material.Outlined.Info">
<div class="h-full"> <div class="h-full">
<MudStack Spacing="0" class="mt-4"> <MudStack Spacing="0" class="mt-4">
<MudText Typo="Typo.h6">اطلاعات کلی</MudText> <MudText Typo="Typo.h6">اطلاعات کلی</MudText>
<MudText Typo="Typo.caption">اطلاعات کلی محصول را به دقت وارد کنید</MudText> <MudText Typo="Typo.caption">اطلاعات کلی محصول را به دقت وارد کنید</MudText>
</MudStack> </MudStack>
<MudGrid> <MudGrid>
<MudItem xs="12" lg="4" md="6"> <MudItem xs="12" lg="4" md="6">
@ -177,6 +177,78 @@
</div> </div>
</MudTabPanel> </MudTabPanel>
<MudTabPanel Text="زیر محصولاتــ" Icon="@Icons.Material.Outlined.ShoppingBag">
<div class="min-h-[33rem] mt-5">
<MudGrid>
<MudItem xs="10">
<MudStack Row="true" class="mb-5">
<MudStack>
<MudText Typo="Typo.h6">زیر محصولات</MudText>
<MudText Typo="Typo.caption">می توانید زیر محصولات مورد نظر را وارد کنید</MudText>
</MudStack>
</MudStack>
</MudItem>
<MudItem xs="2">
<MudButton Variant="Variant.Filled"
Size="Size.Large"
Color="Color.Info"
class="mt-2 w-full py-3"
OnClick="ViewModel.AddSubProduct"
StartIcon="@Icons.Material.Outlined.Add">افزودن</MudButton>
</MudItem>
<MudItem xs="12">
<MudGrid>
<MudItem sm="12">
<MudDataGrid Items="@ViewModel.SubProducts"
T="SubProductSDto"
Elevation="0"
Outlined="true"
Bordered="true"
Striped="true"
Filterable="false"
SortMode="@SortMode.None"
Groupable="false">
<Columns>
<PropertyColumn T="SubProductSDto" TProperty="string" Property="x => x.PersianName" Title="نام" />
<TemplateColumn T="SubProductSDto" CellClass="d-flex justify-end">
<CellTemplate>
<MudStack Row>
<MudButton DisableElevation="true"
Size="@Size.Small"
Variant="@Variant.Filled"
Color="@Color.Info"
OnClick="async()=>await ViewModel.EditSubProduct(context.Item)"
StartIcon="@Icons.Material.Outlined.Edit">ویرایش</MudButton>
<MudButton DisableElevation="true"
Size="@Size.Small"
Variant="@Variant.Filled"
Color="@Color.Error"
OnClick="async()=>await ViewModel.DeleteSubProduct(context.Item)"
StartIcon="@Icons.Material.Outlined.Remove">حذف</MudButton>
</MudStack>
</CellTemplate>
</TemplateColumn>
</Columns>
</MudDataGrid>
</MudItem>
</MudGrid>
</MudItem>
</MudGrid>
</div>
</MudTabPanel>
<MudTabPanel Text="متاتگ و سوالات متداول"> <MudTabPanel Text="متاتگ و سوالات متداول">
<MudGrid> <MudGrid>
@ -215,8 +287,8 @@
SortMode="@SortMode.None" SortMode="@SortMode.None"
Groupable="false"> Groupable="false">
<Columns> <Columns>
<PropertyColumn T="MetaTagSDto" TProperty="string" Property="x => x.Type" Title="نوع"/> <PropertyColumn T="MetaTagSDto" TProperty="string" Property="x => x.Type" Title="نوع" />
<PropertyColumn T="MetaTagSDto" TProperty="string" Property="x => x.Value" Title="مقدار"/> <PropertyColumn T="MetaTagSDto" TProperty="string" Property="x => x.Value" Title="مقدار" />
<TemplateColumn T="MetaTagSDto" CellClass="d-flex justify-end"> <TemplateColumn T="MetaTagSDto" CellClass="d-flex justify-end">
<CellTemplate> <CellTemplate>
<MudStack Row> <MudStack Row>
@ -268,7 +340,7 @@
Size="@Size.Small" Size="@Size.Small"
Variant="@Variant.Outlined" Variant="@Variant.Outlined"
Color="@Color.Error" Color="@Color.Error"
OnClick="() => ViewModel.Faqs.Remove(item.Key)"/> OnClick="() => ViewModel.Faqs.Remove(item.Key)" />
<MudText>@item.Key</MudText> <MudText>@item.Key</MudText>
</MudStack> </MudStack>
</TitleContent> </TitleContent>

View File

@ -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; namespace Netina.AdminPanel.PWA.Dialogs;
@ -68,15 +73,17 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
IsAmountType = value; IsAmountType = value;
IsPercentType = value; IsPercentType = value;
} }
else if(!IsAmountType) else if (!IsAmountType)
IsPercentType = true; IsPercentType = true;
} }
} }
public string SpecificationTitle = string.Empty; public string SpecificationTitle = string.Empty;
public string SpecificationValue = string.Empty; public string SpecificationValue = string.Empty;
public readonly ObservableCollection<SpecificationSDto> Specifications = new ObservableCollection<SpecificationSDto>(); public readonly ObservableCollection<SpecificationSDto> Specifications = [];
public readonly ObservableCollection<StorageFileSDto> Files = new ObservableCollection<StorageFileSDto>(); public readonly ObservableCollection<StorageFileSDto> Files = [];
public readonly ObservableCollection<SubProductSDto> SubProducts = [];
public override async Task InitializeAsync() public override async Task InitializeAsync()
@ -126,6 +133,8 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
StartDate = Discount.StartDate; StartDate = Discount.StartDate;
IsSpecialOffer = true; IsSpecialOffer = true;
} }
await FetchSubProducts();
} }
catch (ApiException ex) catch (ApiException ex)
{ {
@ -148,6 +157,13 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
}; };
} }
private async Task FetchSubProducts()
{
SubProducts.Clear();
var subProducts = await _restWrapper.ProductRestApi.GetSubProductsAsync(PageDto.Id);
subProducts.ForEach(s => SubProducts.Add(s));
}
public async Task SubmitEditAsync() public async Task SubmitEditAsync()
{ {
try try
@ -217,6 +233,7 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
IsProcessing = false; IsProcessing = false;
} }
} }
public async Task SubmitCreateAsync() public async Task SubmitCreateAsync()
{ {
try try
@ -305,7 +322,7 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
} }
} }
private List<ProductCategorySDto> _productCategories = new List<ProductCategorySDto>(); private List<ProductCategorySDto> _productCategories = [];
public ProductCategorySDto? SelectedCategory; public ProductCategorySDto? SelectedCategory;
public async Task<IEnumerable<ProductCategorySDto>> SearchProductCategory(string category) public async Task<IEnumerable<ProductCategorySDto>> SearchProductCategory(string category)
{ {
@ -332,7 +349,7 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
} }
} }
private List<BrandSDto> _brands = new List<BrandSDto>(); private List<BrandSDto> _brands = [];
public BrandSDto? SelectedBrand; public BrandSDto? SelectedBrand;
public async Task<IEnumerable<BrandSDto>> SearchBrand(string brand) public async Task<IEnumerable<BrandSDto>> SearchBrand(string brand)
{ {
@ -379,7 +396,7 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
} }
} }
public readonly ObservableCollection<MetaTagSDto> MetaTags = new(); public readonly ObservableCollection<MetaTagSDto> MetaTags = [];
public string MetaTagType { get; set; } = string.Empty; public string MetaTagType { get; set; } = string.Empty;
public string MetaTagValue { get; set; } = string.Empty; public string MetaTagValue { get; set; } = string.Empty;
public void AddMetaTag() public void AddMetaTag()
@ -437,4 +454,74 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
{ {
Files.Remove(file); Files.Remove(file);
} }
public async Task AddSubProduct(MouseEventArgs obj)
{
DialogOptions maxWidth = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, DisableBackdropClick = true };
var parameters = new DialogParameters<SubProductActionDialogBox>
{
{ x => x.ProductId, PageDto.Id },
{ x => x.ProductName, PageDto.PersianName }
};
var dialog = await _dialogService.ShowAsync<SubProductActionDialogBox>($"افزودن زیر محصول به {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<SubProductActionDialogBox>
{
{ x => x.ProductId, PageDto.Id },
{ x => x.ProductName, PageDto.PersianName },
{ x => x.SubProduct , subProduct}
};
var dialog = await _dialogService.ShowAsync<SubProductActionDialogBox>($"افزودن زیر محصول به {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<SubProduct, SubProductSDto, Guid>(Address.SubProductController)
.Delete(subProduct.Id, token);
_snackbar.Add("حذف زیر محصول با موفقیت انجام شد", Severity.Success);
SubProducts.Remove(subProduct);
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
_snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error);
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
}
finally
{
IsProcessing = false;
}
}
}
} }

View File

@ -0,0 +1,107 @@
@inject ISnackbar Snackbar
@inject IRestWrapper RestWrapper
@inject IUserUtility UserUtility
@inject IDialogService DialogService
<MudDialog class="mx-auto">
<DialogContent>
<MudContainer class="h-full p-0">
<MudTabs Outlined="true" Elevation="0" Rounded="true" Centered="true">
<MudTabPanel Text="اطلاعات کلی" Icon="@Icons.Material.Outlined.Info">
<MudStack>
<MudStack Spacing="0">
<MudText Typo="Typo.h6">اطلاعات کلی</MudText>
<MudText Typo="Typo.caption">اطلاعات کلی زیر محصول را به دقت وارد کنید</MudText>
</MudStack>
<MudGrid>
<MudItem xs="12" lg="4" md="6">
<MudSelect Variant="Variant.Outlined" T="ProductDiversity"
ToStringFunc="type => type.ToDisplay()"
Label="نوع تنوع"
@bind-Value="@ViewModel.PageDto.Diversity">
<MudSelectItem T="ProductDiversity" Value="ProductDiversity.None"></MudSelectItem>
<MudSelectItem T="ProductDiversity" Value="ProductDiversity.Color"></MudSelectItem>
<MudSelectItem T="ProductDiversity" Value="ProductDiversity.Guarantee"></MudSelectItem>
<MudSelectItem T="ProductDiversity" Value="ProductDiversity.Size"></MudSelectItem>
</MudSelect>
</MudItem>
<MudItem xs="12" lg="4" md="6">
<MudTextField @bind-Value="@ViewModel.PageDto.DiversityValue" T="string" Label="تنوع" Format="N0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12" lg="4" md="6">
<MudTextField @bind-Value="@ViewModel.PageDto.Cost" T="double" Label="قیمت محصول" Adornment="Adornment.End" Format="N0" AdornmentText="ریالــ" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12" lg="4" md="6">
<MudTextField @bind-Value="@ViewModel.PageDto.PackingCost" T="double" Label="مبلغ بسته بندی" Format="N0" Adornment="Adornment.End" AdornmentText="ریالــ" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12" lg="4" md="6">
<MudTextField @bind-Value="@ViewModel.PageDto.Stock" T="int" Format="N0" Label="موجودی انبار" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12" lg="4" md="6">
<MudTextField @bind-Value="@ViewModel.PageDto.MaxOrderCount" T="int" Format="N0" Label="بیشترین خرید" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12" lg="4" md="6">
<MudSelect T="bool" @bind-Value="@ViewModel.PageDto.HasExpressDelivery" Label="آیا ارسال سریع دارد ؟" ToStringFunc="b=>b.ToPersianString()" Variant="Variant.Outlined" AnchorOrigin="Origin.BottomCenter">
<MudSelectItem T="bool" Value="true" />
<MudSelectItem T="bool" Value="false" />
</MudSelect>
</MudItem>
</MudGrid>
</MudStack>
</MudTabPanel>
</MudTabs>
</MudContainer>
</DialogContent>
<DialogActions>
<MudStack Row="true" class="w-full mx-4 mb-2">
@if (ViewModel.IsEditing)
{
<BaseButtonUi class="w-64 rounded-md" IsProcessing="@ViewModel.IsProcessing"
Icon="@Icons.Material.Outlined.Check"
Variant="Variant.Filled" Color="Color.Success"
Content="ثبت ویرایش" OnClickCallback="ViewModel.SubmitEditAsync" />
}
else
{
<BaseButtonUi class="w-64 rounded-md" IsProcessing="@ViewModel.IsProcessing"
Icon="@Icons.Material.Outlined.Check"
Variant="Variant.Filled" Color="Color.Success"
Content="تایید" OnClickCallback="ViewModel.SubmitCreateAsync" />
}
<MudSpacer />
<MudButton Variant="Variant.Outlined" Size="Size.Large" Color="Color.Error" OnClick="ViewModel.Cancel">بستن</MudButton>
</MudStack>
</DialogActions>
</MudDialog>
@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();
}
}

View File

@ -0,0 +1,168 @@
namespace Netina.AdminPanel.PWA.Dialogs;
public class SubProductActionDialogBoxViewModel : BaseViewModel<SubProductSDto>
{
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<StorageFileSDto>());
await _restWrapper.CrudApiRest<SubProduct, Guid>(Address.SubProductController).Create<CreateSubProductCommand>(request, token);
_mudDialog.Close(DialogResult.Ok(true));
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
_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<StorageFileSDto>());
await _restWrapper.CrudApiRest<SubProduct, Guid>(Address.SubProductController).Update<UpdateSubProductCommand>(request, token);
_mudDialog.Close();
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
_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;
}
}
}

View File

@ -9,6 +9,8 @@ public static class Address
public static string ProductCategoryController = $"/product/category"; public static string ProductCategoryController = $"/product/category";
public static string ProductController = $"/product"; public static string ProductController = $"/product";
public static string BrandController = $"/brand"; public static string BrandController = $"/brand";
public static string SubProductController = $"/sub/product";
public static string FileController => $"/file"; public static string FileController => $"/file";
public static string BlogController => $"/blog"; public static string BlogController => $"/blog";
public static string BlogCategoryController => $"/blog/category"; public static string BlogCategoryController => $"/blog/category";

View File

@ -12,6 +12,9 @@ public interface IProductRestApi
[Get("/{productId}")] [Get("/{productId}")]
Task<GetProductResponseDto> ReadOne(Guid productId); Task<GetProductResponseDto> ReadOne(Guid productId);
[Get("/{productId}/sub")]
Task<List<SubProductSDto>> GetSubProductsAsync(Guid productId);
[Get("")] [Get("")]
Task<GetProductsResponseDto> ReadAll([Query] string productName, [Header("Authorization")] string authorization); Task<GetProductsResponseDto> ReadAll([Query] string productName, [Header("Authorization")] string authorization);