feat : complete create and edit product , add file controller

add upload and get files in file controller , complete create and edit product with uploading images
release
Amir Hossein Khademi 2024-01-30 02:33:27 +03:30
parent 639d858476
commit d6e0983f13
15 changed files with 887 additions and 92 deletions

View File

@ -1,105 +1,270 @@
@using Radzen.Blazor @using Radzen.Blazor
@using NetinaShop.AdminPanel.PWA.Extensions
@inject ISnackbar Snackbar
@inject IRestWrapper RestWrapper
@inject IUserUtility UserUtility
@inject IDialogService DialogService
<head>
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
</head>
<MudDialog class="mx-auto"> <MudDialog class="mx-auto">
<DialogContent> <DialogContent>
<MudStack> <MudStack>
<MudDivider/> <MudTabs Outlined="true" Elevation="0" Rounded="true" Centered="true">
<MudStack Spacing="0"> <MudTabPanel Text="اطلاعات کلی" Icon="@Icons.Material.Outlined.Info">
<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 lg="4" md="6">
<MudTextField T="string" Label="نام فارسی محصول" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="4" md="6">
<MudTextField T="string" Label="نام انگلیسی محصول" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="4" md="6">
<MudTextField T="string" Label="قیمت محصول" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="4" md="6">
<MudTextField T="string" Label="مبلغ بسته بندی" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="4" md="6">
<MudTextField T="string" Label="بیشترین خرید" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="4" md="6">
<MudTextField T="string" Label="ایا ارسال سریع دارد" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="12" md="12">
<MudTextField T="string" Label="توضیحاتــ" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudStack class="mt-4 mx-4" Spacing="0"> <MudItem lg="4" md="6">
<MudTextField @bind-Value="@ViewModel.PersianName" T="string" Label="نام فارسی محصول" Variant="Variant.Outlined" />
</MudItem>
<MudItem lg="4" md="6">
<MudTextField @bind-Value="@ViewModel.EnglishName" T="string" Label="نام انگلیسی محصول" Variant="Variant.Outlined" />
</MudItem>
<MudItem lg="4" md="6">
<MudText Typo="Typo.h6">توضیحات تکمیلی</MudText> <MudAutocomplete Required="true" ToStringFunc="dto => dto.Name" @bind-Value="@ViewModel._selectedCategory"
<MudText Typo="Typo.caption">می توانید توضیحاتــ تکمیلی محصول را کامل وارد کنید</MudText> SearchFunc="ViewModel.SearchProductCategory"
</MudStack> T="ProductCategorySDto"
<MudItem lg="12" md="12"> Label="دسته بندی"
<RadzenHtmlEditor class="min-h-[10rem]"> Variant="Variant.Outlined">
<RadzenHtmlEditorUndo /> <ProgressIndicatorInPopoverTemplate>
<RadzenHtmlEditorRedo /> <MudList Clickable="false">
<RadzenHtmlEditorSeparator /> <MudListItem>
<div class="flex flex-row w-full mx-auto">
<MudProgressCircular class="my-auto mr-1 -ml-4" Size="Size.Small" Indeterminate="true" />
<p class="font-bold my-1 mx-auto text-md">منتظر بمانید</p>
</div>
</MudListItem>
</MudList>
</ProgressIndicatorInPopoverTemplate>
<ItemTemplate Context="e">
<p>@e.Name</p>
</ItemTemplate>
</MudAutocomplete>
</MudItem>
<MudItem lg="4" md="6">
<RadzenHtmlEditorFormatBlock /> <MudAutocomplete Required="true" ToStringFunc="dto => dto.Name" @bind-Value="@ViewModel._selectedBrand"
<RadzenHtmlEditorSeparator /> SearchFunc="ViewModel.SearchBrand"
T="BrandSDto"
Label="برند"
Variant="Variant.Outlined">
<ProgressIndicatorInPopoverTemplate>
<MudList Clickable="false">
<MudListItem>
<div class="flex flex-row w-full mx-auto">
<MudProgressCircular class="my-auto mr-1 -ml-4" Size="Size.Small" Indeterminate="true" />
<p class="font-bold my-1 mx-auto text-md">منتظر بمانید</p>
</div>
</MudListItem>
</MudList>
</ProgressIndicatorInPopoverTemplate>
<ItemTemplate Context="e">
<p>@e.Name</p>
</ItemTemplate>
</MudAutocomplete>
</MudItem>
<MudItem lg="4" md="6">
<MudTextField @bind-Value="@ViewModel.Cost" T="double" Label="قیمت محصول" Adornment="Adornment.End" AdornmentText="ریالــ" Variant="Variant.Outlined" />
</MudItem>
<MudItem lg="4" md="6">
<MudTextField @bind-Value="@ViewModel.PackingCost" T="double" Label="مبلغ بسته بندی" Adornment="Adornment.End" AdornmentText="ریالــ" Variant="Variant.Outlined" />
</MudItem>
<MudItem lg="4" md="6">
<MudTextField @bind-Value="@ViewModel.MaxOrder" T="int" Label="بیشترین خرید" Variant="Variant.Outlined" />
</MudItem>
<MudItem lg="4" md="6">
<MudTextField @bind-Value="@ViewModel.Warranty" T="string" Label="گارانتی" Variant="Variant.Outlined" />
</MudItem>
<MudItem lg="4" md="6">
<MudSelect T="bool" @bind-Value="@ViewModel.HasExpressDelivery" Label="آیا ارسال سریع دارد ؟" ToStringFunc="b=>b.ToPersianString()" Variant="Variant.Outlined" AnchorOrigin="Origin.BottomCenter">
<MudSelectItem T="bool" Value="true" />
<MudSelectItem T="bool" Value="false" />
</MudSelect>
</MudItem>
<MudItem lg="4" md="6">
<MudSelect T="bool" @bind-Value="@ViewModel.BeDisplayed" Label="آیا نمایش داده شود است ؟" ToStringFunc="b=>b.ToPersianString()" Variant="Variant.Outlined" AnchorOrigin="Origin.BottomCenter">
<MudSelectItem T="bool" Value="true" />
<MudSelectItem T="bool" Value="false" />
</MudSelect>
</MudItem>
<MudItem lg="12" md="12">
<MudStack>
<MudTextField @bind-Value="@ViewModel.Tags" T="string" Label="تگ ها" HelperText="تگ ها را با , میتوانید جدا کنید" HelperTextOnFocus="true" Variant="Variant.Outlined" />
</MudStack>
</MudItem>
<MudItem lg="12" md="12">
<MudTextField class="-mt-3" @bind-Value="@ViewModel.Summery" T="string" Label="توضیحاتــ" Variant="Variant.Outlined"></MudTextField>
</MudItem>
</MudGrid>
</MudTabPanel>
<MudTabPanel Text="ویژگی های کلی" Icon="@Icons.Material.Outlined.AutoGraph">
<RadzenHtmlEditorAlignLeft /> <MudStack class="mt-4" Spacing="0">
<RadzenHtmlEditorAlignCenter />
<RadzenHtmlEditorAlignRight />
<RadzenHtmlEditorJustify />
<RadzenHtmlEditorSeparator />
<RadzenHtmlEditorSource /> <MudText Typo="Typo.h6">ویژگی های کلی</MudText>
<RadzenHtmlEditorBold /> <MudText Typo="Typo.caption">می توانید ویگی های تکمیلی محصول را کامل وارد کنید</MudText>
<RadzenHtmlEditorItalic /> </MudStack>
<RadzenHtmlEditorUnderline /> <MudGrid>
<RadzenHtmlEditorStrikeThrough />
<RadzenHtmlEditorRemoveFormat />
<RadzenHtmlEditorSeparator />
</RadzenHtmlEditor> <MudItem lg="4" md="6">
<MudTextField @bind-Value="@ViewModel.SpecificationTitle" T="string" Label="عنوان" Variant="Variant.Outlined"></MudTextField>
</MudItem>
</MudItem> <MudItem lg="4" md="6">
<MudTextField @bind-Value="@ViewModel.SpecificationValue" T="string" Label="مقدار" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudStack class="mt-4 mb-10 mx-4" Spacing="0"> <MudItem lg="4" md="12">
<MudButton Variant="Variant.Filled"
Size="Size.Large"
Color="Color.Info"
class="w-full py-3 mt-2"
OnClick="ViewModel.AddSpecification"
StartIcon="@Icons.Material.Outlined.Add">افزودن</MudButton>
</MudItem>
<MudText Typo="Typo.h6">تصاویر محصول</MudText> <MudItem sm="12">
<MudText Typo="Typo.caption">می توانید برای محصول چند تصویر اپلود کنید</MudText> <MudDataGrid Items="@ViewModel.Specifications" Elevation="0" Outlined="true" Bordered="true" Striped="true" Filterable="false" SortMode="@SortMode.None" Groupable="false">
</MudStack> <Columns>
</MudGrid> <PropertyColumn Property="x => x.Title" Title="عنوان" />
<PropertyColumn Property="x => x.Value" Title="مقدار" />
<TemplateColumn T="SpecificationSDto" CellClass="d-flex justify-end">
<CellTemplate>
<MudStack Row>
<MudButton DisableElevation="true"
Size="@Size.Small"
Variant="@Variant.Filled"
OnClick="()=>ViewModel.RemoveSpecification(context.Item)"
Color="@Color.Error"
StartIcon="@Icons.Material.Outlined.Delete">حذف</MudButton>
</MudStack>
</CellTemplate>
</TemplateColumn>
</Columns>
</MudDataGrid>
</MudItem>
</MudGrid>
</MudTabPanel>
<MudTabPanel Text="توضیحات تکمیلی" Icon="@Icons.Material.Outlined.Article">
<MudStack class="mt-4" Spacing="0">
<MudText Typo="Typo.h6">توضیحات تکمیلی</MudText>
<MudText Typo="Typo.caption">می توانید توضیحاتــ تکمیلی محصول را کامل وارد کنید</MudText>
</MudStack>
<MudGrid>
<MudItem sm="12">
<RadzenHtmlEditor @bind-Value="@ViewModel.ExpertCheck" class="min-h-[10rem]">
<RadzenHtmlEditorUndo />
<RadzenHtmlEditorRedo />
<RadzenHtmlEditorSeparator />
<RadzenHtmlEditorFormatBlock />
<RadzenHtmlEditorSeparator />
<RadzenHtmlEditorAlignLeft />
<RadzenHtmlEditorAlignCenter />
<RadzenHtmlEditorAlignRight />
<RadzenHtmlEditorJustify />
<RadzenHtmlEditorSeparator />
<RadzenHtmlEditorSource />
<RadzenHtmlEditorBold />
<RadzenHtmlEditorItalic />
<RadzenHtmlEditorUnderline />
<RadzenHtmlEditorStrikeThrough />
<RadzenHtmlEditorRemoveFormat />
<RadzenHtmlEditorSeparator />
</RadzenHtmlEditor>
</MudItem>
</MudGrid>
</MudTabPanel>
<MudTabPanel Text="تـــــصاویر" Icon="@Icons.Material.Outlined.ImageSearch">
<MudStack class="mt-4 mb-2" Spacing="0">
<MudText Typo="Typo.h6">تصاویر محصول</MudText>
<MudText Typo="Typo.caption">می توانید برای محصول چند تصویر اپلود کنید</MudText>
</MudStack>
<MudStack Row="true">
<MudIconButton HtmlTag="label"
Color="Color.Info"
Variant="Variant.Outlined"
class="w-28 h-28"
Size="Size.Large"
Icon="@Icons.Material.Outlined.Wallpaper"
OnClick="async () => await ViewModel.SelectFileAsync()">
</MudIconButton>
@foreach (var item in ViewModel.Files)
{
<div class="w-28 h-28">
<MudImage Src="@item.GetLink()" Elevation="25" Class="rounded-lg w-28 h-28 absolute" />
<MudIconButton DisableElevation="true"
class="absolute m-1.5"
Size="@Size.Small"
Variant="@Variant.Filled"
OnClick="() => ViewModel.RemoveFile(item)"
Color="@Color.Error"
Icon="@Icons.Material.Outlined.Delete" />
</div>
}
</MudStack>
</MudTabPanel>
</MudTabs>
</MudStack> </MudStack>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<MudStack class="w-full" Row="true"> <MudStack Row="true" class="w-full mx-4 mb-2">
<MudButton DisableElevation="true" Size="Size.Large" StartIcon="@Icons.Material.Outlined.Check" Variant="Variant.Filled" Color="Color.Success" OnClick="Submit">تایید</MudButton>
<MudButton Variant="Variant.Outlined" Size="Size.Large" Color="Color.Error" OnClick="Cancel">بستن</MudButton>
</MudStack> @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> </DialogActions>
</MudDialog> </MudDialog>
@code { @code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; } [CascadingParameter]
MudDialogInstance MudDialog { get; set; }
void Submit() => MudDialog.Close(DialogResult.Ok(true));
void Cancel() => MudDialog.Cancel();
private bool _isEditing = false;
private bool _isProcessing = false;
private ProductSDto? _product = null;
[Parameter] [Parameter]
public ProductSDto? Product public ProductSDto? Product { get; set; }
public ProductActionDialogBoxViewModel ViewModel { get; set; }
protected override async Task OnInitializedAsync()
{ {
get => _product; if (Product == null)
set ViewModel = new ProductActionDialogBoxViewModel(Snackbar, RestWrapper, UserUtility, DialogService, MudDialog);
{ else
_product = value; ViewModel = new ProductActionDialogBoxViewModel(Snackbar, RestWrapper, UserUtility, DialogService, MudDialog, Product);
if (_product != null) await ViewModel.InitializeAsync();
{ await base.OnInitializedAsync();
_isEditing = true;
}
}
} }
} }

