feat(FaqInActions) , feat(FaqManagementPage) , feat(FaqController)

subProduct
Amir Hossein Khademi 2024-08-07 16:15:52 +03:30
parent 77a6cf80a2
commit 8b4c9934b3
19 changed files with 1079 additions and 498 deletions

View File

@ -40,7 +40,7 @@
<MudNavGroup Title="مدیریت برگه ها" Expanded="false"
Icon="@Icons.Material.Outlined.Pages">
<MudNavLink Href="management/pages" Icon="@Icons.Material.Filled.Pageview">برگه ها</MudNavLink>
<MudNavLink Href="management/faqs" Icon="@Icons.Material.Filled.ManageAccounts">سوالات متداول</MudNavLink>
<MudNavLink Href="setting/faq" Icon="@Icons.Material.Filled.ManageAccounts">سوالات متداول</MudNavLink>
</MudNavGroup>
<MudNavGroup Title="شخصی سازی" Expanded="false" Icon="@Icons.Material.Filled.SettingsSystemDaydream">

View File

@ -1,236 +1,180 @@
@using Netina.AdminPanel.PWA.Models.Api
@using Netina.AdminPanel.PWA.Models
@using Netina.Domain.Entities.Brands
@inject ISnackbar Snackbar
@inject ISnackbar Snackbar
@inject IRestWrapper RestWrapper
@inject IUserUtility UserUtility
@inject IDialogService DialogService
<MudDialog class="mx-auto">
<DialogContent>
<MudStack>
<MudDivider class="-mt-3" />
<MudStack Spacing="0">
<MudContainer class="h-full p-0">
<MudTabs Outlined="true" Elevation="0" Rounded="true" Centered="true">
<MudTabPanel Text="اطلاعات کلی" Icon="@Icons.Material.Outlined.Info">
<MudText Typo="Typo.h6">اطلاعات کلی</MudText>
<MudText Typo="Typo.caption">اطلاعات کلی دسته بندی محصول را به دقت وارد کنید</MudText>
</MudStack>
<MudGrid>
<MudItem lg="6" md="6">
<MudTextField T="string" Label="نام فارسی برند" @bind-Value="@_persianName" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="6" md="6">
<MudTextField T="string" Label="نام انگلیسی برند" @bind-Value="@_englishName" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="6" md="6">
<MudSelect T="bool" Label="آیا صفحه شخصی دارد ؟" @bind-Value="@_hasSpecialPage" ToStringFunc="b=>b.ToPersianString()" Variant="Variant.Outlined" AnchorOrigin="Origin.BottomCenter">
<MudSelectItem T="bool" Value="true"></MudSelectItem>
<MudSelectItem T="bool" Value="false" />
</MudSelect>
</MudItem>
<MudItem lg="6" md="6">
<MudTextField T="string" Label="لینک صفحه شخصی برند" @bind-Value="@_pageUrl" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="12" md="12">
<MudTextField T="string" Label="توضیحاتــ" @bind-Value="@_description" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem sm="12" md="12" lg="12">
<MudStack class="mt-1 mb-4" Spacing="0">
<MudStack>
<MudStack Spacing="0">
<MudText Typo="Typo.h6">تصاویر برند</MudText>
<MudText Typo="Typo.caption">می توانید برای برند چند تصویر اپلود کنید</MudText>
<MudText Typo="Typo.h6">اطلاعات کلی</MudText>
<MudText Typo="Typo.caption">اطلاعات کلی دسته بندی محصول را به دقت وارد کنید</MudText>
</MudStack>
<MudGrid>
<MudItem lg="6" md="6">
<MudTextField T="string" Label="نام فارسی برند" @bind-Value="@ViewModel.PageDto.PersianName" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="6" md="6">
<MudTextField T="string" Label="نام انگلیسی برند" @bind-Value="@ViewModel.PageDto.EnglishName" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="6" md="6">
<MudSelect T="bool" Label="آیا صفحه شخصی دارد ؟" @bind-Value="@ViewModel.PageDto.HasSpecialPage" ToStringFunc="b=>b.ToPersianString()" Variant="Variant.Outlined" AnchorOrigin="Origin.BottomCenter">
<MudSelectItem T="bool" Value="true"></MudSelectItem>
<MudSelectItem T="bool" Value="false" />
</MudSelect>
</MudItem>
<MudItem lg="6" md="6">
<MudTextField T="string" Label="لینک صفحه شخصی برند" @bind-Value="@ViewModel.PageDto.PageUrl" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem sm="12" md="12" lg="12">
<MudStack class="mt-1 mb-4" 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()" />
@foreach (var item in ViewModel.PageDto.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.PageDto.Files.Remove(item)"
Color="@Color.Error"
Icon="@Icons.Material.Outlined.Delete" />
@if (item.IsHeader)
{
<p class="bg-pink-500 px-1.5 py-0.5 absolute bottom-0 mr-2 rounded-lg text-white">هدر</p>
}
@if (item.IsPrimary)
{
<p class="bg-blue-500 px-1.5 py-0.5 absolute bottom-0 mr-2 rounded-lg text-white">اصلی</p>
}
</div>
}
</MudStack>
</MudItem>
<MudItem lg="12" md="12">
<RichTextEditorUi @bind-Text="@ViewModel.PageDto.Description" ></RichTextEditorUi>
</MudItem>
</MudGrid>
</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 SelectFileAsync()">
</MudIconButton>
@foreach (var item in Files)
{
<div class="w-28 h-28">
<MudImage Src="@item.GetLink()" Elevation="25" Class="rounded-lg w-28 h-28 absolute" />
</MudTabPanel>
<MudIconButton DisableElevation="true"
class="absolute m-1.5"
Size="@Size.Small"
Variant="@Variant.Filled"
OnClick="() => RemoveFile(item)"
Color="@Color.Error"
Icon="@Icons.Material.Outlined.Delete" />
@if (item.IsHeader)
{
<p class="bg-pink-500 px-1.5 py-0.5 absolute bottom-0 mr-2 rounded-lg text-white">هدر</p>
}
@if (item.IsPrimary)
{
<p class="bg-blue-500 px-1.5 py-0.5 absolute bottom-0 mr-2 rounded-lg text-white">اصلی</p>
}
</div>
}
<MudTabPanel Text="متاتگ و سوالات متداول">
</MudStack>
</MudItem>
<MudGrid>
<MudItem xs="12">
</MudGrid>
</MudStack>
<MudText Typo="Typo.h6">سوالات متداول</MudText>
<MudText Typo="Typo.caption">می توانید سوالات متداول شهر موردنظر را وارد کنید</MudText>
<MudGrid class="mt-1">
<MudItem lg="5" md="6">
<MudTextField @bind-Value="@ViewModel.FaqQuestion" T="string" Label="سوال" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="5" md="6">
<MudTextField @bind-Value="@ViewModel.FaqAnswer" T="string" Label="پاسخ" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="2" md="12">
<MudButton Variant="Variant.Filled"
Size="Size.Large"
Color="Color.Info"
class="mt-2 w-full py-3"
OnClick="ViewModel.AddFaq"
StartIcon="@Icons.Material.Outlined.Add">افزودن</MudButton>
</MudItem>
<MudItem sm="12">
<MudExpansionPanels class="mt-1" Elevation="2">
@foreach (var item in ViewModel.Faqs)
{
<MudExpansionPanel>
<TitleContent>
<MudStack Row="true">
<MudIconButton Icon="@Icons.Material.Outlined.Delete"
Size="@Size.Small"
Variant="@Variant.Outlined"
Color="@Color.Error"
OnClick="() => ViewModel.Faqs.Remove(item.Key)" />
<MudText>@item.Key</MudText>
</MudStack>
</TitleContent>
<ChildContent>
@item.Value
</ChildContent>
</MudExpansionPanel>
}
</MudExpansionPanels>
</MudItem>
</MudGrid>
</MudItem>
</MudGrid>
</MudTabPanel>
</MudTabs>
</MudContainer>
</DialogContent>
<DialogActions>
<MudStack Row="true" class="w-full mx-4 mb-2">
@if (_isEditing)
@if (ViewModel.IsEditing)
{
<BaseButtonUi class="w-64 rounded-md" IsProcessing="@_isProcessing"
<BaseButtonUi class="w-64 rounded-md" IsProcessing="@ViewModel.IsProcessing"
Icon="@Icons.Material.Outlined.Check"
Variant="Variant.Filled" Color="Color.Success"
Content="ثبت ویرایش" OnClickCallback="SubmitEditAsync" />
Content="ثبت ویرایش" OnClickCallback="ViewModel.SubmitEditAsync" />
}
else
{
<BaseButtonUi class="w-64 rounded-md" IsProcessing="@_isProcessing"
<BaseButtonUi class="w-64 rounded-md" IsProcessing="@ViewModel.IsProcessing"
Icon="@Icons.Material.Outlined.Check"
Variant="Variant.Filled" Color="Color.Success"
Content="تایید" OnClickCallback="SubmitCreateAsync" />
Content="تایید" OnClickCallback="ViewModel.SubmitCreateAsync" />
}
<MudSpacer />
<MudButton Variant="Variant.Outlined" Size="Size.Large" Color="Color.Error" OnClick="Cancel">بستن</MudButton>
<MudButton Variant="Variant.Outlined" Size="Size.Large" Color="Color.Error" OnClick="ViewModel.Cancel">بستن</MudButton>
</MudStack>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
[Parameter]
public BrandSDto? Brand { get; set; }
void Cancel() => MudDialog.Cancel();
public readonly ObservableCollection<StorageFileSDto> Files = new ObservableCollection<StorageFileSDto>();
private bool _isProcessing = false;
private string _persianName = string.Empty;
private string _englishName = string.Empty;
private string _description = string.Empty;
private bool _hasSpecialPage;
private bool _isEditing;
private string _pageUrl = string.Empty;
protected override async Task OnParametersSetAsync()
public BrandActionDialogBoxViewModel ViewModel { get; set; }
protected override async Task OnInitializedAsync()
{
if (Brand != null)
{
_isEditing = true;
try
{
_isProcessing = true;
var response = await RestWrapper.CrudDtoApiRest<Brand, BrandLDto, Guid>(Address.BrandController).ReadOne(Brand.Id);
var brandLDto = response;
brandLDto.Files.ForEach(f => Files.Add(f));
_hasSpecialPage = brandLDto.HasSpecialPage;
_description = brandLDto.Description;
_englishName = brandLDto.EnglishName;
_persianName = brandLDto.PersianName;
}
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;
}
}
await base.OnParametersSetAsync();
}
private async Task SubmitCreateAsync()
{
try
{
if (_englishName.IsNullOrEmpty())
throw new AppException("لطفا نام برند را وارد کنید");
_isProcessing = true;
var token = await UserUtility.GetBearerTokenAsync();
if (token == null)
throw new AppException("Token is null");
var request = new CreateBrandCommand(_persianName, _englishName, _description, _hasSpecialPage, _pageUrl, Files.ToList());
await RestWrapper.CrudApiRest<Brand, Guid>(Address.BrandController).Create<CreateBrandCommand>(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;
}
}
private async Task SubmitEditAsync()
{
try
{
if (Brand == null)
throw new AppException("برند به درستی ارسال نشده است");
if (_englishName.IsNullOrEmpty())
throw new AppException("لطفا نام برند را وارد کنید");
_isProcessing = true;
var token = await UserUtility.GetBearerTokenAsync();
if (token == null)
throw new AppException("Token is null");
var request = new UpdateBrandCommand(Brand.Id, _persianName, _englishName, _description, _hasSpecialPage, _pageUrl, Files.ToList());
await RestWrapper.CrudApiRest<Brand, Guid>(Address.BrandController).Update<UpdateBrandCommand>(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;
}
}
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);
ViewModel = Brand == null ? new BrandActionDialogBoxViewModel(Snackbar, RestWrapper, UserUtility, DialogService, MudDialog) :
new BrandActionDialogBoxViewModel(Snackbar, RestWrapper, UserUtility, DialogService, MudDialog, Brand);
await ViewModel.InitializeAsync();
await base.OnInitializedAsync();
}
}

View File

@ -0,0 +1,209 @@
using Netina.Domain.Entities.Brands;
namespace Netina.AdminPanel.PWA.Dialogs;
public class BrandActionDialogBoxViewModel : BaseViewModel<BrandLDto>
{
private readonly ISnackbar _snackbar;
private readonly IRestWrapper _restWrapper;
private readonly IUserUtility _userUtility;
private readonly IDialogService _dialogService;
private readonly MudDialogInstance _mudDialog;
public BrandActionDialogBoxViewModel(ISnackbar snackbar,
IRestWrapper restWrapper,
IUserUtility userUtility,
IDialogService dialogService,
MudDialogInstance mudDialog) : base(userUtility)
{
_snackbar = snackbar;
_restWrapper = restWrapper;
_userUtility = userUtility;
_dialogService = dialogService;
_mudDialog = mudDialog;
}
public BrandActionDialogBoxViewModel(ISnackbar snackbar,
IRestWrapper restWrapper,
IUserUtility userUtility,
IDialogService dialogService,
MudDialogInstance mudDialog,
BrandSDto brand) : base(userUtility)
{
_snackbar = snackbar;
_restWrapper = restWrapper;
_userUtility = userUtility;
_dialogService = dialogService;
_mudDialog = mudDialog;
Brand = brand;
}
private BrandSDto? _brand = null;
public BrandSDto? Brand
{
get => _brand;
set
{
_brand = value;
if (_brand != null)
{
IsEditing = true;
}
}
}
public void Cancel() => _mudDialog.Cancel();
public bool IsEditing = false;
public override async Task InitializeAsync()
{
if (Brand != null)
{
IsEditing = true;
try
{
IsProcessing = true;
var response = await _restWrapper.CrudDtoApiRest<Brand, BrandLDto, Guid>(Address.BrandController).ReadOne(Brand.Id);
var brandLDto = response;
PageDto = brandLDto;
}
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;
}
}
await base.InitializeAsync();
}
public async Task SubmitCreateAsync()
{
try
{
if (PageDto.EnglishName.IsNullOrEmpty())
throw new AppException("لطفا نام برند را وارد کنید");
IsProcessing = true;
var token = await _userUtility.GetBearerTokenAsync();
if (token == null)
throw new AppException("Token is null");
var request = new CreateBrandCommand(PageDto.PersianName,
PageDto.EnglishName,
PageDto.Description,
PageDto.HasSpecialPage,
PageDto.PageUrl,
PageDto.Files,
Faqs,
new Dictionary<string, string>());
await _restWrapper.CrudApiRest<Brand, Guid>(Address.BrandController).Create<CreateBrandCommand>(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 (Brand == null)
throw new AppException("برند به درستی ارسال نشده است");
if (PageDto.EnglishName.IsNullOrEmpty())
throw new AppException("لطفا نام برند را وارد کنید");
IsProcessing = true;
var token = await _userUtility.GetBearerTokenAsync();
if (token == null)
throw new AppException("Token is null");
var request = new UpdateBrandCommand(
PageDto.Id,
PageDto.PersianName,
PageDto.EnglishName,
PageDto.Description,
PageDto.HasSpecialPage,
PageDto.PageUrl,
PageDto.Files,
Faqs,
new Dictionary<string, string>());
await _restWrapper.CrudApiRest<Brand, Guid>(Address.BrandController).Update<UpdateBrandCommand>(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;
}
}
public string FaqQuestion { get; set; } = string.Empty;
public string FaqAnswer { get; set; } = string.Empty;
public Dictionary<string, string> Faqs = new();
public void AddFaq()
{
try
{
if (FaqAnswer.IsNullOrEmpty())
throw new Exception("لطفا پاسخ را وارد کنید");
if (FaqQuestion.IsNullOrEmpty())
throw new Exception("لطفا سوال را وارد کنید");
Faqs.Add(FaqQuestion, FaqAnswer);
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
}
}
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)
PageDto.Files.Add(storageFile);
}
public void RemoveFile(StorageFileSDto file)
{
PageDto.Files.Remove(file);
}
}