View File

@ -0,0 +1,272 @@
namespace NetinaShop.AdminPanel.PWA.Dialogs;
public class ProductActionDialogBoxViewModel : BaseViewModel
{
private readonly ISnackbar _snackbar;
private readonly IRestWrapper _restWrapper;
private readonly IUserUtility _userUtility;
private readonly IDialogService _dialogService;
private readonly MudDialogInstance _mudDialog;
public ProductActionDialogBoxViewModel(ISnackbar snackbar, IRestWrapper restWrapper, IUserUtility userUtility, IDialogService dialogService, MudDialogInstance mudDialog)
{
_snackbar = snackbar;
_restWrapper = restWrapper;
_userUtility = userUtility;
_dialogService = dialogService;
_mudDialog = mudDialog;
}
public ProductActionDialogBoxViewModel(ISnackbar snackbar,
IRestWrapper restWrapper,
IUserUtility userUtility,
IDialogService dialogService,
MudDialogInstance mudDialog,
ProductSDto product)
{
_snackbar = snackbar;
_restWrapper = restWrapper;
_userUtility = userUtility;
_dialogService = dialogService;
_mudDialog = mudDialog;
Product = product;
}
private ProductSDto? _product = null;
public ProductSDto? Product
{
get => _product;
set
{
_product = value;
if (_product != null)
{
IsEditing = true;
}
}
}
public void Cancel() => _mudDialog.Cancel();
public bool IsEditing = false;
public string ExpertCheck = string.Empty;
public string Summery = string.Empty;
public bool BeDisplayed = true;
public bool HasExpressDelivery = false;
public string PersianName = string.Empty;
public string EnglishName = string.Empty;
public double Cost;
public double PackingCost;
public int MaxOrder;
public string Warranty = string.Empty;
public string Tags = string.Empty;
public string SpecificationTitle = string.Empty;
public string SpecificationValue = string.Empty;
public readonly ObservableCollection<SpecificationSDto> Specifications = new ObservableCollection<SpecificationSDto>();
public readonly ObservableCollection<StorageFileSDto> Files = new ObservableCollection<StorageFileSDto>();
public async Task SubmitEditAsync()
{
try
{
IsProcessing = true;
if (Product == null || Product.Id == default)
throw new Exception("محصول اشتباه است");
var token = await _userUtility.GetBearerTokenAsync();
var request = new UpdateProductCommand(Product.Id, PersianName, EnglishName, Summery, ExpertCheck, Tags, Warranty, BeDisplayed, Cost, PackingCost, HasExpressDelivery, MaxOrder, _selectedBrand?.Id ?? default, _selectedCategory?.Id ?? default, Specifications.ToList(), Files.ToList());
await _restWrapper.CrudApiRest<Product, Guid>(Address.ProductController).Update<UpdateProductCommand>(request, token);
_snackbar.Add($"ویرایش محصول {PersianName} با موفقیت انجام شد", Severity.Success);
_mudDialog.Close();
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
if (exe != null)
_snackbar.Add(exe.Message, Severity.Error);
_snackbar.Add(ex.Content, Severity.Error);
_mudDialog.Cancel();
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
_mudDialog.Cancel();
}
finally
{
IsProcessing = false;
}
}
public void AddSpecification()
{
try
{
if (SpecificationTitle.IsNullOrEmpty())
throw new AppException("عنوان ویژگی مورد نظر را وارد کنید");
if (SpecificationValue.IsNullOrEmpty())
throw new AppException("مقدار ویژگی مورد نظر را وارد کنید");
Specifications.Add(new SpecificationSDto { Title = SpecificationTitle.ToString(), Value = SpecificationValue.ToString() });
SpecificationTitle = string.Empty;
SpecificationValue = string.Empty;
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
}
}
public void RemoveSpecification(SpecificationSDto specification)
{
var spec = Specifications.FirstOrDefault(s => s.Value == specification.Value && s.Title == specification.Title);
if (spec != null)
Specifications.Remove(spec);
}
public async Task SubmitCreateAsync()
{
try
{
IsProcessing = true;
var token = await _userUtility.GetBearerTokenAsync();
var request = new CreateProductCommand(PersianName, EnglishName, Summery, ExpertCheck, Tags, Warranty, BeDisplayed, Cost, PackingCost, HasExpressDelivery, MaxOrder, _selectedBrand?.Id ?? default, _selectedCategory?.Id ?? default, Specifications.ToList(), Files.ToList());
await _restWrapper.CrudApiRest<Product, Guid>(Address.ProductController).Create<CreateProductCommand>(request, token);
_snackbar.Add($"ساخت محصول {PersianName} با موفقیت انجام شد", Severity.Success);
_mudDialog.Close();
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
if (exe != null)
_snackbar.Add(exe.Message, Severity.Error);
_snackbar.Add(ex.Content, Severity.Error);
_mudDialog.Cancel();
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
_mudDialog.Cancel();
}
finally
{
IsProcessing = false;
}
}
public override async Task InitializeAsync()
{
if (IsEditing && _product != null)
{
try
{
IsProcessing = true;
var productLDto = await _restWrapper.CrudDtoApiRest<Product, ProductLDto, Guid>(Address.ProductController).ReadOne(_product.Id);
ExpertCheck = productLDto.ExpertCheck;
Summery = productLDto.Summery;
BeDisplayed = productLDto.BeDisplayed;
HasExpressDelivery = productLDto.HasExpressDelivery;
PersianName = productLDto.PersianName;
EnglishName = productLDto.EnglishName;
Cost = productLDto.Cost;
PackingCost = productLDto.PackingCost;
MaxOrder = productLDto.MaxOrderCount;
Warranty = productLDto.Warranty;
productLDto.Specifications.ForEach(s => Specifications.Add(s));
productLDto.Files.ForEach(f => Files.Add(f));
_selectedCategory = new ProductCategorySDto { Id = productLDto.CategoryId, Name = productLDto.CategoryName };
_selectedBrand = new BrandSDto { Id = productLDto.BrandId, Name = productLDto.BrandName };
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
if (exe != null)
_snackbar.Add(exe.Message, Severity.Error);
_snackbar.Add(ex.Content, Severity.Error);
_mudDialog.Cancel();
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
_mudDialog.Cancel();
}
finally
{
IsProcessing = false;
}
};
}
public List<ProductCategorySDto> _productCategories = new List<ProductCategorySDto>();
public ProductCategorySDto? _selectedCategory;
public async Task<IEnumerable<ProductCategorySDto>> SearchProductCategory(string category)
{
try
{
if (category.IsNullOrEmpty())
_productCategories = await _restWrapper.ProductCategoryRestApi.ReadAll(0);
else
_productCategories = await _restWrapper.ProductCategoryRestApi.ReadAll(category);
return _productCategories;
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
if (exe != null)
_snackbar.Add(exe.Message, Severity.Error);
_snackbar.Add(ex.Content, Severity.Error);
return _productCategories;
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
return _productCategories;
}
}
public List<BrandSDto> _brands = new List<BrandSDto>();
public BrandSDto? _selectedBrand;
public async Task<IEnumerable<BrandSDto>> SearchBrand(string brand)
{
try
{
if (brand.IsNullOrEmpty())
_brands = await _restWrapper.BrandRestApi.ReadAll(0);
else
_brands = await _restWrapper.BrandRestApi.ReadAll(brand);
return _brands;
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
if (exe != null)
_snackbar.Add(exe.Message, Severity.Error);
_snackbar.Add(ex.Content, Severity.Error);
return _brands;
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
return _brands;
}
}
public async Task SelectFileAsync()
{
DialogOptions maxWidth = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, DisableBackdropClick = true };
var dialog = await _dialogService.ShowAsync<StorageDialogBox>("انتخاب عکس", maxWidth);
var result = await dialog.Result;
var file = result.Data;
if (file is StorageFileSDto storageFile)
Files.Add(storageFile);
}
public void RemoveFile(StorageFileSDto file)
{
Files.Remove(file);
}
}