View File

@ -0,0 +1,117 @@
@inject ISnackbar Snackbar
@inject IRestWrapper RestWrapper
@inject IUserUtility UserUtility
@inject IDialogService DialogService
<MudDialog class="mx-auto">
<DialogContent>
<MudStack>
<MudDivider class="-mt-3" />
<MudStack Spacing="0">
<MudText Typo="Typo.h6">اطلاعات کلی</MudText>
<MudText Typo="Typo.caption">اطلاعات کلی را به دقت وارد کنید</MudText>
</MudStack>
<MudGrid>
<MudItem sm="12" lg="6" md="6">
<MudTextField T="string" Label="عنوان صفحه سوالات متداول" @bind-Value="@ViewModel.PageDto.Title" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem sm="12" lg="6" md="6">
<MudTextField T="string" Label="اسلاگ صفحه سوالات متداول" @bind-Value="@ViewModel.PageDto.Slug" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem sm="12" lg="12" md="12">
<MudText Typo="Typo.h6">سوالات متداول</MudText>
<MudText Typo="Typo.caption">می توانید سوالات متداول شهر موردنظر را وارد کنید</MudText>
<MudGrid>
<MudItem lg="5" md="6">
<MudTextField @bind-Value="@ViewModel.Question" T="string" Label="سوال" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="5" md="6">
<MudTextField @bind-Value="@ViewModel.Answer" T="string" Label="پاسخ" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="2" md="12">
<MudButton Variant="Variant.Filled"
Size="Size.Large"
Color="Color.Info"
class="mt-2 w-full py-3"
OnClick="ViewModel.AddFaq"
StartIcon="@Icons.Material.Outlined.Add">افزودن</MudButton>
</MudItem>
<MudItem sm="12">
<MudExpansionPanels class="mt-1" Elevation="2">
@foreach (var item in ViewModel.PageDto.Faqs)
{
<MudExpansionPanel>
<TitleContent>
<MudStack Row="true">
<MudIconButton Icon="@Icons.Material.Outlined.Delete"
Size="@Size.Small"
Variant="@Variant.Outlined"
Color="@Color.Error"
OnClick="()=>ViewModel.PageDto.Faqs.Remove(item.Key)" />
<MudText>@item.Key</MudText>
</MudStack>
</TitleContent>
<ChildContent>
@item.Value
</ChildContent>
</MudExpansionPanel>
}
</MudExpansionPanels>
</MudItem>
</MudGrid>
</MudItem>
</MudGrid>
</MudStack>
</DialogContent>
<DialogActions>
<MudStack Row="true" class="mx-4 mb-2 w-full">
@if (ViewModel.IsEditing)
{
<BaseButtonUi class="w-64 rounded-md" IsProcessing="@ViewModel.IsProcessing"
Icon="@Icons.Material.Outlined.Check"
Variant="Variant.Filled" Color="Color.Info"
Content="ثبت ویرایش" OnClickCallback="@ViewModel.SubmitUpdateAsync" />
}
else
{
<BaseButtonUi class="w-64 rounded-md" IsProcessing="@ViewModel.IsProcessing"
Icon="@Icons.Material.Outlined.Check"
Variant="Variant.Filled" Color="Color.Success"
Content="تایید" OnClickCallback="@ViewModel.SubmitAsync" />
}
<MudSpacer/>
<MudButton Variant="Variant.Outlined" Size="Size.Large" Color="Color.Error" OnClick="Cancel">بستن</MudButton>
</MudStack>
</DialogActions>
</MudDialog>
@code
{
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
void Cancel() => MudDialog.Cancel();
[Parameter]
public BaseFaq? Faq { get; set; }
public FaqActionDialogBoxViewModel ViewModel;
protected override async Task OnInitializedAsync()
{
if (Faq != null)
ViewModel = new FaqActionDialogBoxViewModel(Faq,UserUtility, Snackbar, RestWrapper, MudDialog);
else
ViewModel = new FaqActionDialogBoxViewModel(UserUtility, Snackbar, RestWrapper, MudDialog);
await ViewModel.InitializeAsync();
await base.OnInitializedAsync();
}
}

View File