View File

@ -23,7 +23,7 @@
</MudItem> </MudItem>
<MudItem lg="4" md="6"> <MudItem lg="4" md="6">
<MudSelect T="bool" @bind-Value="@_isMain" Label="آیا دسته بندی اصلی است ؟" ToStringFunc="b=>b.ToPersianString()" Variant="Variant.Outlined" AnchorOrigin="Origin.BottomCenter"> <MudSelect T="bool" @bind-Value="@_isMain" Label="آیا دسته بندی اصلی است ؟" ToStringFunc="b=>b.ToPersianString()" Variant="Variant.Outlined" AnchorOrigin="Origin.BottomCenter">
<MudSelectItem T="bool" Value="true" ></MudSelectItem> <MudSelectItem T="bool" Value="true" />
<MudSelectItem T="bool" Value="false" /> <MudSelectItem T="bool" Value="false" />
</MudSelect> </MudSelect>
</MudItem> </MudItem>

View File

@ -0,0 +1,192 @@
@using NetinaShop.AdminPanel.PWA.Extensions
@inject IRestWrapper RestWrapper
@inject IUserUtility UserUtility
@inject ISnackbar Snackbar
<MudDialog class="mx-auto">
<DialogContent>
<div class="flex flex-row">
<MudStack class="grow " Spacing="0">
<MudText Typo="Typo.h6">انتخاب یا اپلود عکس جدید</MudText>
<MudText Typo="Typo.body2">میتوانید از بین عکس های اپلود شده یکی را انتخاب کرده یا عکس جدیدی اپلود کنید</MudText>
</MudStack>
<MudFileUpload class="flex-none" T="IBrowserFile" OnFilesChanged="FileChangeForUpload">
<ButtonTemplate>
<MudButton HtmlTag="label"
class="h-full"
Variant="Variant.Filled"
Color="Color.Info"
StartIcon="@Icons.Material.Filled.CloudUpload"
for="@context.Id">
اپلود فایل جدید
</MudButton>
</ButtonTemplate>
</MudFileUpload>
</div>
<MudTextField T="string" Placeholder="جست جو بر اساس نام فایل" Adornment="Adornment.Start" Immediate="true"
Clearable="true"
ValueChanged="@SearchChanged"
AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium"
Variant="Variant.Outlined"
OnAdornmentClick="@SearchAsync"></MudTextField>
<MudContainer class="max-h-[30rem]" Style="overflow-y: scroll">
<div class="flex flex-wrap justify-center">
@foreach (var item in _files)
{
@if (item.Selected)
{
<MudImage @onclick="()=>UnSelectFile(item)" class="cursor-pointer rounded-lg mx-1 w-52 h-52 mt-2 border-solid border-4 border-blue-500" Src="@item.GetLink()" Elevation="25" />
}
else
{
<MudImage @onclick="()=>SelectFile(item)" class="cursor-pointer rounded-lg mx-1 w-52 h-52 mt-2" Src="@item.GetLink()" Elevation="25" />
}
}
</div>
</MudContainer>
</DialogContent>
<DialogActions>
<MudStack Row="true" class="w-full mx-4 mb-2">
<BaseButtonUi class="w-64 rounded-md" IsProcessing="@_isProcessing"
Icon="@Icons.Material.Outlined.Check"
Variant="Variant.Filled" Color="Color.Success"
OnClickCallback="SelectFile"
Content="انتخابــ" />
<MudSpacer />
<MudButton Variant="Variant.Outlined" Size="Size.Large" Color="Color.Error" OnClick="Cancel">بستن</MudButton>
</MudStack>
</DialogActions>
</MudDialog>
@code
{
private void SelectFile(StorageFileSDto item)
{
var pastSelect = _files.FirstOrDefault(f => f.Selected);
if (pastSelect != null)
pastSelect.Selected = false;
item.Selected = true;
}
private void UnSelectFile(StorageFileSDto item) => item.Selected = false;
public void SearchChanged(string search)
{
if (search.IsNullOrEmpty() && !_search.IsNullOrEmpty())
{
_files.Clear();
_originalFiles.ForEach(f=>_files.Add(f));
}
_search = search;
}
public void SearchAsync()
{
try
{
if (_search.IsNullOrEmpty())
throw new AppException("دسته بندی برای جست جو وارد نشده است");
_files.Clear();
foreach (var storageFileSDto in _originalFiles.Where(f => f.FileName.ToLower().Trim().Contains(_search.ToLower().Trim())))
_files.Add(storageFileSDto);
}
catch (Exception e)
{
Snackbar.Add(e.Message, Severity.Error);
}
}
public void SelectFile()
{
var selected = _files.FirstOrDefault(f => f.Selected);
if (selected == null)
throw new Exception("یک فایل را انتخاب کنید");
MudDialog.Close(selected);
}
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
void Cancel() => MudDialog.Cancel();
private readonly ObservableCollection<StorageFileSDto> _files = new ObservableCollection<StorageFileSDto>();
private List<StorageFileSDto> _originalFiles = new List<StorageFileSDto>();
private bool _isProcessing = false;
private string _search = string.Empty;
protected override async Task OnInitializedAsync()
{
try
{
_isProcessing = true;
_files.Clear();
var token = await UserUtility.GetBearerTokenAsync();
var files = await RestWrapper.FileRestApi.GetFilesAsync(token);
files.ForEach(f => _files.Add(f));
_originalFiles = files;
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
if (exe != null)
Snackbar.Add(exe.Message, Severity.Error);
Snackbar.Add(ex.Content, Severity.Error);
}
catch (Exception e)
{
Snackbar.Add(e.Message, Severity.Error);
}
finally
{
_isProcessing = false;
}
await base.OnInitializedAsync();
}
private async Task FileChangeForUpload(InputFileChangeEventArgs obj)
{
try
{
_isProcessing = true;
using var memoryStream = new MemoryStream();
var file = obj.File;
var stream = file.OpenReadStream();
await stream.CopyToAsync(memoryStream);
var fileUpload = new FileUploadRequest
{
ContentType = file.ContentType,
FileName = file.Name,
FileUploadType = FileUploadType.Image,
StringBaseFile = Convert.ToBase64String(memoryStream.ToArray())
};
var token = await UserUtility.GetBearerTokenAsync();
var rest = await RestWrapper.FileRestApi.UploadFileAsync(fileUpload, token);
_files.Insert(0, new StorageFileSDto
{
FileLocation = rest.FileLocation,
FileName = rest.FileName,
FileType = StorageFileType.Image
});
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
if (exe != null)
Snackbar.Add(exe.Message, Severity.Error);
Snackbar.Add(ex.Content, Severity.Error);
}
catch (Exception e)
{
Snackbar.Add(e.Message, Severity.Error);
}
finally
{
_isProcessing = false;
}
}
}