@ -0,0 +1,104 @@
using Netina.Domain.MartenEntities.Faqs;
namespace Netina.AdminPanel.PWA.Dialogs;
public class FaqActionDialogBoxViewModel : BaseViewModel<BaseFaq>
{
private readonly IRestWrapper _restWrapper;
private readonly ISnackbar _snackbar;
private readonly MudDialogInstance _dialogInstance;
public bool IsEditing = false;
public FaqActionDialogBoxViewModel(IUserUtility userUtility, ISnackbar snackbar, IRestWrapper restWrapper, MudDialogInstance dialogInstance) : base(userUtility)
{
_snackbar = snackbar;
_restWrapper = restWrapper;
_dialogInstance = dialogInstance;
}
public FaqActionDialogBoxViewModel(BaseFaq faq,IUserUtility userUtility, ISnackbar snackbar, IRestWrapper restWrapper, MudDialogInstance dialogInstance) : base(userUtility)
{
PageDto = faq;
IsEditing = true;
_snackbar = snackbar;
_restWrapper = restWrapper;
_dialogInstance = dialogInstance;
}
public string Question { get; set; } = string.Empty;
public string Answer { get; set; } = string.Empty;
public void AddFaq()
{
try
{
if (Question.IsNullOrEmpty())
throw new Exception("سوال را وارد کنید");
if (Answer.IsNullOrEmpty())
throw new Exception("پاسخ را وارد کنید");
PageDto.Faqs.Add(Question,Answer);
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
}
}
public async Task SubmitAsync()
{
try
{
var token = await _userUtility.GetBearerTokenAsync();
if (token == null)
throw new Exception("token is null");
if (PageDto.Slug.IsNullOrEmpty())
throw new Exception("اسلاگ صفحه سوال متداول را وارد کنید");
if (PageDto.Title.IsNullOrEmpty())
throw new Exception("عنوان صفحه سوالات متداول را وارد کنید");
await _restWrapper.FaqApiRest.Create(PageDto, token);
_snackbar.Add("تغییر سوالات متداول با موفقیت انجام شد", Severity.Success);
_dialogInstance.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);
}
}
public async Task SubmitUpdateAsync()
{
try
{
var token = await _userUtility.GetBearerTokenAsync();
if (token == null)
throw new Exception("token is null");
if (PageDto.Slug.IsNullOrEmpty())
throw new Exception("اسلاگ صفحه سوال متداول را وارد کنید");
if (PageDto.Title.IsNullOrEmpty())
throw new Exception("عنوان صفحه سوالات متداول را وارد کنید");
if (PageDto.Id == default)
throw new Exception("ای دی معتبر نمی باشد");
await _restWrapper.FaqApiRest.Update(PageDto, token);
_snackbar.Add("تغییر سوالات متداول با موفقیت انجام شد", Severity.Success);
_dialogInstance.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);
}
}
}

View File

@ -106,6 +106,7 @@
</MudGrid>
</div>
</MudTabPanel>
<MudTabPanel Text="ویژگی های کلی" Icon="@Icons.Material.Outlined.AutoGraph">
<div class="min-h-[33rem]">
@ -157,6 +158,7 @@
</MudGrid>
</div>
</MudTabPanel>
<MudTabPanel Text="توضیحات تکمیلی" Icon="@Icons.Material.Outlined.Article">
<div class="min-h-[33rem]">
@ -174,6 +176,62 @@
</MudGrid>
</div>
</MudTabPanel>
<MudTabPanel Text="متاتگ و سوالات متداول">
<MudGrid>
<MudItem xs="12">
<MudText Typo="Typo.h6">سوالات متداول</MudText>
<MudText Typo="Typo.caption">می توانید سوالات متداول شهر موردنظر را وارد کنید</MudText>
<MudGrid class="mt-1">
<MudItem lg="5" md="6">
<MudTextField @bind-Value="@ViewModel.FaqQuestion" T="string" Label="سوال" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="5" md="6">
<MudTextField @bind-Value="@ViewModel.FaqAnswer" T="string" Label="پاسخ" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="2" md="12">
<MudButton Variant="Variant.Filled"
Size="Size.Large"
Color="Color.Info"
class="mt-2 w-full py-3"
OnClick="ViewModel.AddFaq"
StartIcon="@Icons.Material.Outlined.Add">افزودن</MudButton>
</MudItem>
<MudItem sm="12">
<MudExpansionPanels class="mt-1" Elevation="2">
@foreach (var item in ViewModel.Faqs)
{
<MudExpansionPanel>
<TitleContent>
<MudStack Row="true">
<MudIconButton Icon="@Icons.Material.Outlined.Delete"
Size="@Size.Small"
Variant="@Variant.Outlined"
Color="@Color.Error"
OnClick="()=>ViewModel.Faqs.Remove(item.Key)" />
<MudText>@item.Key</MudText>
</MudStack>
</TitleContent>
<ChildContent>
@item.Value
</ChildContent>
</MudExpansionPanel>
}
</MudExpansionPanels>
</MudItem>
</MudGrid>
</MudItem>
</MudGrid>
</MudTabPanel>
<MudTabPanel Text="تـــــصاویر" Icon="@Icons.Material.Outlined.ImageSearch">
<div class="min-h-[33rem]">
@ -235,10 +293,10 @@
<MudItem xs="12" md="4">
<MudSelect Disabled="@ViewModel.IsSpecialOffer.Not()" T="DiscountAmountType"
<MudSelect Disabled="@ViewModel.IsSpecialOffer.Not()" T="DiscountAmountType"
ValueChanged="@ViewModel.AmountTypeChanged"
Label="نوع تخفیفـــ" ToStringFunc="b=>b.ToDisplay()"
Variant="Variant.Outlined"
Label="نوع تخفیفـــ" ToStringFunc="b=>b.ToDisplay()"
Variant="Variant.Outlined"
Value="@ViewModel.Discount.AmountType"
AnchorOrigin="Origin.BottomCenter">
<MudSelectItem T="DiscountAmountType" Value="DiscountAmountType.Percent" />

View File

@ -93,6 +93,14 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
SelectedBrand = new BrandSDto { Id = productLDto.BrandId, PersianName = productLDto.BrandName };
PageDto.IsSpecialOffer = productLDto.IsSpecialOffer;
IsSpecialOffer = PageDto.IsSpecialOffer;
if (!PageDto.Slug.IsNullOrEmpty())
{
var faq = await _restWrapper.FaqApiRest.ReadOne(PageDto.GetWebSiteUrl());
foreach (var pair in faq.Faqs)
Faqs.Add(pair.Key, pair.Value);
}
if (productLDto.SpecialOffer != null)
{
Discount = productLDto.SpecialOffer;
@ -161,7 +169,29 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
Discount.StartDate = StartDate.Value;
PageDto.SpecialOffer = Discount;
}
var request = PageDto.Adapt<UpdateProductCommand>();
var request = new UpdateProductCommand(PageDto.Id,
PageDto.PersianName,
PageDto.EnglishName,
PageDto.Summery,
PageDto.ExpertCheck,
PageDto.Tags,
PageDto.Warranty,
PageDto.BeDisplayed,
PageDto.Cost,
PageDto.PackingCost,
PageDto.Stock,
PageDto.HasExpressDelivery,
PageDto.MaxOrderCount,
PageDto.IsSpecialOffer,
PageDto.BrandId,
PageDto.CategoryId,
PageDto.SpecialOffer ?? new DiscountSDto(),
PageDto.Specifications,
PageDto.Files,
Faqs,
new Dictionary<string, string>());
await _restWrapper.CrudApiRest<Product, Guid>(Address.ProductController).Update<UpdateProductCommand>(request, token);
_snackbar.Add($"ویرایش محصول {PageDto.PersianName} با موفقیت انجام شد", Severity.Success);
_mudDialog.Close();
@ -205,7 +235,27 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
Discount.StartDate = StartDate.Value;
PageDto.SpecialOffer = Discount;
}
var request = PageDto.Adapt<CreateProductCommand>();
var request = new CreateProductCommand(
PageDto.PersianName,
PageDto.EnglishName,
PageDto.Summery,
PageDto.ExpertCheck,
PageDto.Tags,
PageDto.Warranty,
PageDto.BeDisplayed,
PageDto.Cost,
PageDto.PackingCost,
PageDto.Stock,
PageDto.HasExpressDelivery,
PageDto.MaxOrderCount,
PageDto.IsSpecialOffer,
PageDto.BrandId,
PageDto.CategoryId,
PageDto.SpecialOffer ?? new DiscountSDto(),
PageDto.Specifications,
PageDto.Files,
Faqs,
new Dictionary<string, string>());
await _restWrapper.CrudApiRest<Product, Guid>(Address.ProductController).Create<CreateProductCommand>(request, token);
_snackbar.Add($"ساخت محصول {PageDto.PersianName} با موفقیت انجام شد", Severity.Success);
@ -302,6 +352,28 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
}
public string FaqQuestion { get; set; } = string.Empty;
public string FaqAnswer { get; set; } = string.Empty;
public Dictionary<string, string> Faqs = new();
public void AddFaq()
{
try
{
if (FaqAnswer.IsNullOrEmpty())
throw new Exception("لطفا پاسخ را وارد کنید");
if (FaqQuestion.IsNullOrEmpty())
throw new Exception("لطفا سوال را وارد کنید");
Faqs.Add(FaqQuestion, FaqAnswer);
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
}
}
public void AddSpecification()
{
try
@ -320,7 +392,6 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
_snackbar.Add(e.Message, Severity.Error);
}
}
public void RemoveSpecification(SpecificationSDto specification)
{
var spec = Specifications.FirstOrDefault(s => s.Value == specification.Value && s.Title == specification.Title);
@ -337,7 +408,6 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
if (file is StorageFileSDto storageFile)
Files.Add(storageFile);
}
public void RemoveFile(StorageFileSDto file)
{
Files.Remove(file);

View File

@ -6,122 +6,180 @@
<MudDialog class="mx-auto" DisableSidePadding="true">
<DialogContent>
<MudContainer class="h-full">
<MudDivider class="-mt-3" />
<MudStack Spacing="0">
<MudTabs Outlined="true" Elevation="0" Rounded="true" Centered="true">
<MudTabPanel Text="اطلاعات کلی" Icon="@Icons.Material.Outlined.Info">
<MudStack Spacing="0">
<MudText Typo="Typo.h6">اطلاعات کلی</MudText>
<MudText Typo="Typo.caption">اطلاعات کلی دسته بندی محصول را به دقت وارد کنید</MudText>
</MudStack>
<MudStack class="mx-5 overflow-x-hidden overflow-y-hidden">
<MudGrid>
<MudItem sm="12" lg="4" md="6">
<MudTextField T="string" @bind-Value="@ViewModel.Name" Label="نام دسته بندی" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem sm="12" lg="4" md="6">
<MudSelect T="bool" @bind-Value="@ViewModel.IsMain" Label="آیا دسته بندی اصلی است ؟" ToStringFunc="b=>b.ToPersianString()" Variant="Variant.Outlined" AnchorOrigin="Origin.BottomCenter">
<MudSelectItem T="bool" Value="true" />
<MudSelectItem T="bool" Value="false" />
</MudSelect>
</MudItem>
<MudItem sm="12" lg="4" md="6">
<MudAutocomplete Required="true" ToStringFunc="dto => dto.Name" @bind-Value="@ViewModel.SelectedCategory"
SearchFunc="ViewModel.SearchCategory"
T="ProductCategorySDto"
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>
<MudText Typo="Typo.h6">اطلاعات کلی</MudText>
<MudText Typo="Typo.caption">اطلاعات کلی دسته بندی محصول را به دقت وارد کنید</MudText>
</MudStack>
<MudStack class="overflow-x-hidden overflow-y-hidden">
<MudGrid>
<MudItem sm="12" lg="4" md="6">
<MudTextField T="string" @bind-Value="@ViewModel.Name" Label="نام دسته بندی" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem sm="12" lg="4" md="6">
<MudSelect T="bool" @bind-Value="@ViewModel.IsMain" Label="آیا دسته بندی اصلی است ؟" ToStringFunc="b=>b.ToPersianString()" Variant="Variant.Outlined" AnchorOrigin="Origin.BottomCenter">
<MudSelectItem T="bool" Value="true" />
<MudSelectItem T="bool" Value="false" />
</MudSelect>
</MudItem>
<MudItem sm="12" lg="4" md="6">
<MudAutocomplete Required="true" ToStringFunc="dto => dto.Name" @bind-Value="@ViewModel.SelectedCategory"
SearchFunc="ViewModel.SearchCategory"
T="ProductCategorySDto"
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 sm="12" md="12" lg="12">
<MudStack class="mt-2 mb-4" 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" />
@if (item.IsHeader)
{
<p class="bg-pink-500 px-1.5 py-0.5 absolute bottom-0 mr-2 rounded-lg text-white">هدر</p>
}
@if (item.IsPrimary)
{
<p class="bg-blue-500 px-1.5 py-0.5 absolute bottom-0 mr-2 rounded-lg text-white">اصلی</p>
}
</div>
</MudListItem>
</MudList>
</ProgressIndicatorInPopoverTemplate>
<ItemTemplate Context="e">
<p>@e.Name</p>
</ItemTemplate>
</MudAutocomplete>
</MudItem>
<MudItem sm="12" md="12" lg="12">
<MudStack class="mt-2 mb-4" 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" />
@if (item.IsHeader)
{
<p class="bg-pink-500 px-1.5 py-0.5 absolute bottom-0 mr-2 rounded-lg text-white">هدر</p>
}
@if (item.IsPrimary)
{
<p class="bg-blue-500 px-1.5 py-0.5 absolute bottom-0 mr-2 rounded-lg text-white">اصلی</p>
}
</div>
}
</MudStack>
</MudItem>
<MudItem sm="12" lg="12" md="12">
<MudContainer class="overflow-y-hidden overflow-x-hidden">
<MudStack class="mt-4 mb-2" Spacing="0">
</MudStack>
</MudItem>
<MudItem sm="12" lg="12" md="12">
<MudContainer class="overflow-y-hidden overflow-x-hidden">
<MudStack class="mt-4 mb-2" Spacing="0">
<MudText Typo="Typo.h6">توضیحات تکمیلی</MudText>
<MudText Typo="Typo.caption">می توانید توضیحاتــ تکمیلی محصول را کامل وارد کنید</MudText>
</MudStack>
<div class="!text-black">
<RichTextEditorUi @bind-Text="@ViewModel.Description" />
</div>
</MudContainer>
</MudItem>
<MudText Typo="Typo.h6">توضیحات تکمیلی</MudText>
<MudText Typo="Typo.caption">می توانید توضیحاتــ تکمیلی محصول را کامل وارد کنید</MudText>
</MudStack>
<div class="!text-black">
<RichTextEditorUi @bind-Text="@ViewModel.Description" />
</div>
</MudContainer>
</MudItem>
</MudGrid>
</MudStack>
</MudGrid>
</MudStack>
</MudTabPanel>
<MudStack Row="true" class="w-full mx-4 mb-2">
<MudTabPanel Text="متاتگ و سوالات متداول">
@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>
<MudGrid>
<MudItem xs="12">
<MudText Typo="Typo.h6">سوالات متداول</MudText>
<MudText Typo="Typo.caption">می توانید سوالات متداول شهر موردنظر را وارد کنید</MudText>
<MudGrid class="mt-1">
<MudItem lg="5" md="6">
<MudTextField @bind-Value="@ViewModel.FaqQuestion" T="string" Label="سوال" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="5" md="6">
<MudTextField @bind-Value="@ViewModel.FaqAnswer" T="string" Label="پاسخ" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="2" md="12">
<MudButton Variant="Variant.Filled"
Size="Size.Large"
Color="Color.Info"
class="mt-2 w-full py-3"
OnClick="ViewModel.AddFaq"
StartIcon="@Icons.Material.Outlined.Add">افزودن</MudButton>
</MudItem>
<MudItem sm="12">
<MudExpansionPanels class="mt-1" Elevation="2">
@foreach (var item in ViewModel.Faqs)
{
<MudExpansionPanel>
<TitleContent>
<MudStack Row="true">
<MudIconButton Icon="@Icons.Material.Outlined.Delete"
Size="@Size.Small"
Variant="@Variant.Outlined"
Color="@Color.Error"
OnClick="() => ViewModel.Faqs.Remove(item.Key)" />
<MudText>@item.Key</MudText>
</MudStack>
</TitleContent>
<ChildContent>
@item.Value
</ChildContent>
</MudExpansionPanel>
}
</MudExpansionPanels>
</MudItem>
</MudGrid>
</MudItem>
</MudGrid>
</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
{

View File

@ -94,7 +94,13 @@ public class ProductCategoryActionDialogBoxViewModel:BaseViewModel
var token = await _userUtility.GetBearerTokenAsync();
if (token == null)
throw new AppException("Token is null");
var request = new CreateProductCategoryCommand(Name, Description, IsMain, SelectedCategory?.Id ?? default, Files.ToList());
var request = new CreateProductCategoryCommand(Name,
Description,
IsMain,
SelectedCategory?.Id ?? default,
Files.ToList(),
Faqs,
new Dictionary<string, string>());
await _restWrapper.CrudApiRest<ProductCategory, Guid>(Address.ProductCategoryController).Create<CreateProductCategoryCommand>(request, token);
_mudDialog.Close(DialogResult.Ok(true));
}
@ -127,7 +133,14 @@ public class ProductCategoryActionDialogBoxViewModel:BaseViewModel
IsProcessing = true;
await Task.Delay(1000);
var token = await _userUtility.GetBearerTokenAsync();
var request = new UpdateProductCategoryCommand(Category.Id, Name, Description, IsMain, SelectedCategory?.Id ?? default, Files.ToList());
var request = new UpdateProductCategoryCommand(Category.Id,
Name,
Description,
IsMain,
SelectedCategory?.Id ?? default,
Files.ToList(),
Faqs,
new Dictionary<string, string>());
await _restWrapper.CrudApiRest<ProductCategory, Guid>(Address.ProductCategoryController).Update<UpdateProductCategoryCommand>(request, token);
_mudDialog.Close(DialogResult.Ok(true));
}
@ -177,6 +190,28 @@ public class ProductCategoryActionDialogBoxViewModel:BaseViewModel
}
public string FaqQuestion { get; set; } = string.Empty;
public string FaqAnswer { get; set; } = string.Empty;
public Dictionary<string, string> Faqs = new();
public void AddFaq()
{
try
{
if (FaqAnswer.IsNullOrEmpty())
throw new Exception("لطفا پاسخ را وارد کنید");
if (FaqQuestion.IsNullOrEmpty())
throw new Exception("لطفا سوال را وارد کنید");
Faqs.Add(FaqQuestion, FaqAnswer);
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
}
}
public async Task SelectFileAsync()
{
DialogOptions maxWidth = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, DisableBackdropClick = true };