View File

@ -0,0 +1,12 @@
using NetinaShop.Common.Extensions;
namespace NetinaShop.AdminPanel.PWA.Extensions;
public static class StorageFileExtension
{
public static string GetLink(this StorageFileSDto file)
{
var link = $"https://storage.vesmook.com/{file.FileLocation}";
return link;
}
}

View File

@ -13,4 +13,5 @@ public static class Address
public static string ProductCategoryController = $"{BaseAddress}/product/category"; public static string ProductCategoryController = $"{BaseAddress}/product/category";
public static string ProductController = $"{BaseAddress}/product"; public static string ProductController = $"{BaseAddress}/product";
public static string BrandController = $"{BaseAddress}/brand"; public static string BrandController = $"{BaseAddress}/brand";
public static string FileController => $"{BaseAddress}/file";
} }

View File

@ -53,6 +53,8 @@
<Using Include="NetinaShop.AdminPanel.PWA.Utilities" /> <Using Include="NetinaShop.AdminPanel.PWA.Utilities" />
<Using Include="NetinaShop.Common.Models.Api" /> <Using Include="NetinaShop.Common.Models.Api" />
<Using Include="NetinaShop.Common.Models.Exception" /> <Using Include="NetinaShop.Common.Models.Exception" />
<Using Include="NetinaShop.Domain.CommandQueries.Commands" />
<Using Include="NetinaShop.Domain.Dtos.LargDtos" />
<Using Include="NetinaShop.Domain.Dtos.RequestDtos" /> <Using Include="NetinaShop.Domain.Dtos.RequestDtos" />
<Using Include="NetinaShop.Domain.Dtos.SmallDtos" /> <Using Include="NetinaShop.Domain.Dtos.SmallDtos" />
<Using Include="NetinaShop.Domain.Entities.ProductCategories" /> <Using Include="NetinaShop.Domain.Entities.ProductCategories" />

View File

@ -46,12 +46,13 @@
<MudIconButton Icon="@Icons.Material.Filled.Edit" <MudIconButton Icon="@Icons.Material.Filled.Edit"
Size="@Size.Small" Size="@Size.Small"
Variant="@Variant.Outlined" Variant="@Variant.Outlined"
Color="@Color.Info"></MudIconButton> Color="@Color.Info"
OnClick="async()=>await ViewModel.EditProductClicked(context.Item)"/>
<MudIconButton Icon="@Icons.Material.Filled.Delete" <MudIconButton Icon="@Icons.Material.Filled.Delete"
Size="@Size.Small" Size="@Size.Small"
Variant="@Variant.Outlined" Variant="@Variant.Outlined"
OnClick="async () => await ViewModel.DeleteProductAsync(context.Item.Id)" OnClick="async () => await ViewModel.DeleteProductAsync(context.Item.Id)"
Color="@Color.Error"></MudIconButton> Color="@Color.Error"/>
</MudStack> </MudStack>
</CellTemplate> </CellTemplate>
</TemplateColumn> </TemplateColumn>

View File

@ -0,0 +1,14 @@
namespace NetinaShop.AdminPanel.PWA.Services.RestServices;
public interface IBrandRestApi
{
[Get("")]
Task<List<BrandSDto>> ReadAll();
[Get("")]
Task<List<BrandSDto>> ReadAll([Query] int page);
[Get("")]
Task<List<BrandSDto>> ReadAll([Query] int page, [Query] string brandName);
[Get("")]
Task<List<BrandSDto>> ReadAll([Query] string brandName);
}