View File

@ -64,44 +64,11 @@
<MudMainContent class="bg-[--mud-palette-background-grey] h-screen overflow-y-auto">
@Body
</MudMainContent>
<div dir="ltr">
<PWAUpdater Text="@_updateText" ButtonCaption="اپدیت کنید" />
</div>
</MudLayout>
</MudRTLProvider>
@* <MudRTLProvider RightToLeft="true">
<MudThemeProvider IsDarkMode="@MainTheme.IsDarkMode" Theme="@MainTheme.MyCustomTheme"/>
<MudDialogProvider/>
<MudSnackbarProvider/>
<MudLayout>
<MudAppBar class="py-2" Color="Color.Transparent" Fixed="false" Elevation="2">
<MudHidden Breakpoint="Breakpoint.MdAndUp">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" OnClick="@ToggleDrawer" Edge="Edge.Start"/>
</MudHidden>
<RadzenGravatar class="h-14 w-14" Email="@_user?.Email"/>
<MudStack class="mr-2" Spacing="0">
<MudText Color="Color.Inherit" Typo="Typo.body1"><b>@_user?.FullName</b></MudText>
<MudText Color="Color.Inherit" Typo="Typo.caption">@_user?.PhoneNumber</MudText>
</MudStack>
<MudSpacer/>
<MudToggleIconButton @bind-Toggled="@MainTheme.IsDarkMode"
Icon="@Icons.Material.Outlined.DarkMode" Color="@Color.Default" Title="تاریک"
ToggledIcon="@Icons.Material.Filled.LightMode" ToggledColor="@Color.Default" ToggledTitle="روشن"/>
<MudIconButton Size="Size.Medium" Color="Color.Error" OnClick="LogoutAsync" Icon="@Icons.Material.Outlined.ExitToApp"/>
</MudAppBar>
<MudDrawer @bind-Open="@open" ClipMode="DrawerClipMode.Always" Breakpoint="Breakpoint.MdAndUp" Elevation="1" Variant="@DrawerVariant.Responsive">
<SideBarUi/>
</MudDrawer>
<MudMainContent class="h-screen">
@Body
</MudMainContent>
</MudLayout>
<div dir="ltr">
<PWAUpdater Align="PWAUpdater.Aligns.Buttom" Text="@_updateText" ButtonCaption="اپدیت کنید"/>
</div>
</MudRTLProvider> *@
</Authorized>
<NotAuthorized>

View File

@ -23,4 +23,5 @@ public static class Address
public static string DashboardController => $"/dashboard";
public static string SettingController => $"/setting";
public static string DistrictController => $"/district";
public static string FaqController => $"/faq";
}

View File

@ -19,7 +19,7 @@ public class BaseViewModel<TPageDto>
public bool IsProcessing { get; set; } = false;
public TPageDto PageDto { get; set; }
private string[] _permissions = new string[]{};
private readonly IUserUtility _userUtility;
protected readonly IUserUtility _userUtility;
public bool IsPermitted { get; set; } = false;
public BaseViewModel(IUserUtility userUtility,params string[] permissions)
{

View File

@ -96,6 +96,7 @@
<Using Include="Netina.Domain.Entities.ProductCategories" />
<Using Include="Netina.Domain.Entities.Products" />
<Using Include="Netina.Domain.Enums" />
<Using Include="Netina.Domain.Extensions" />
<Using Include="Netina.Domain.MartenEntities.Pages" />
<Using Include="Netina.Domain.MartenEntities.Settings" />
<Using Include="Netina.Domain.Models.Districts" />

View File

@ -1,4 +1,5 @@
@page "/management/faqs"
@page "/setting/faq"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@inject IDialogService DialogService
@ -10,45 +11,54 @@
<MudStack class="h-full w-full p-8">
<MudGrid>
<MudItem xs="12">
<MudStack Row="true" class="mb-8">
<MudText Typo="Typo.h4">سوالات متداول فروشگاه من</MudText>
<MudStack Row="true" class="mb-5">
<MudText Typo="Typo.h4">سوالات متداول</MudText>
<MudSpacer />
<MudButton Variant="Variant.Filled" Size="Size.Large" Color="Color.Success" OnClick="@ViewModel.SaveAsync">ذخیره سوالات</MudButton>
<MudButton Variant="Variant.Filled"
DisableElevation="true"
StartIcon="@Icons.Material.Outlined.Add"
Color="Color.Secondary"
OnClick="@ViewModel.AddClicked"
class="my-auto">افزودن سوالات متداول جدید</MudButton>
</MudStack>
<MudGrid>
<MudItem sm="4">
<MudTextField T="string" Label="سوال" @bind-Value="@ViewModel.Question" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem sm="6">
<MudTextField T="string" Label="پاسخ" @bind-Value="@ViewModel.Answer" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem>
<MudButton class="mt-2 py-3" EndIcon="@Icons.Material.Outlined.Add"
Variant="Variant.Outlined" Size="Size.Large" Color="Color.Info" OnClick="@ViewModel.AddNewQuestion">افزودن</MudButton>
</MudItem>
</MudGrid>
<MudPaper>
<MudExpansionPanels class="mt-8">
@foreach (var item in ViewModel.PageDto.Faqs)
{
<MudExpansionPanel>
<TitleContent>
<MudDataGrid FixedFooter="true" FixedHeader="true" Striped="true"
T="BaseFaq" Items="@ViewModel.PageDto" Filterable="false"
CurrentPage="@ViewModel.CurrentPage"
RowsPerPage="20"
Loading="@ViewModel.IsProcessing"
SortMode="@SortMode.None" Groupable="false">
<Columns>
<PropertyColumn Title="عنوان" Property="arg => arg.Title" />
<PropertyColumn Title="اسلاگ" Property="arg => arg.Slug" />
<PropertyColumn T="BaseFaq" TProperty="int" Title="تعداد سوالات" Property="arg => arg.Faqs.Count" />
<TemplateColumn CellClass="d-flex justify-end">
<CellTemplate>
<MudStack Row="true">
<MudIconButton Icon="@Icons.Material.Outlined.Delete"
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Size="@Size.Small"
Variant="@Variant.Outlined"
Color="@Color.Error"
OnClick="()=>ViewModel.RemoveQuestion(item.Key)" />
<MudText>@item.Key</MudText>
</MudStack>
</TitleContent>
<ChildContent>
@item.Value
</ChildContent>
</MudExpansionPanel>
}
</MudExpansionPanels>
Color="@Color.Info"
OnClick="async()=>await ViewModel.EditClicked(context.Item)" />
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Size="@Size.Small"
Variant="@Variant.Outlined"
OnClick="async () => await ViewModel.DeleteAsync(context.Item.Id)"
Color="@Color.Error" />
</MudStack>
</CellTemplate>
</TemplateColumn>
</Columns>
<PagerContent>
<MudStack Row="true" class="w-full">
<MudPagination Rectangular="true" Variant="Variant.Filled" Count="@ViewModel.PageCount"
SelectedChanged="@ViewModel.ChangePageAsync" class="mx-auto my-4" />
</MudStack>
</PagerContent>
</MudDataGrid>
</MudPaper>
</MudItem>
</MudGrid>
@ -59,8 +69,8 @@
public FaqManagementPageViewModel ViewModel { get; set; }
protected override async Task OnInitializedAsync()
{
ViewModel = new FaqManagementPageViewModel(NavigationManager, Snackbar, UserUtility, RestWrapper, DialogService);
ViewModel = new FaqManagementPageViewModel(DialogService, NavigationManager, RestWrapper, Snackbar, UserUtility);
await ViewModel.InitializeAsync();
await base.OnInitializedAsync();
}
}
}

View File