View File

@ -34,7 +34,7 @@ public interface ICrudDtoApiRest<T, TDto, in TKey> where T : class where TDto :
Task<List<TDto>> ReadAll(); Task<List<TDto>> ReadAll();
[Get("/{key}")] [Get("/{key}")]
Task<TDto> ReadOne(TKey key, [Header("Authorization")] string authorization); Task<TDto> ReadOne(TKey key);
[Put("")] [Put("")]
Task Update([Body] T payload, [Header("Authorization")] string authorization); Task Update([Body] T payload, [Header("Authorization")] string authorization);

View File

@ -0,0 +1,9 @@
namespace NetinaShop.AdminPanel.PWA.Services.RestServices;
public interface IFileRestApi
{
[Get("")]
Task<List<StorageFileSDto>> GetFilesAsync([Header("Authorization")] string authorization);
[Post("")]
Task<FileUploadResponse> UploadFileAsync([Body] FileUploadRequest request, [Header("Authorization")] string authorization);
}

View File

@ -10,4 +10,6 @@ public interface IRestWrapper
public IUserRestApi UserRestApi { get; } public IUserRestApi UserRestApi { get; }
public IProductCategoryRestApi ProductCategoryRestApi { get; } public IProductCategoryRestApi ProductCategoryRestApi { get; }
public IProductRestApi ProductRestApi { get; } public IProductRestApi ProductRestApi { get; }
public IBrandRestApi BrandRestApi { get; }
public IFileRestApi FileRestApi { get; }
} }

View File

@ -22,4 +22,6 @@ public class RestWrapper : IRestWrapper
public IUserRestApi UserRestApi => RestService.For<IUserRestApi>(Address.UserController, setting); public IUserRestApi UserRestApi => RestService.For<IUserRestApi>(Address.UserController, setting);
public IProductCategoryRestApi ProductCategoryRestApi => RestService.For<IProductCategoryRestApi>(Address.ProductCategoryController, setting); public IProductCategoryRestApi ProductCategoryRestApi => RestService.For<IProductCategoryRestApi>(Address.ProductCategoryController, setting);
public IProductRestApi ProductRestApi => RestService.For<IProductRestApi>(Address.ProductController, setting); public IProductRestApi ProductRestApi => RestService.For<IProductRestApi>(Address.ProductController, setting);
public IBrandRestApi BrandRestApi => RestService.For<IBrandRestApi>(Address.BrandController, setting);
public IFileRestApi FileRestApi => RestService.For<IFileRestApi>(Address.FileController, setting);
} }

View File

@ -501,6 +501,19 @@ video {
--tw-backdrop-saturate: ; --tw-backdrop-saturate: ;
--tw-backdrop-sepia: ; --tw-backdrop-sepia: ;
} }
.absolute {
position: absolute;
}
.m-1 {
margin: 0.25rem;
}
.m-1\.5 {
margin: 0.375rem;
}
.mx-1 {
margin-left: 0.25rem;
margin-right: 0.25rem;
}
.mx-3 { .mx-3 {
margin-left: 0.75rem; margin-left: 0.75rem;
margin-right: 0.75rem; margin-right: 0.75rem;
@ -537,9 +550,6 @@ video {
.-mt-3 { .-mt-3 {
margin-top: -0.75rem; margin-top: -0.75rem;
} }
.mb-10 {
margin-bottom: 2.5rem;
}
.mb-2 { .mb-2 {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
@ -585,9 +595,15 @@ video {
.h-12 { .h-12 {
height: 3rem; height: 3rem;
} }
.h-28 {
height: 7rem;
}
.h-5 { .h-5 {
height: 1.25rem; height: 1.25rem;
} }
.h-52 {
height: 13rem;
}
.h-64 { .h-64 {
height: 16rem; height: 16rem;
} }
@ -600,6 +616,9 @@ video {
.h-screen { .h-screen {
height: 100vh; height: 100vh;
} }
.max-h-\[30rem\] {
max-height: 30rem;
}
.min-h-\[10rem\] { .min-h-\[10rem\] {
min-height: 10rem; min-height: 10rem;
} }
@ -609,9 +628,15 @@ video {
.w-12 { .w-12 {
width: 3rem; width: 3rem;
} }
.w-28 {
width: 7rem;
}
.w-5 { .w-5 {
width: 1.25rem; width: 1.25rem;
} }
.w-52 {
width: 13rem;
}
.w-64 { .w-64 {
width: 16rem; width: 16rem;
} }
@ -624,30 +649,58 @@ video {
.w-screen { .w-screen {
width: 100vw; width: 100vw;
} }
.flex-none {
flex: none;
}
.grow {
flex-grow: 1;
}
.basis-full { .basis-full {
flex-basis: 100%; flex-basis: 100%;
} }
.cursor-pointer {
cursor: pointer;
}
.flex-row { .flex-row {
flex-direction: row; flex-direction: row;
} }
.flex-col-reverse { .flex-col-reverse {
flex-direction: column-reverse; flex-direction: column-reverse;
} }
.flex-wrap {
flex-wrap: wrap;
}
.items-center { .items-center {
align-items: center; align-items: center;
} }
.justify-end { .justify-end {
justify-content: flex-end; justify-content: flex-end;
} }
.justify-center {
justify-content: center;
}
.overflow-hidden { .overflow-hidden {
overflow: hidden; overflow: hidden;
} }
.rounded-lg {
border-radius: 0.5rem;
}
.rounded-md { .rounded-md {
border-radius: 0.375rem; border-radius: 0.375rem;
} }
.border { .border {
border-width: 1px; border-width: 1px;
} }
.border-4 {
border-width: 4px;
}
.border-solid {
border-style: solid;
}
.border-blue-500 {
--tw-border-opacity: 1;
border-color: rgb(59 130 246 / var(--tw-border-opacity));
}
.bg-\[\#000000\] { .bg-\[\#000000\] {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); background-color: rgb(0 0 0 / var(--tw-bg-opacity));

View File

@ -554,6 +554,23 @@ video {
--tw-backdrop-sepia: ; --tw-backdrop-sepia: ;
} }
.absolute {
position: absolute;
}
.m-1 {
margin: 0.25rem;
}
.m-1\.5 {
margin: 0.375rem;
}
.mx-1 {
margin-left: 0.25rem;
margin-right: 0.25rem;
}
.mx-3 { .mx-3 {
margin-left: 0.75rem; margin-left: 0.75rem;
margin-right: 0.75rem; margin-right: 0.75rem;
@ -600,10 +617,6 @@ video {
margin-top: -0.75rem; margin-top: -0.75rem;
} }
.mb-10 {
margin-bottom: 2.5rem;
}
.mb-2 { .mb-2 {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
@ -663,10 +676,18 @@ video {
height: 3rem; height: 3rem;
} }
.h-28 {
height: 7rem;
}
.h-5 { .h-5 {
height: 1.25rem; height: 1.25rem;
} }
.h-52 {
height: 13rem;
}
.h-64 { .h-64 {
height: 16rem; height: 16rem;
} }
@ -683,6 +704,10 @@ video {
height: 100vh; height: 100vh;
} }
.max-h-\[30rem\] {
max-height: 30rem;
}
.min-h-\[10rem\] { .min-h-\[10rem\] {
min-height: 10rem; min-height: 10rem;
} }
@ -695,10 +720,18 @@ video {
width: 3rem; width: 3rem;
} }
.w-28 {
width: 7rem;
}
.w-5 { .w-5 {
width: 1.25rem; width: 1.25rem;
} }
.w-52 {
width: 13rem;
}
.w-64 { .w-64 {
width: 16rem; width: 16rem;
} }
@ -715,10 +748,22 @@ video {
width: 100vw; width: 100vw;
} }
.flex-none {
flex: none;
}
.grow {
flex-grow: 1;
}
.basis-full { .basis-full {
flex-basis: 100%; flex-basis: 100%;
} }
.cursor-pointer {
cursor: pointer;
}
.flex-row { .flex-row {
flex-direction: row; flex-direction: row;
} }
@ -727,6 +772,10 @@ video {
flex-direction: column-reverse; flex-direction: column-reverse;
} }
.flex-wrap {
flex-wrap: wrap;
}
.items-center { .items-center {
align-items: center; align-items: center;
} }
@ -735,10 +784,18 @@ video {
justify-content: flex-end; justify-content: flex-end;
} }
.justify-center {
justify-content: center;
}
.overflow-hidden { .overflow-hidden {
overflow: hidden; overflow: hidden;
} }
.rounded-lg {
border-radius: 0.5rem;
}
.rounded-md { .rounded-md {
border-radius: 0.375rem; border-radius: 0.375rem;
} }
@ -747,6 +804,19 @@ video {
border-width: 1px; border-width: 1px;
} }
.border-4 {
border-width: 4px;
}
.border-solid {
border-style: solid;
}
.border-blue-500 {
--tw-border-opacity: 1;
border-color: rgb(59 130 246 / var(--tw-border-opacity));
}
.bg-\[\#000000\] { .bg-\[\#000000\] {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); background-color: rgb(0 0 0 / var(--tw-bg-opacity));