@ -1,72 +1,25 @@
namespace Netina.AdminPanel.PWA.Pages;
using Netina.Domain.MartenEntities.Faqs;
public class FaqManagementPageViewModel : BaseViewModel<FAQPage>
namespace Netina.AdminPanel.PWA.Pages;
public class FaqManagementPageViewModel(
IDialogService dialogService,
NavigationManager navigationManager,
IRestWrapper restWrapper,
ISnackbar snackbar,
IUserUtility userUtility)
: BaseViewModel<ObservableCollection<BaseFaq>> (userUtility)
{
private readonly NavigationManager _navigationManager;
private readonly ISnackbar _snackbar;
private readonly IUserUtility _userUtility;
private readonly IDialogService _dialogService;
private readonly IRestWrapper _restWrapper;
public int CurrentPage = 0;
public int PageCount = 2;
public FaqManagementPageViewModel(NavigationManager navigationManager, ISnackbar snackbar, IUserUtility userUtility, IRestWrapper restWrapper, IDialogService dialogService) : base(userUtility)
{
_navigationManager = navigationManager;
_snackbar = snackbar;
_userUtility = userUtility;
_restWrapper = restWrapper;
_dialogService = dialogService;
}
public string Question { get; set; } = string.Empty;
public string Answer { get; set; } = string.Empty;
private PageActionRequestDto? _request;
public override async Task InitializeAsync()
{
try
{
var token = await _userUtility.GetBearerTokenAsync();
if (token == null)
throw new Exception("Token is null");
IsProcessing = true;
PageDto.Faqs.Clear();
var typeName = typeof(FAQPage).FullName;
var dto = await _restWrapper.PageRestApi.ReadByType(typeName, token);
if (!dto.Data.IsNullOrEmpty())
{
PageDto = dto.GetData<FAQPage>();
_request = new PageActionRequestDto
{
Title = dto.Title,
Content = dto.Content,
Description = dto.Description,
Id = dto.Id,
IsCustomPage = dto.IsCustomPage,
IsHtmlBasePage = dto.IsHtmlBasePage,
Slug = dto.Slug,
Type = typeof(FAQPage).FullName
};
}
}
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;
}
await GetEntitiesAsync();
await base.InitializeAsync();
}
public async Task SaveAsync()
public async Task GetEntitiesAsync()
{
try
{
@ -74,64 +27,97 @@ public class FaqManagementPageViewModel : BaseViewModel<FAQPage>
if (token == null)
throw new Exception("Token is null");
IsProcessing = true;
var request = new PageActionRequestDto
{
Title = "سوالات متداول",
Content = string.Empty,
Description = string.Empty,
Data = PageDto,
IsCustomPage = true,
IsHtmlBasePage = false,
Slug = "faq",
Type = typeof(FAQPage).FullName
};
if (_request != null)
{
request = _request;
}
request.Data = PageDto;
await _restWrapper.PageRestApi.CreatePage(request, token);
var faqs = await restWrapper.FaqApiRest.GetFaqs(CurrentPage, token);
PageDto.Clear();
faqs.ForEach(f => PageDto.Add(f));
if (PageDto.Count % 20 == 0)
PageCount = CurrentPage + 2;
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
_snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error);
if (ex.StatusCode == HttpStatusCode.Unauthorized)
{
await userUtility.LogoutAsync();
navigationManager.NavigateTo("login", true, true);
}
snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error);
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
snackbar.Add(e.Message, Severity.Error);
}
finally
{
IsProcessing = false;
}
}
public void AddNewQuestion()
{
try
{
if (Question.IsNullOrEmpty())
throw new Exception("سوال را وارد کنید");
if (Answer.IsNullOrEmpty())
throw new Exception("پاسخ را وارد کنید");
PageDto.Faqs.Add(Question,Answer);
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
}
}
public void RemoveQuestion(string question)
public async Task ChangePageAsync(int page)
{
try
CurrentPage = page - 1;
await GetEntitiesAsync();
}
public async Task AddClicked()
{
DialogOptions maxWidth = new DialogOptions()
{ MaxWidth = MaxWidth.Medium, FullWidth = true, DisableBackdropClick = true };
var reference = await dialogService.ShowAsync<FaqActionDialogBox>("افزودن اژانس هواپیمایی جدید", maxWidth);
var result = await reference.Result;
if (result.Data is bool and true)
await InitializeAsync();
}
public async Task EditClicked(BaseFaq item)
{
DialogOptions maxWidth = new DialogOptions()
{ MaxWidth = MaxWidth.Medium, FullWidth = true, DisableBackdropClick = true };
var parameters = new DialogParameters<FaqActionDialogBox>();
parameters.Add(x => x.Faq, item);
var reference = await dialogService.ShowAsync<FaqActionDialogBox>($"ویرایش سوالات متداول {item.Title}", parameters, maxWidth);
var result = await reference.Result;
if (result.Data is bool and true)
await InitializeAsync();
}
public async Task DeleteAsync(Guid selectedId)
{
var reference = await dialogService.ShowQuestionDialog($"آیا از حذف سوالات متداول مورد نظر اطمینان دارید ?");
var result = await reference.Result;
if (!result.Canceled)
{
PageDto.Faqs.Remove(question);
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
try
{
IsProcessing = true;
var token = await _userUtility.GetBearerTokenAsync();
if (token == null)
throw new Exception("Token is null");
await restWrapper.FaqApiRest.Delete(selectedId, token);
await InitializeAsync();
snackbar.Add("حذف سوالات متداول با موفقیت انجام شد", Severity.Success);
}
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,21 @@
using Netina.Domain.MartenEntities.Faqs;
namespace Netina.AdminPanel.PWA.Services.RestServices;
public interface IFaqApiRest
{
[Delete("/{id}")]
Task Delete(Guid id, [Header("Authorization")] string token);
[Post("")]
Task Create([Body] BaseFaq faq, [Header("Authorization")] string token);
[Put("")]
Task Update([Body] BaseFaq faq, [Header("Authorization")] string token);
[Get("")]
Task<List<BaseFaq>> GetFaqs([Query]int page,[Header("Authorization")] string token);
[Get("/slug")]
Task<BaseFaq> ReadOne([Query]string slug);
}

View File

@ -23,4 +23,5 @@ public interface IRestWrapper
public IDashboardApiRest DashboardApiRest { get; }
public ISettingRestApi SettingRestApi { get; }
public IDistrictApiRest DistrictApiRest { get; }
public IFaqApiRest FaqApiRest { get; }
}

View File

@ -1,12 +1,9 @@
namespace Netina.AdminPanel.PWA.Services.RestServices;
public class RestWrapper : IRestWrapper
public class RestWrapper(IConfiguration configuration) : IRestWrapper
{
private string baseApiAddress;
public RestWrapper(IConfiguration configuration)
{
baseApiAddress = configuration.GetValue<string>("ApiUrl") ?? string.Empty;
}
private string baseApiAddress = configuration.GetValue<string>("ApiUrl") ?? string.Empty;
private static RefitSettings setting = new RefitSettings(new NewtonsoftJsonContentSerializer(new JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented,
@ -39,4 +36,5 @@ public class RestWrapper : IRestWrapper
public ISettingRestApi SettingRestApi => RestService.For<ISettingRestApi>($"{baseApiAddress}{Address.SettingController}", setting);
public IDashboardApiRest DashboardApiRest => RestService.For<IDashboardApiRest>($"{baseApiAddress}{Address.DashboardController}", setting);
public IDistrictApiRest DistrictApiRest => RestService.For<IDistrictApiRest>($"{baseApiAddress}{Address.DistrictController}", setting);
public IFaqApiRest FaqApiRest => RestService.For<IFaqApiRest>($"{baseApiAddress}{Address.FaqController}", setting);
}

View File

@ -19,4 +19,5 @@
@using Netina.Domain.Entities.Users
@using Netina.AdminPanel.PWA.Components.Originals
@using Netina.Domain.Models.Claims
@using MudBlazor.Services
@using MudBlazor.Services
@using Netina.Domain.MartenEntities.Faqs