Add product and category management pages

This commit is contained in:
masoodafar-web
2025-11-27 04:20:28 +03:30
parent a607ddbea5
commit ee26d36536
56 changed files with 1580 additions and 189 deletions

View File

@@ -17,7 +17,7 @@
}
});
}
</NotAuthorized>
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
@@ -27,4 +27,10 @@
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
</CascadingAuthenticationState>
@code{
}

View File

@@ -21,10 +21,15 @@
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
<PackageReference Include="Foursat.BackOffice.BFF.Category.Protobuf" Version="0.0.1" />
<PackageReference Include="Foursat.BackOffice.BFF.Otp.Protobuf" Version="0.0.111" />
<PackageReference Include="FourSat.BackOffice.BFF.Package.Protobuf" Version="0.0.111" />
<PackageReference Include="Foursat.BackOffice.BFF.Products.Protobuf" Version="0.0.3" />
<PackageReference Include="Foursat.BackOffice.BFF.Role.Protobuf" Version="0.0.111" />
<PackageReference Include="Foursat.BackOffice.BFF.User.Protobuf" Version="0.0.111" />

View File

@@ -6,9 +6,9 @@
<MudStack Row="true" AlignItems="AlignItems.Center">
<MudText Typo="Typo.subtitle2">فیلتر ها</MudText>
<MudSpacer />
@if (IsFilterd)
@if (IsFiltered)
{
<MudButton Color="Color.Error" Variant="Variant.Text" Size="Size.Small" OnClick="@(async () => await OnClearFilterClick.InvokeAsync())">خذف فیلتر</MudButton>
<MudButton Color="Color.Error" Variant="Variant.Text" Size="Size.Small" OnClick="@(async () => await OnClearFilterClick.InvokeAsync())">حذف فیلتر</MudButton>
}
</MudStack>
<MudItem Style="height:100%;">
@@ -32,4 +32,3 @@
</MudItem>
</MudStack>

View File

@@ -3,15 +3,9 @@ using Microsoft.AspNetCore.Components;
namespace BackOffice.Common.BaseComponents;
public partial class BasePageComponent
{
[Parameter] public RenderFragment Filters { get; set; }
[Parameter] public RenderFragment Content { get; set; }
[Parameter] public bool IsFilterd { get; set; }
[Parameter] public RenderFragment Filters { get; set; } = default!;
[Parameter] public RenderFragment Content { get; set; } = default!;
[Parameter] public bool IsFiltered { get; set; }
[Parameter] public EventCallback OnSubmitClick { get; set; }
[Parameter] public EventCallback OnClearFilterClick { get; set; }
private bool _open = true;
private void ToggleDrawer()
{
_open = !_open;
}
}
}

View File

@@ -55,12 +55,17 @@ public partial class DateRangePicker
}
private async Task OnClickOK()
{
_picker.CloseAsync();
await OnChanged.InvokeAsync(_picker.DateRange);
if (_picker.DateRange is not null)
{
await OnChanged.InvokeAsync(_picker.DateRange);
}
await _picker.CloseAsync();
}
private async Task OnClickClear()
{
_picker.CloseAsync();
await OnChanged.InvokeAsync(_picker.DateRange);
_dateRange = new DateRange();
await OnChanged.InvokeAsync(_dateRange);
await _picker.CloseAsync();
}
}
}

View File

@@ -2,23 +2,22 @@
using BackOffice.BFF.Otp.Protobuf.Protos.Otp;
using BackOffice.BFF.Package.Protobuf.Protos.Package;
using BackOffice.BFF.Role.Protobuf.Protos.Role;
using BackOffice.BFF.Products.Protobuf.Protos.Products;
using BackOffice.BFF.User.Protobuf.Protos.User;
using BackOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
using BackOffice.BFF.UserRole.Protobuf.Protos.UserRole;
using BackOffice.BFF.Category.Protobuf.Protos.Category;
using BackOffice.Common.Utilities;
using Blazored.LocalStorage;
using Google.Protobuf.Reflection;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using MudBlazor.Services;
using System.Text.Json;
using System.Text.Json.Serialization;
using static MudBlazor.Colors;
namespace Microsoft.Extensions.DependencyInjection;
@@ -48,25 +47,35 @@ public static class ConfigureServices
public static IServiceCollection AddGrpcServices(this IServiceCollection services, IConfiguration configuration) //
public static IServiceCollection AddGrpcServices(this IServiceCollection services, IConfiguration configuration)
{
var baseUri = configuration["GwUrl"];
Console.WriteLine();
Console.WriteLine(baseUri);
var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
httpClient.Timeout = TimeSpan.FromMinutes(10); // TODO Check Timeout
var serviceProvider = services.BuildServiceProvider();
var channel = CreateAuthenticatedChannel(baseUri, httpClient, serviceProvider);
services.AddSingleton<CallInvoker>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
var baseUri = config["GwUrl"];
if (string.IsNullOrWhiteSpace(baseUri))
{
throw new InvalidOperationException("Configuration value 'GwUrl' is missing or empty.");
}
var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()))
{
Timeout = TimeSpan.FromMinutes(10)
};
services.AddTransient(sp => new OtpContract.OtpContractClient(channel));
services.AddTransient(sp => new PackageContract.PackageContractClient(channel));
services.AddTransient(sp => new RoleContract.RoleContractClient(channel));
services.AddTransient(sp => new UserContract.UserContractClient(channel));
services.AddTransient(sp => new UserAddressContract.UserAddressContractClient(channel));
services.AddTransient(sp => new UserOrderContract.UserOrderContractClient(channel));
services.AddTransient(sp => new UserRoleContract.UserRoleContractClient(channel));
return CreateAuthenticatedChannel(baseUri, httpClient, sp);
});
services.AddTransient(sp => new OtpContract.OtpContractClient(sp.GetRequiredService<CallInvoker>()));
services.AddTransient(sp => new PackageContract.PackageContractClient(sp.GetRequiredService<CallInvoker>()));
services.AddTransient(sp => new ProductsContract.ProductsContractClient(sp.GetRequiredService<CallInvoker>()));
services.AddTransient(sp => new RoleContract.RoleContractClient(sp.GetRequiredService<CallInvoker>()));
services.AddTransient(sp => new UserContract.UserContractClient(sp.GetRequiredService<CallInvoker>()));
services.AddTransient(sp => new UserAddressContract.UserAddressContractClient(sp.GetRequiredService<CallInvoker>()));
services.AddTransient(sp => new UserOrderContract.UserOrderContractClient(sp.GetRequiredService<CallInvoker>()));
services.AddTransient(sp => new UserRoleContract.UserRoleContractClient(sp.GetRequiredService<CallInvoker>()));
services.AddTransient(sp => new CategoryContract.CategoryContractClient(sp.GetRequiredService<CallInvoker>()));
return services;
}
@@ -98,7 +107,6 @@ public static class ConfigureServices
MaxReceiveMessageSize = 1000 * 1024 * 1024, // 1 GB
MaxSendMessageSize = 1000 * 1024 * 1024 // 1 GB
});
var invoker = channel.Intercept(new ErrorHandlerInterceptor());
return invoker;
return channel.Intercept(new ErrorHandlerInterceptor());
}
}
}

View File

@@ -1,14 +1,15 @@
using Grpc.Core.Interceptors;
using Grpc.Core;
using Grpc.Core;
using Grpc.Core.Interceptors;
using MudBlazor;
namespace BackOffice.Common.Utilities;
public class ErrorHandlerInterceptor : Interceptor
{
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
var call = continuation(request, context);
@@ -28,8 +29,9 @@ public class ErrorHandlerInterceptor : Interceptor
}
catch (Exception ex)
{
GlobalConstants.ConstSnackbar.Add(ex.Message, severity: MudBlazor.Severity.Error);
var message = ex.Message.ExtractUserFriendlyMessage();
GlobalConstants.ConstSnackbar?.Add(message, Severity.Error);
throw;
}
}
}
}

View File

@@ -8,6 +8,5 @@ public static class GlobalConstants
public const string DateTimeFormat = "MMM dd, yyyy - HH:mm";
public static string JwtTokenKey = "AuthToken";
public const string SuccessMsg = "با موفقیت انجام شد";
public static ISnackbar ConstSnackbar;
}
public static ISnackbar? ConstSnackbar { get; set; }
}

View File

@@ -11,4 +11,6 @@ public static class RouteConstance
public const string UserOrder = "/UserOrderPage/";
public const string UserRole = "/UserRolePage/";
public const string UserAddress = "/UserAddressPage/";
public const string Products = "/ProductsPage/";
public const string Category = "/CategoryPage/";
}

View File

@@ -0,0 +1,15 @@
@using BackOffice.BFF.Products.Protobuf.Protos.Products
<MudAutocomplete T="GetAllProductsByFilterResponseModel"
Label="@Label"
Value="@_item"
DebounceInterval="700"
ValueChanged="@((e) => OnSelected(e))"
ToStringFunc="@(e => e == null ? null : e.Title)"
SearchFunc="@Search"
Variant="Variant.Outlined"
Clearable="true"
ShowProgressIndicator="true"
CoerceText="false"
OnClearButtonClick="()=> OnSelected(null)" />

View File

@@ -0,0 +1,79 @@
using BackOffice.BFF.Products.Protobuf.Protos.Products;
using Microsoft.AspNetCore.Components;
namespace BackOffice.Pages.AutoComplete;
public partial class ProductsAutoComplete
{
[Inject] public ProductsContract.ProductsContractClient ProductsService { get; set; } = default!;
[Parameter] public long? Value { get; set; }
[Parameter] public string? Label { get; set; } = "محصول";
[Parameter] public EventCallback<long?> ValueChanged { get; set; }
private List<GetAllProductsByFilterResponseModel> _items = new();
private GetAllProductsByFilterResponseModel? _item;
protected override async void OnParametersSet()
{
await base.OnParametersSetAsync();
if (Value.HasValue && Value.Value > 0)
{
var getAll = await ProductsService.GetAllProductsByFilterAsync(new GetAllProductsByFilterRequest
{
Filter = new GetAllProductsByFilterFilter
{
Id = Value.Value
},
PaginationState = new PaginationState
{
PageNumber = 1,
PageSize = 1
}
});
if (getAll?.Models != null && getAll.Models.Any())
{
_item = getAll.Models.First();
StateHasChanged();
}
}
}
private async Task OnSelected(GetAllProductsByFilterResponseModel? model)
{
if (model == null)
{
_item = null;
await ValueChanged.InvokeAsync(null);
}
else
{
_item = model;
await ValueChanged.InvokeAsync(model.Id);
}
}
private async Task<IEnumerable<GetAllProductsByFilterResponseModel>> Search(string value, CancellationToken token)
{
var request = new GetAllProductsByFilterRequest
{
Filter = new GetAllProductsByFilterFilter
{
Title = string.IsNullOrWhiteSpace(value) ? null : value
},
PaginationState = new PaginationState
{
PageNumber = 1,
PageSize = 9
}
};
var getAll = await ProductsService.GetAllProductsByFilterAsync(request);
_items = getAll?.Models?.ToList() ?? new List<GetAllProductsByFilterResponseModel>();
return _items;
}
}

View File

@@ -0,0 +1,83 @@
@attribute [Route(RouteConstance.Category)]
@using BackOffice.BFF.Category.Protobuf.Protos.Category
@using BackOffice.Common.BaseComponents
@using DataModel = BackOffice.BFF.Category.Protobuf.Protos.Category.GetAllCategoryByFilterResponseModel
<BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
<Filters>
<MudTextField T="string" Clearable="true" Label="عنوان" @bind-Value="@_request.Filter.Title" Variant="Variant.Outlined" Margin="Margin.Dense" />
</Filters>
<Content>
<MudDataGrid T="DataModel"
ServerData="@(new Func<GridState<DataModel>, Task<GridData<DataModel>>>(ServerReload))"
Hover="true"
@ref="_gridData"
Height="72vh">
<ColGroup>
<col />
<col />
<col />
<col />
<col />
<col style="width: 80px;" />
</ColGroup>
<ToolBarContent>
<MudText>مدیریت دسته‌بندی‌ها</MudText>
<MudSpacer />
<MudStack Spacing="2" Row="true" Justify="Justify.Center" AlignItems="AlignItems.Center">
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Size="Size.Large"
ButtonType="ButtonType.Button"
OnClick="CreateNew"
Style="cursor:pointer;">
افزودن
</MudButton>
</MudStack>
</ToolBarContent>
<Columns>
<PropertyColumn Property="x => x.Id" Title="شناسه" />
<PropertyColumn Property="x => x.Name" Title="نام لاتین" />
<PropertyColumn Property="x => x.Title" Title="عنوان" />
<PropertyColumn Property="x => x.ParentId" Title="شناسه والد" />
<TemplateColumn Title="فعال؟">
<CellTemplate>
<MudChip Color="@(context.Item.IsActive ? Color.Success : Color.Error)"
Variant="Variant.Filled"
Size="Size.Small">
@(context.Item.IsActive ? "فعال" : "غیرفعال")
</MudChip>
</CellTemplate>
</TemplateColumn>
<TemplateColumn StickyLeft="true" Title="عملیات" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
<CellTemplate>
<MudStack Row="true" AlignItems="AlignItems.Center">
<MudTooltip Text="ویرایش">
<MudIconButton Icon="@Icons.Material.Filled.EditNote"
Size="Size.Small"
ButtonType="ButtonType.Button"
OnClick="@(() => Update(context.Item))"
Style="cursor:pointer;" />
</MudTooltip>
<MudTooltip Text="حذف">
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline"
Size="Size.Small"
ButtonType="ButtonType.Button"
OnClick="@(() => OnDelete(context.Item))"
Style="cursor:pointer;" />
</MudTooltip>
</MudStack>
</CellTemplate>
</TemplateColumn>
</Columns>
<PagerContent>
<MudDataGridPager T="DataModel"
PageSizeOptions="@(new int[] { 30, 60, 90 })"
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
RowsPerPageString="تعداد سطرهای صفحه" />
</PagerContent>
</MudDataGrid>
</Content>
</BasePageComponent>

View File

@@ -0,0 +1,120 @@
using BackOffice.BFF.Category.Protobuf.Protos.Category;
using BackOffice.Common.BaseComponents;
using Mapster;
using Microsoft.AspNetCore.Components;
using MudBlazor;
using DataModel = BackOffice.BFF.Category.Protobuf.Protos.Category.GetAllCategoryByFilterResponseModel;
namespace BackOffice.Pages.Category;
public partial class CategoryMainPage
{
[Inject] public CategoryContract.CategoryContractClient CategoryContract { get; set; } = default!;
private BasePageComponent _basePage = default!;
private MudDataGrid<DataModel> _gridData = default!;
private GetAllCategoryByFilterRequest _request = new() { Filter = new() };
private async Task<GridData<DataModel>> ServerReload(GridState<DataModel> state)
{
_request.Filter ??= new();
_request.PaginationState ??= new();
_request.PaginationState.PageNumber = state.Page + 1;
_request.PaginationState.PageSize = state.PageSize;
var result = await CategoryContract.GetAllCategoryByFilterAsync(_request);
if (result != null && result.Models != null && result.Models.Any())
{
return new GridData<DataModel>
{
Items = result.Models.ToList(),
TotalItems = (int)result.MetaData.TotalCount
};
}
return new GridData<DataModel>();
}
public async Task CreateNew()
{
var dialog = await DialogService.ShowAsync<CreateOrUpdateCategoryDialog>(
"افزودن دسته‌بندی",
new DialogParameters<CreateOrUpdateCategoryDialog>
{
{ x => x.Model, new CreateNewCategoryRequest { IsActive = true } }
},
new DialogOptions { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Small });
var result = await dialog.Result;
if (!result.Canceled)
{
await ReloadData();
Snackbar.Add("دسته‌بندی با موفقیت ایجاد شد.", Severity.Success);
}
}
public async Task Update(DataModel model)
{
var getResponse = await CategoryContract.GetCategoryAsync(new GetCategoryRequest { Id = model.Id });
var editModel = getResponse.Adapt<UpdateCategoryRequest>();
var dialog = await DialogService.ShowAsync<CreateOrUpdateCategoryDialog>(
"ویرایش دسته‌بندی",
new DialogParameters<CreateOrUpdateCategoryDialog>
{
{ x => x.EditModel, editModel }
},
new DialogOptions { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Small });
var result = await dialog.Result;
if (!result.Canceled)
{
await ReloadData();
Snackbar.Add("دسته‌بندی با موفقیت ویرایش شد.", Severity.Success);
}
}
private async Task OnDelete(DataModel model)
{
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small };
bool? result = await DialogService.ShowMessageBox(
"اخطار",
"آیا از حذف این دسته‌بندی مطمئن هستید؟",
yesText: "حذف",
cancelText: "لغو",
options: options);
if (result == true)
{
await CategoryContract.DeleteCategoryAsync(new DeleteCategoryRequest { Id = model.Id });
await ReloadData();
Snackbar.Add("دسته‌بندی با موفقیت حذف شد.", Severity.Success);
}
StateHasChanged();
}
private async Task ReloadData()
{
if (_gridData != null)
{
await _gridData.ReloadServerData();
}
}
public async Task OnFilterSubmit()
{
_basePage.IsFiltered = true;
StateHasChanged();
await ReloadData();
}
public async Task OnFilterCleared()
{
_basePage.IsFiltered = false;
StateHasChanged();
_request = new GetAllCategoryByFilterRequest { Filter = new GetAllCategoryByFilterFilter() };
await ReloadData();
}
}

View File

@@ -0,0 +1,48 @@
@using BackOffice.BFF.Category.Protobuf.Protos.Category
@using Microsoft.AspNetCore.Components.Forms
<MudDialog>
<DialogContent>
<MudStack Spacing="2">
<MudTextField T="string"
@bind-Value="Name"
Label="نام لاتین"
Variant="Variant.Outlined"
Margin="Margin.Dense" />
<MudTextField T="string"
@bind-Value="Title"
Label="عنوان"
Variant="Variant.Outlined"
Margin="Margin.Dense" />
<MudTextField T="string"
@bind-Value="Description"
Label="توضیحات"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Lines="3"
TextArea="true" />
<MudNumericField T="long?"
@bind-Value="ParentId"
HideSpinButtons="true"
Label="شناسه والد (اختیاری)"
Variant="Variant.Outlined"
Margin="Margin.Dense" />
<MudNumericField T="int"
@bind-Value="SortOrder"
HideSpinButtons="true"
Label="ترتیب نمایش"
Variant="Variant.Outlined"
Margin="Margin.Dense" />
<MudCheckBox T="bool" @bind-Checked="IsActive" Label="فعال" />
</MudStack>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">لغو</MudButton>
<MudButton Color="Color.Primary" OnClick="Save">ثبت</MudButton>
</DialogActions>
</MudDialog>

View File

@@ -0,0 +1,86 @@
using BackOffice.BFF.Category.Protobuf.Protos.Category;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace BackOffice.Pages.Category;
public partial class CreateOrUpdateCategoryDialog
{
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!;
[Inject] public CategoryContract.CategoryContractClient CategoryContract { get; set; } = default!;
[Parameter] public CreateNewCategoryRequest? Model { get; set; }
[Parameter] public UpdateCategoryRequest? EditModel { get; set; }
private bool IsEdit => EditModel != null;
private string Name { get; set; } = string.Empty;
private string Title { get; set; } = string.Empty;
private string? Description { get; set; }
private long? ParentId { get; set; }
private bool IsActive { get; set; } = true;
private int SortOrder { get; set; }
protected override void OnInitialized()
{
if (IsEdit && EditModel != null)
{
Name = EditModel.Name;
Title = EditModel.Title;
Description = EditModel.Description;
ParentId = EditModel.ParentId;
IsActive = EditModel.IsActive;
SortOrder = EditModel.SortOrder;
}
else if (Model != null)
{
Name = Model.Name;
Title = Model.Title;
Description = Model.Description;
ParentId = Model.ParentId;
IsActive = Model.IsActive;
SortOrder = Model.SortOrder;
}
}
private async Task Save()
{
if (IsEdit && EditModel != null)
{
var request = new UpdateCategoryRequest
{
Id = EditModel.Id,
Name = Name,
Title = Title,
Description = Description,
ParentId = ParentId,
IsActive = IsActive,
SortOrder = SortOrder
};
await CategoryContract.UpdateCategoryAsync(request);
}
else
{
var request = new CreateNewCategoryRequest
{
Name = Name,
Title = Title,
Description = Description,
ParentId = ParentId,
IsActive = IsActive,
SortOrder = SortOrder
};
await CategoryContract.CreateNewCategoryAsync(request);
}
MudDialog.Close(DialogResult.Ok(true));
}
private void Cancel()
{
MudDialog.Cancel();
}
}

View File

@@ -15,7 +15,6 @@ public partial class LoginPage
[Inject] public OtpContract.OtpContractClient OtpContract { get; set; }
private async Task OnSubmitClick()
{
Console.WriteLine(OtpContract == null);
await _form.Validate();
if (!_form.IsValid)
return;
@@ -31,11 +30,12 @@ public partial class LoginPage
}
catch (Exception ex)
{
Snackbar.Add(message: ex.Message, severity: Severity.Error, null);
var message = ex.Message.ExtractUserFriendlyMessage();
Snackbar.Add(message: message, severity: Severity.Error);
}
_isLoading = false;
StateHasChanged();
}
}
}

View File

@@ -45,7 +45,8 @@ public partial class VerifyCodePage
}
catch (Exception ex)
{
Snackbar.Add(message: ex.Message, severity: Severity.Error, null);
var message = ex.Message.ExtractUserFriendlyMessage();
Snackbar.Add(message: message, severity: Severity.Error);
}
_isLoading = false;
@@ -68,7 +69,8 @@ public partial class VerifyCodePage
}
catch (Exception ex)
{
Snackbar.Add(message: ex.Message, severity: Severity.Error, null);
var message = ex.Message.ExtractUserFriendlyMessage();
Snackbar.Add(message: message, severity: Severity.Error);
}
_isLoading = false;
@@ -94,4 +96,4 @@ public partial class VerifyCodePage
}
}), null, 1000, 1000);
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@
@using BackOffice.Common.BaseComponents
@using DataModel = BackOffice.BFF.Package.Protobuf.Protos.Package.GetAllPackageByFilterResponseModel
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleard" OnSubmitClick="OnFilterSubmit">
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
<Filters>
<MudTextField T="string" Clearable="true" Label="عنوان" @bind-Value="@_request.Filter.Title" Variant="Variant.Outlined" Margin="Margin.Dense" />
</Filters>
@@ -93,4 +93,3 @@

View File

@@ -82,16 +82,16 @@ public partial class PackageMainPage
public async Task OnFilterSubmit()
{
_basePage.IsFilterd = true;
_basePage.IsFiltered = true;
StateHasChanged();
ReLoadData();
}
public async Task OnFilterCleard()
public async Task OnFilterCleared()
{
_basePage.IsFilterd = false;
_basePage.IsFiltered = false;
StateHasChanged();
_request = new() { Filter = new() { } };
ReLoadData();
}
}
}

View File

@@ -0,0 +1,87 @@
@using BackOffice.BFF.Products.Protobuf.Protos.Products
@using Microsoft.AspNetCore.Components.Forms
@using Tizzani.MudBlazor.HtmlEditor
@using BackOffice.Common.BaseComponents
<MudDialog>
<DialogContent>
<MudStack Spacing="3">
<MudStack Justify="Justify.Center" AlignItems="AlignItems.Center">
@if (!string.IsNullOrWhiteSpace(_mainImagePreview))
{
<Image Src="@(_mainImagePreview)" Width="220" Height="140" ObjectPosition="ObjectPosition.Center" ObjectFit="ObjectFit.Cover" />
}
else
{
<MudPaper Class="d-flex align-center justify-center" Style="width:220px;height:140px;">
<MudText Typo="Typo.caption">تصویری انتخاب نشده است</MudText>
</MudPaper>
}
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnMainImageSelected">
<ActivatorContent>
<MudButton HtmlTag="label"
Variant="Variant.Filled"
Color="Color.Primary"
ButtonType="ButtonType.Button"
StartIcon="@Icons.Material.Filled.Image"
Style="cursor:pointer;">
انتخاب تصویر اصلی
</MudButton>
</ActivatorContent>
<SelectedTemplate>
@if (context != null)
{
<MudText Class="mt-2" Typo="Typo.subtitle2">
<MudTooltip Text="@context.Name" Inline="true">
@context.Name
</MudTooltip>
</MudText>
}
else
{
<MudText Class="mt-2" Typo="Typo.subtitle2">فایلی انتخاب نشده</MudText>
}
</SelectedTemplate>
</MudFileUpload>
</MudStack>
<MudStack Row="true" AlignItems="AlignItems.Center">
<MudItem xs="6">
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" Margin="Margin.Dense" />
</MudItem>
<MudItem xs="6">
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Format="N0" Immediate="true" Disabled="_isLoading" Label="قیمت" Variant="Variant.Outlined" Margin="Margin.Dense" />
</MudItem>
</MudStack>
<MudNumericField T="int" HideSpinButtons="true" @bind-Value="Model.Discount" Disabled="_isLoading" Label="تخفیف (%)" Variant="Variant.Outlined" Margin="Margin.Dense" />
<MudTextField T="string" @bind-Value="Model.ShortInfomation" Disabled="_isLoading" Label="خلاصه" Variant="Variant.Outlined" Margin="Margin.Dense" />
<MudHtmlEditor @bind-Html="Model.FullInformation">
<MudHtmlToolbarOptions InsertImage="false" />
</MudHtmlEditor>
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2">تصویر بندانگشتی</MudText>
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnThumbnailSelected">
<ActivatorContent>
<MudButton HtmlTag="label"
Variant="Variant.Outlined"
Color="Color.Secondary"
ButtonType="ButtonType.Button"
StartIcon="@Icons.Material.Filled.Image"
Style="cursor:pointer;">
انتخاب تصویر بندانگشتی (اختیاری)
</MudButton>
</ActivatorContent>
</MudFileUpload>
</MudStack>
</MudStack>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel" Disabled="_isLoading">لغو</MudButton>
<MudButton Color="Color.Primary" OnClick="CallCreateMethod" Disabled="_isLoading">ثبت</MudButton>
</DialogActions>
</MudDialog>

View File

@@ -0,0 +1,91 @@
using BackOffice.BFF.Products.Protobuf.Protos.Products;
using Google.Protobuf;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using MudBlazor;
namespace BackOffice.Pages.Products.Components;
public partial class CreateDialog
{
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
[Inject] public ProductsContract.ProductsContractClient ProductsContract { get; set; } = default!;
[Parameter] public CreateNewProductsRequest Model { get; set; } = default!;
private IBrowserFile? _mainImageFile;
private IBrowserFile? _thumbnailImageFile;
private readonly long _maxAllowedSize = (1024 * 1024) * 5;
private bool _isLoading;
private string? _mainImagePreview;
private async Task OnMainImageSelected(IBrowserFile? file)
{
_mainImageFile = file;
if (file != null)
{
var buffer = new byte[file.Size];
await file.OpenReadStream(_maxAllowedSize).ReadAsync(buffer);
_mainImagePreview = $"data:{file.ContentType};base64," + Convert.ToBase64String(buffer);
StateHasChanged();
}
}
private async Task OnThumbnailSelected(IBrowserFile? file)
{
_thumbnailImageFile = file;
}
public async void CallCreateMethod()
{
_isLoading = true;
StateHasChanged();
if (string.IsNullOrWhiteSpace(Model.Title))
{
Snackbar.Add("لطفا عنوان را وارد کنید!", Severity.Warning);
_isLoading = false;
StateHasChanged();
return;
}
if (_mainImageFile != null)
{
var buffer = new byte[_mainImageFile.Size];
await _mainImageFile.OpenReadStream(_maxAllowedSize).ReadAsync(buffer);
Model.ImageFile = new ImageFileModel
{
File = ByteString.CopyFrom(buffer),
Mime = _mainImageFile.ContentType,
FileName = Path.GetFileNameWithoutExtension(_mainImageFile.Name)
};
}
if (_thumbnailImageFile != null)
{
var buffer = new byte[_thumbnailImageFile.Size];
await _thumbnailImageFile.OpenReadStream(_maxAllowedSize).ReadAsync(buffer);
Model.ThumbnailFile = new ImageFileModel
{
File = ByteString.CopyFrom(buffer),
Mime = _thumbnailImageFile.ContentType,
FileName = Path.GetFileNameWithoutExtension(_thumbnailImageFile.Name)
};
}
try
{
await ProductsContract.CreateNewProductsAsync(Model);
Submit();
}
catch
{
Snackbar.Add("خطای سرور", Severity.Error);
}
_isLoading = false;
StateHasChanged();
}
void Submit() => MudDialog.Close(DialogResult.Ok(true));
void Cancel() => MudDialog.Cancel();
}

View File

@@ -0,0 +1,83 @@
@using BackOffice.BFF.Products.Protobuf.Protos.Products
@using Microsoft.AspNetCore.Components.Forms
<MudDialog>
<DialogContent>
<MudStack Spacing="3">
<MudText Typo="Typo.h6">گالری تصاویر - @ProductTitle</MudText>
<MudGrid>
<MudItem xs="12" md="4">
<MudStack Spacing="2">
<MudTextField @bind-Value="_title"
Label="عنوان تصویر"
Variant="Variant.Outlined"
Margin="Margin.Dense" />
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnImageSelected">
<ActivatorContent>
<MudButton HtmlTag="label"
Variant="Variant.Filled"
Color="Color.Primary"
ButtonType="ButtonType.Button"
StartIcon="@Icons.Material.Filled.Image"
Style="cursor:pointer;">
انتخاب تصویر
</MudButton>
</ActivatorContent>
<SelectedTemplate>
@if (context != null)
{
<MudText Class="mt-1" Typo="Typo.caption">@context.Name</MudText>
}
else
{
<MudText Class="mt-1" Typo="Typo.caption">فایلی انتخاب نشده</MudText>
}
</SelectedTemplate>
</MudFileUpload>
@if (!string.IsNullOrWhiteSpace(_previewImage))
{
<MudPaper Class="pa-1">
<img src="@_previewImage" alt="پیش‌نمایش" style="width:100%; height:160px; object-fit:cover;" />
</MudPaper>
}
<MudButton Color="Color.Primary" OnClick="AddImage" Disabled="_isUploading">افزودن به گالری</MudButton>
</MudStack>
</MudItem>
<MudItem xs="12" md="8">
@if (Items?.Count > 0)
{
<MudGrid>
@foreach (var item in Items)
{
<MudItem xs="6" sm="4" md="3">
<MudPaper Class="pa-2">
<img src="@item.ImageThumbnailPath" alt="@item.Title" style="width:100%; height:120px; object-fit:cover;" />
<MudText Typo="Typo.caption" Class="mt-1">@item.Title</MudText>
<MudStack Row="true" Justify="Justify.FlexEnd">
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline"
Color="Color.Error"
Size="Size.Small"
OnClick="@(() => RemoveImage(item))" />
</MudStack>
</MudPaper>
</MudItem>
}
</MudGrid>
}
else
{
<MudText Typo="Typo.caption">هنوز تصویری برای این محصول ثبت نشده است.</MudText>
}
</MudItem>
</MudGrid>
</MudStack>
</DialogContent>
<DialogActions>
<MudButton OnClick="Close">بستن</MudButton>
</DialogActions>
</MudDialog>

View File

@@ -0,0 +1,129 @@
using BackOffice.BFF.Products.Protobuf.Protos.Products;
using Google.Protobuf;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using MudBlazor;
namespace BackOffice.Pages.Products.Components;
public partial class GalleryDialog
{
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
[Inject] public ProductsContract.ProductsContractClient ProductsContract { get; set; } = default!;
[Parameter] public long ProductId { get; set; }
[Parameter] public string ProductTitle { get; set; } = string.Empty;
private List<GalleryItemViewModel> Items { get; set; } = new List<GalleryItemViewModel>();
private IBrowserFile? _file;
private string? _title;
private readonly long _maxAllowedSize = (1024 * 1024) * 5;
private bool _isUploading;
private string? _previewImage;
private byte[]? _imageBuffer;
protected override async Task OnInitializedAsync()
{
if (string.IsNullOrWhiteSpace(_title))
_title = ProductTitle;
await LoadGallery();
}
private async Task LoadGallery()
{
var response = await ProductsContract.GetProductGalleryAsync(new GetProductGalleryRequest
{
ProductId = ProductId
});
Items = response?.Items?
.Select(x => new GalleryItemViewModel
{
ProductGalleryId = x.ProductGalleryId,
ProductImageId = x.ProductImageId,
Title = x.Title,
ImagePath = x.ImagePath,
ImageThumbnailPath = x.ImageThumbnailPath
})
.ToList() ?? new List<GalleryItemViewModel>();
StateHasChanged();
}
private async Task OnImageSelected(IBrowserFile? file)
{
_file = file;
_previewImage = null;
_imageBuffer = null;
if (file != null)
{
var buffer = new byte[file.Size];
await file.OpenReadStream(_maxAllowedSize).ReadAsync(buffer);
_imageBuffer = buffer;
_previewImage = $"data:{file.ContentType};base64," + Convert.ToBase64String(buffer);
StateHasChanged();
}
}
private async Task AddImage()
{
if (_file == null)
return;
_isUploading = true;
StateHasChanged();
if (_imageBuffer == null)
{
var buffer = new byte[_file.Size];
await _file.OpenReadStream(_maxAllowedSize).ReadAsync(buffer);
_imageBuffer = buffer;
}
var request = new AddProductImageRequest
{
ProductId = ProductId,
Title = _title ?? string.Empty,
ImageFile = new ImageFileModel
{
File = ByteString.CopyFrom(_imageBuffer),
Mime = _file.ContentType,
FileName = Path.GetFileNameWithoutExtension(_file.Name)
}
};
await ProductsContract.AddProductImageAsync(request);
_file = null;
_title = string.Empty;
_previewImage = null;
_imageBuffer = null;
await LoadGallery();
_isUploading = false;
StateHasChanged();
}
private async Task RemoveImage(GalleryItemViewModel item)
{
await ProductsContract.RemoveProductImageAsync(new RemoveProductImageRequest
{
ProductGalleryId = item.ProductGalleryId
});
await LoadGallery();
}
private void Close() => MudDialog.Close();
private class GalleryItemViewModel
{
public long ProductGalleryId { get; set; }
public long ProductImageId { get; set; }
public string Title { get; set; }
public string ImagePath { get; set; }
public string ImageThumbnailPath { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
@using BackOffice.Common.BaseComponents
<MudDialog>
<DialogContent>
<MudStack Spacing="2" AlignItems="AlignItems.Center">
<MudText Typo="Typo.h6">@Title</MudText>
@if (!string.IsNullOrWhiteSpace(ImageUrl))
{
<Image Src="@ImageUrl"
Width="400"
Height="260"
ObjectPosition="ObjectPosition.Center"
ObjectFit="ObjectFit.Cover" />
}
else
{
<MudText Typo="Typo.caption">تصویری برای نمایش وجود ندارد.</MudText>
}
</MudStack>
</DialogContent>
<DialogActions>
<MudButton OnClick="Close">بستن</MudButton>
</DialogActions>
</MudDialog>

View File

@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace BackOffice.Pages.Products.Components;
public partial class ImagePreviewDialog
{
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter] public string ImageUrl { get; set; } = string.Empty;
[Parameter] public string Title { get; set; } = string.Empty;
private void Close() => MudDialog.Close();
}

View File

@@ -0,0 +1,73 @@
@using BackOffice.BFF.Products.Protobuf.Protos.Products
@using Microsoft.AspNetCore.Components.Forms
@using Tizzani.MudBlazor.HtmlEditor
@using BackOffice.Common.BaseComponents
<MudDialog>
<DialogContent>
<MudStack Spacing="3">
<MudStack Justify="Justify.Center" AlignItems="AlignItems.Center">
@if (!string.IsNullOrWhiteSpace(_mainImagePreview))
{
<Image Src="@(_mainImagePreview)" Width="220" Height="140" ObjectPosition="ObjectPosition.Center" ObjectFit="ObjectFit.Cover" />
}
else
{
<MudPaper Class="d-flex align-center justify-center" Style="width:220px;height:140px;">
<MudText Typo="Typo.caption">تصویری انتخاب نشده است</MudText>
</MudPaper>
}
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnMainImageSelected">
<ActivatorContent>
<MudButton HtmlTag="label"
Variant="Variant.Filled"
Color="Color.Primary"
ButtonType="ButtonType.Button"
StartIcon="@Icons.Material.Filled.Image"
Style="cursor:pointer;">
تغییر تصویر اصلی
</MudButton>
</ActivatorContent>
</MudFileUpload>
</MudStack>
<MudStack Row="true" AlignItems="AlignItems.Center">
<MudItem xs="6">
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" Margin="Margin.Dense" />
</MudItem>
<MudItem xs="6">
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Format="N0" Immediate="true" Disabled="_isLoading" Label="قیمت" Variant="Variant.Outlined" Margin="Margin.Dense" />
</MudItem>
</MudStack>
<MudNumericField T="int" HideSpinButtons="true" @bind-Value="Model.Discount" Disabled="_isLoading" Label="تخفیف (%)" Variant="Variant.Outlined" Margin="Margin.Dense" />
<MudTextField T="string" @bind-Value="Model.ShortInfomation" Disabled="_isLoading" Label="خلاصه" Variant="Variant.Outlined" Margin="Margin.Dense" />
<MudHtmlEditor @bind-Html="Model.FullInformation">
<MudHtmlToolbarOptions InsertImage="false" />
</MudHtmlEditor>
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2">تصویر بندانگشتی</MudText>
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnThumbnailSelected">
<ActivatorContent>
<MudButton HtmlTag="label"
Variant="Variant.Outlined"
Color="Color.Secondary"
ButtonType="ButtonType.Button"
StartIcon="@Icons.Material.Filled.Image"
Style="cursor:pointer;">
تغییر تصویر بندانگشتی (اختیاری)
</MudButton>
</ActivatorContent>
</MudFileUpload>
</MudStack>
</MudStack>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel" Disabled="_isLoading">لغو</MudButton>
<MudButton Color="Color.Primary" OnClick="@CallUpdateMethod" Disabled="_isLoading">ویرایش</MudButton>
</DialogActions>
</MudDialog>

View File

@@ -0,0 +1,97 @@
using BackOffice.BFF.Products.Protobuf.Protos.Products;
using Google.Protobuf;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using MudBlazor;
namespace BackOffice.Pages.Products.Components;
public partial class UpdateDialog
{
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
[Inject] public ProductsContract.ProductsContractClient ProductsContract { get; set; } = default!;
[Parameter] public UpdateProductsRequest Model { get; set; } = default!;
private IBrowserFile? _mainImageFile;
private IBrowserFile? _thumbnailImageFile;
private readonly long _maxAllowedSize = (1024 * 1024) * 5;
private bool _isLoading;
private string? _mainImagePreview;
private async Task OnMainImageSelected(IBrowserFile? file)
{
_mainImageFile = file;
if (file != null)
{
var buffer = new byte[file.Size];
await file.OpenReadStream(_maxAllowedSize).ReadAsync(buffer);
_mainImagePreview = $"data:{file.ContentType};base64," + Convert.ToBase64String(buffer);
StateHasChanged();
}
}
private async Task OnThumbnailSelected(IBrowserFile? file)
{
_thumbnailImageFile = file;
}
protected override Task OnInitializedAsync()
{
_mainImagePreview = Model.ImagePath;
return base.OnInitializedAsync();
}
public async void CallUpdateMethod()
{
_isLoading = true;
StateHasChanged();
if (string.IsNullOrWhiteSpace(Model.Title))
{
Snackbar.Add("لطفا عنوان را وارد کنید!", Severity.Warning);
_isLoading = false;
StateHasChanged();
return;
}
if (_mainImageFile != null)
{
var buffer = new byte[_mainImageFile.Size];
await _mainImageFile.OpenReadStream(_maxAllowedSize).ReadAsync(buffer);
Model.ImageFile = new ImageFileModel
{
File = ByteString.CopyFrom(buffer),
Mime = _mainImageFile.ContentType,
FileName = Path.GetFileNameWithoutExtension(_mainImageFile.Name)
};
}
if (_thumbnailImageFile != null)
{
var buffer = new byte[_thumbnailImageFile.Size];
await _thumbnailImageFile.OpenReadStream(_maxAllowedSize).ReadAsync(buffer);
Model.ThumbnailFile = new ImageFileModel
{
File = ByteString.CopyFrom(buffer),
Mime = _thumbnailImageFile.ContentType,
FileName = Path.GetFileNameWithoutExtension(_thumbnailImageFile.Name)
};
}
try
{
await ProductsContract.UpdateProductsAsync(Model);
Submit();
}
catch
{
Snackbar.Add("خطای سرور", Severity.Error);
}
_isLoading = false;
StateHasChanged();
}
void Submit() => MudDialog.Close(DialogResult.Ok(true));
void Cancel() => MudDialog.Cancel();
}

View File

@@ -0,0 +1,85 @@
@attribute [Route(RouteConstance.Products)]
@using BackOffice.BFF.Products.Protobuf.Protos.Products
@using BackOffice.Common.BaseComponents
@using BackOffice.Pages.Products.Components
@using DataModel = BackOffice.BFF.Products.Protobuf.Protos.Products.GetAllProductsByFilterResponseModel
<BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
<Filters>
<MudTextField T="string" Clearable="true" Label="عنوان" @bind-Value="@_request.Filter.Title" Variant="Variant.Outlined" Margin="Margin.Dense" />
</Filters>
<Content>
<MudDataGrid T="DataModel" ServerData="@(new Func<GridState<DataModel>, Task<GridData<DataModel>>>(ServerReload))"
Hover="true" @ref="_gridData" Height="72vh">
<ColGroup>
<col />
<col />
<col />
<col style="width: 58px;" />
</ColGroup>
<ToolBarContent>
<MudText>مدیریت محصولات</MudText>
<MudSpacer />
<MudStack Spacing="2" Row="true" Justify="Justify.Center" AlignItems="AlignItems.Center">
<MudButton Variant="Variant.Filled" Color="Color.Primary" Size="Size.Large" ButtonType="ButtonType.Button" OnClick="CreateNew" Style="cursor:pointer;">افزودن</MudButton>
</MudStack>
</ToolBarContent>
<Columns>
<PropertyColumn Property="x => x.Id" Title="شناسه" />
<PropertyColumn Property="x => x.Title" Title="عنوان" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
<CellTemplate>
<MudStack Row="true" AlignItems="AlignItems.Center">
<MudTooltip Text="پیش‌نمایش تصویر">
<div style="cursor:pointer"
@onclick="@(() => OpenImagePreview(context.Item.ImagePath, context.Item.Title))">
<Image Src="@context.Item.ImagePath"
Width="25"
Height="25"
ObjectPosition="ObjectPosition.Center"
ObjectFit="ObjectFit.Fill" />
</div>
</MudTooltip>
@if (string.IsNullOrWhiteSpace(context.Item.Title))
{
<MudText Typo="Typo.inherit">-</MudText>
}
else
{
<MudTooltip Text="@(context.Item.Title)" Arrow="true">
<MudText Typo="Typo.inherit" Style="text-wrap: nowrap;">
@(context.Item.Title.Truncate(20, true))
</MudText>
</MudTooltip>
}
</MudStack>
</CellTemplate>
</PropertyColumn>
<PropertyColumn Property="x => x.Price" Title="قیمت" />
<TemplateColumn StickyLeft="true" Title="عملیات" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
<CellTemplate>
<MudStack Row="true" AlignItems="AlignItems.Center">
<MudTooltip Text="ویرایش">
<MudIconButton Icon="@Icons.Material.Filled.EditNote" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => Update(context.Item))" Style="cursor:pointer;" />
</MudTooltip>
<MudTooltip Text="حذف">
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => OnDelete(context.Item))" Style="cursor:pointer;" />
</MudTooltip>
<MudTooltip Text="گالری تصاویر">
<MudIconButton Icon="@Icons.Material.Filled.Collections" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => OpenGallery(context.Item))" Style="cursor:pointer;" />
</MudTooltip>
</MudStack>
</CellTemplate>
</TemplateColumn>
</Columns>
<PagerContent>
<MudDataGridPager T="DataModel" PageSizeOptions=@(new int[] { 30, 60, 90 }) />
</PagerContent>
</MudDataGrid>
</Content>
</BasePageComponent>

View File

@@ -0,0 +1,123 @@
using BackOffice.BFF.Products.Protobuf.Protos.Products;
using BackOffice.Common.BaseComponents;
using BackOffice.Pages.Products.Components;
using Mapster;
using Microsoft.AspNetCore.Components;
using MudBlazor;
using DataModel = BackOffice.BFF.Products.Protobuf.Protos.Products.GetAllProductsByFilterResponseModel;
namespace BackOffice.Pages.Products;
public partial class ProductsMainPage
{
[Inject] public ProductsContract.ProductsContractClient ProductsContract { get; set; } = default!;
private bool _isLoading = true;
private MudDataGrid<DataModel> _gridData;
private BasePageComponent _basePage;
private GetAllProductsByFilterRequest _request = new() { Filter = new() };
private async Task<GridData<DataModel>> ServerReload(GridState<DataModel> state)
{
_request.Filter ??= new();
_request.PaginationState ??= new();
_request.PaginationState.PageNumber = state.Page + 1;
_request.PaginationState.PageSize = state.PageSize;
var result = await ProductsContract.GetAllProductsByFilterAsync(_request);
if (result != null && result.Models != null && result.Models.Any())
{
return new GridData<DataModel> { Items = result.Models.ToList(), TotalItems = (int)result.MetaData.TotalCount };
}
return new GridData<DataModel>();
}
public async Task Update(DataModel model)
{
var parameters = new DialogParameters<UpdateDialog> { { x => x.Model, model.Adapt<UpdateProductsRequest>() } };
var dialog = await DialogService.ShowAsync<UpdateDialog>("ویرایش محصول", parameters, new DialogOptions { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Small });
var result = await dialog.Result;
if (!result.Canceled)
{
ReLoadData();
Snackbar.Add("عملیات با موفقیت انجام شد", Severity.Success);
}
}
private async Task OnDelete(DataModel model)
{
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small };
bool? result = await DialogService.ShowMessageBox(
"اخطار",
"آیا از حذف این مورد مطمئن هستید؟",
yesText: "حذف", cancelText: "لغو",
options: options);
if (result == true)
{
await ProductsContract.DeleteProductsAsync(new DeleteProductsRequest { Id = model.Id });
ReLoadData();
}
StateHasChanged();
}
public async void ReLoadData()
{
if (_gridData != null)
await _gridData.ReloadServerData();
}
public async Task CreateNew()
{
var dialog = await DialogService.ShowAsync<CreateDialog>("افزودن محصول", new DialogParameters<CreateDialog> { { x => x.Model, new CreateNewProductsRequest() } }, new DialogOptions { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Small });
var result = await dialog.Result;
if (!result.Canceled)
{
ReLoadData();
Snackbar.Add("عملیات با موفقیت انجام شد", Severity.Success);
}
}
public async Task OnFilterSubmit()
{
_basePage.IsFiltered = true;
StateHasChanged();
ReLoadData();
}
public async Task OnFilterCleared()
{
_basePage.IsFiltered = false;
StateHasChanged();
_request = new GetAllProductsByFilterRequest { Filter = new() };
ReLoadData();
}
public async Task OpenGallery(DataModel model)
{
var parameters = new DialogParameters<GalleryDialog>
{
{ x => x.ProductId, model.Id },
{ x => x.ProductTitle, model.Title }
};
await DialogService.ShowAsync<GalleryDialog>("گالری تصاویر", parameters,
new DialogOptions { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Medium });
}
public async Task OpenImagePreview(string imagePath, string title)
{
if (string.IsNullOrWhiteSpace(imagePath))
return;
var parameters = new DialogParameters<ImagePreviewDialog>
{
{ x => x.ImageUrl, imagePath },
{ x => x.Title, title }
};
await DialogService.ShowAsync<ImagePreviewDialog>("پیش‌نمایش تصویر محصول", parameters,
new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true });
}
}

File diff suppressed because one or more lines are too long

View File

@@ -15,7 +15,7 @@ namespace BackOffice.Pages.Role.Components
public UpdateRoleRequest Model { get; set; } = new();
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
IMudDialogInstance MudDialog { get; set; }
public async void CallUpdateMethod()
{

View File

@@ -5,7 +5,7 @@
@using BackOffice.Common.BaseComponents
@using DataModel = BackOffice.BFF.Role.Protobuf.Protos.Role.GetAllRoleByFilterResponseModel
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleard" OnSubmitClick="OnFilterSubmit">
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
<Filters>
<MudTextField T="string" Clearable="true" Label="عنوان" @bind-Value="@_request.Filter.Title" Variant="Variant.Outlined" Margin="Margin.Dense" />
</Filters>
@@ -57,4 +57,3 @@

View File

@@ -81,16 +81,16 @@ public partial class RoleMainPage
}
public async Task OnFilterSubmit()
{
_basePage.IsFilterd = true;
_basePage.IsFiltered = true;
StateHasChanged();
ReLoadData();
}
public async Task OnFilterCleard()
public async Task OnFilterCleared()
{
_basePage.IsFilterd = false;
_basePage.IsFiltered = false;
StateHasChanged();
_request = new() { Filter = new() { } };
ReLoadData();
}
}
}

View File

@@ -8,7 +8,7 @@ namespace BackOffice.Pages.User.Components;
public partial class UserRoleDialog
{
[Parameter] public long UserId { get; set; }
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
MudForm _form;
private bool _isLoading;
private bool _isFirstLoad = true;

View File

@@ -5,7 +5,7 @@
@using BackOffice.Common.BaseComponents
@using DataModel = BackOffice.BFF.User.Protobuf.Protos.User.GetAllUserByFilterResponseModel
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleard" OnSubmitClick="OnFilterSubmit">
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
<Filters>
<MudTextField T="string" Clearable="true" Label="نام" @bind-Value="@_request.Filter.FirstName" Variant="Variant.Outlined" Margin="Margin.Dense" />
<MudTextField T="string" Clearable="true" Label="نام خانوادگی" @bind-Value="@_request.Filter.LastName" Variant="Variant.Outlined" Margin="Margin.Dense" />
@@ -64,4 +64,3 @@

View File

@@ -51,30 +51,6 @@ public partial class UserMainPage
if (_gridData != null)
await _gridData.ReloadServerData();
}
private async Task Searchfirstname(string? firstname)
{
_request.Filter ??= new();
_request.Filter.FirstName = string.IsNullOrWhiteSpace(firstname) ? default : firstname;
ReLoadData();
}
private async Task Searchlastname(string? lastname)
{
_request.Filter ??= new();
_request.Filter.LastName = string.IsNullOrWhiteSpace(lastname) ? default : lastname;
ReLoadData();
}
private async Task SearchNationalCode(string? nationalcode)
{
_request.Filter ??= new();
_request.Filter.NationalCode = string.IsNullOrWhiteSpace(nationalcode) ? default : nationalcode;
ReLoadData();
}
private async Task SearchMobile(string? mobile)
{
_request.Filter ??= new();
_request.Filter.Mobile = string.IsNullOrWhiteSpace(mobile) ? default : mobile;
ReLoadData();
}
public async Task OnclickUserRoleDialog(long userid)
{
var dialog = await DialogService.ShowAsync<UserRoleDialog>($"نقش های کاربر", new DialogParameters<UserRoleDialog>() { { x => x.UserId, userid } }, new DialogOptions() { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Small });
@@ -87,16 +63,16 @@ public partial class UserMainPage
}
public async Task OnFilterSubmit()
{
_basePage.IsFilterd = true;
_basePage.IsFiltered = true;
StateHasChanged();
ReLoadData();
}
public async Task OnFilterCleard()
public async Task OnFilterCleared()
{
_basePage.IsFilterd = false;
_basePage.IsFiltered = false;
StateHasChanged();
_request = new() { Filter = new() { } };
ReLoadData();
}
}
}

View File

@@ -11,7 +11,7 @@ public partial class CreateDialog
{
[Inject] public UserAddressContract.UserAddressContractClient UserAddressContract { get; set; }
[Parameter] public CreateNewUserAddressRequest Model { get; set; } = new();
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
private bool _isLoading = false;
@@ -49,9 +49,8 @@ public partial class CreateDialog
Submit();
}
catch (RpcException ex)
catch (RpcException)
{
Console.WriteLine(ex);
Snackbar.Add("خطای سرور", Severity.Error);
}
@@ -60,4 +59,4 @@ public partial class CreateDialog
}
void Submit() => MudDialog.Close(DialogResult.Ok(true));
void Cancel() => MudDialog.Cancel();
}
}

View File

@@ -15,7 +15,7 @@ namespace BackOffice.Pages.UserAddress.Components
public UpdateUserAddressRequest Model { get; set; } = new();
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
IMudDialogInstance MudDialog { get; set; }
public async void CallUpdateMethod()
{
_isLoading = true;

View File

@@ -5,7 +5,7 @@
@using BackOffice.Common.BaseComponents
@using DataModel = BackOffice.BFF.UserAddress.Protobuf.Protos.UserAddress.GetAllUserAddressByFilterResponseModel
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleard" OnSubmitClick="OnFilterSubmit">
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
<Filters>
<MudTextField T="string" Clearable="true" Label="عنوان" @bind-Value="@_request.Filter.Title" Variant="Variant.Outlined" Margin="Margin.Dense" />
</Filters>
@@ -54,4 +54,3 @@
</BackOffice.Common.BaseComponents.BasePageComponent>

View File

@@ -83,16 +83,16 @@ public partial class UserAddressMainPage
}
public async Task OnFilterSubmit()
{
_basePage.IsFilterd = true;
_basePage.IsFiltered = true;
StateHasChanged();
ReLoadData();
}
public async Task OnFilterCleard()
public async Task OnFilterCleared()
{
_basePage.IsFilterd = false;
_basePage.IsFiltered = false;
StateHasChanged();
_request = new() { Filter = new() { } };
ReLoadData();
}
}
}

View File

@@ -5,7 +5,7 @@
@using BackOffice.Common.BaseComponents
@using DataModel = BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder.GetAllUserOrderByFilterResponseModel
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleard" OnSubmitClick="OnFilterSubmit">
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
<Filters>
<MudNumericField HideSpinButtons="true" T="long?" Clearable="true" Label="قیمت" @bind-Value="@_request.Filter.Price" Variant="Variant.Outlined" Margin="Margin.Dense" />
</Filters>
@@ -65,4 +65,3 @@

View File

@@ -56,16 +56,16 @@ public partial class UserOrderMainPage
}
public async Task OnFilterSubmit()
{
_basePage.IsFilterd = true;
_basePage.IsFiltered = true;
StateHasChanged();
ReLoadData();
}
public async Task OnFilterCleard()
public async Task OnFilterCleared()
{
_basePage.IsFilterd = false;
_basePage.IsFiltered = false;
StateHasChanged();
_request = new() { Filter = new() { } };
ReLoadData();
}
}
}

View File

@@ -11,7 +11,7 @@ public partial class CreateDialog
{
[Inject] public UserRoleContract.UserRoleContractClient UserRoleContract { get; set; }
[Parameter] public CreateNewUserRoleRequest Model { get; set; } = new();
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
private bool _isLoading = false;

View File

@@ -15,7 +15,7 @@ namespace BackOffice.Pages.UserRole.Components
public UpdateUserRoleRequest Model { get; set; } = new();
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
IMudDialogInstance MudDialog { get; set; }
public async void CallUpdateMethod()
{

View File

@@ -5,7 +5,7 @@
@using BackOffice.Common.BaseComponents
@using DataModel = BackOffice.BFF.UserRole.Protobuf.Protos.UserRole.GetAllUserRoleByFilterResponseModel
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleard" OnSubmitClick="OnFilterSubmit">
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
<Filters>
<MudNumericField T="long?" HideSpinButtons="true" Clearable="true" Label="شناسه" @bind-Value="@_request.Filter.Id" Variant="Variant.Outlined" Margin="Margin.Dense" />
</Filters>
@@ -53,4 +53,3 @@

View File

@@ -83,16 +83,16 @@ public partial class UserRoleMainPage
}
public async Task OnFilterSubmit()
{
_basePage.IsFilterd = true;
_basePage.IsFiltered = true;
StateHasChanged();
ReLoadData();
}
public async Task OnFilterCleard()
public async Task OnFilterCleared()
{
_basePage.IsFilterd = false;
_basePage.IsFiltered = false;
StateHasChanged();
_request = new() { Filter = new() { } };
ReLoadData();
}
}
}

View File

@@ -0,0 +1,104 @@
using MudBlazor;
namespace BackOffice.Shared;
public static class CustomMudTheme
{
public static MudTheme CustomMudBlazorTheme { get; set; } = new()
{
PaletteLight = new PaletteLight()
{
Primary = "#0380C0",
Secondary = "#5E4DF9",
PrimaryContrastText = "#FFFFFF",
AppbarText = Colors.Gray.Darken4,
Background = "#F5F5F5",
AppbarBackground = "#F5F5F5",
TextPrimary = Colors.Gray.Darken3,
Surface = "#FFFFFF",
Divider = "#B2BFCB",
DrawerBackground = "#FFFFFF",
DrawerText = Colors.Gray.Darken3,
DrawerIcon = Colors.Gray.Darken2,
},
LayoutProperties = new LayoutProperties
{
DefaultBorderRadius = "12px",
AppbarHeight = "56px",
DrawerWidthLeft = "260px",
DrawerWidthRight = "260px"
},
Typography = new Typography
{
Default = new DefaultTypography()
{
FontFamily = new[] { "Vazir", "Tahoma", "Segoe UI", "Arial", "sans-serif" }
},
H1 = new H1Typography()
{
FontFamily = new[] { "Vazir" }, FontSize = "2rem", LineHeight = "1.70", FontWeight = "800",
LetterSpacing = "normal"
},
H2 = new H2Typography()
{
FontFamily = new[] { "Vazir" }, FontSize = "1.875rem", LineHeight = "1.65", FontWeight = "800",
LetterSpacing = "normal"
},
H3 = new H3Typography()
{
FontFamily = new[] { "Vazir" }, FontSize = "1.5rem", LineHeight = "1.60", FontWeight = "800",
LetterSpacing = "normal"
},
H4 = new H4Typography()
{
FontFamily = new[] { "Vazir" }, FontSize = "1.25rem", LineHeight = "1.55", FontWeight = "800",
LetterSpacing = "normal"
},
H5 = new H5Typography()
{
FontFamily = new[] { "Vazir" }, FontSize = "1.125rem", LineHeight = "1.50", FontWeight = "800",
LetterSpacing = "normal"
},
H6 = new H6Typography()
{
FontFamily = new[] { "Vazir" }, FontSize = "1rem", LineHeight = "1.45", FontWeight = "800",
LetterSpacing = "normal"
},
Subtitle1 = new Subtitle1Typography()
{
FontFamily = new[] { "Vazir" }, FontSize = "1rem", LineHeight = "1.62", FontWeight = "500",
LetterSpacing = "normal"
},
Subtitle2 = new Subtitle2Typography()
{
FontFamily = new[] { "Vazir" }, FontSize = "0.875rem", LineHeight = "1.60", FontWeight = "500",
LetterSpacing = "normal"
},
Body1 = new Body1Typography()
{
FontFamily = new[] { "Vazir" }, FontSize = "1rem", LineHeight = "1.85", FontWeight = "400",
LetterSpacing = "normal"
},
Body2 = new Body2Typography()
{
FontFamily = new[] { "Vazir" }, FontSize = "0.875rem", LineHeight = "1.80", FontWeight = "400",
LetterSpacing = "normal"
},
Caption = new CaptionTypography()
{
FontFamily = new[] { "Vazir" }, FontSize = "0.75rem", LineHeight = "1.60", FontWeight = "400",
LetterSpacing = "normal"
},
Overline = new OverlineTypography()
{
FontFamily = new[] { "Vazir" }, FontSize = "0.75rem", LineHeight = "1.60", FontWeight = "500",
LetterSpacing = "normal"
},
Button = new ButtonTypography()
{
FontFamily = new[] { "Vazir" }, FontSize = "0.875rem", LineHeight = "1.60", FontWeight = "600",
LetterSpacing = "normal", TextTransform = "none"
}
}
};
}

View File

@@ -1,7 +1,7 @@
@inherits LayoutComponentBase
<MudRTLProvider RightToLeft="true">
<MudThemeProvider Theme="CustomTheme" />
<MudThemeProvider Theme="CustomMudTheme.CustomMudBlazorTheme" />
<MudDialogProvider />
<MudSnackbarProvider />
@@ -13,24 +13,3 @@
</MudMainContent>
</MudLayout>
</MudRTLProvider>
@code {
MudTheme CustomTheme = new MudTheme()
{
Typography = new Typography()
{
Default = new Default()
{
FontFamily = new[] { "IRANSans" }
}
},
LayoutProperties = new()
{
DrawerWidthRight = "250px"
},
};
}

View File

@@ -4,14 +4,14 @@
@attribute [AllowAnonymous]
<MudRTLProvider RightToLeft="true">
<MudThemeProvider Theme="CustomTheme" />
<MudThemeProvider Theme="CustomMudTheme.CustomMudBlazorTheme" />
<MudDialogProvider />
<MudSnackbarProvider />
<MudPopoverProvider />
<MudLayout>
<MudAppBar>
<MudAppBar Elevation="1" Color="Color.Surface">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" />
مدیریت
<MudText Class="mx-2" Typo="Typo.h6">پنل مدیریت فرصت</MudText>
<MudSpacer />
<AuthorizeView>
<Authorized>
@@ -33,4 +33,3 @@
</MudLayout>
</MudRTLProvider>

View File

@@ -1,27 +1,24 @@
using BackOffice.Common.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using MudBlazor;
namespace BackOffice.Shared;
public partial class MainLayout
{
bool _drawerOpen = false;
private string Details { get; set; }
private bool _drawerOpen;
protected override void OnInitialized()
{
base.OnInitialized();
GlobalConstants.ConstSnackbar = Snackbar;
}
void DrawerToggle()
{
_drawerOpen = !_drawerOpen;
}
MudTheme CustomTheme = new MudTheme()
{
Typography = new Typography()
{
Default = new Default()
{
FontFamily = new[] { "IRANSans" }
}
}
};
}
}

View File

@@ -1,19 +1,57 @@
@using BackOffice.BFF.Package.Protobuf.Protos.Package
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Authorization
<MudNavMenu Bordered="false" Class="nav-menu">
<MudText Class="nav-menu__title" Typo="Typo.subtitle2">منوی اصلی</MudText>
<MudDivider Class="mb-2" />
<MudNavLink Match="NavLinkMatch.Prefix"
Href="/"
Icon="@Icons.Material.Filled.Dashboard">
داشبورد
</MudNavLink>
<MudNavMenu Bordered="true">
<MudNavLink Match="NavLinkMatch.Prefix" Href="/">داشبورد</MudNavLink>
<AuthorizeView Roles="Administrator">
<Authorized>
<MudNavLink Match="NavLinkMatch.Prefix" Href="@(RouteConstance.Package)">مدیریت پکیج</MudNavLink>
<MudNavLink Match="NavLinkMatch.Prefix" Href="@(RouteConstance.UserPage)">مدیریت کاربر</MudNavLink>
<MudNavLink Match="NavLinkMatch.Prefix" Href="@(RouteConstance.Role)">مدیریت نقش</MudNavLink>
<MudNavLink Match="NavLinkMatch.Prefix"
Href="@(RouteConstance.Package)"
Icon="@Icons.Material.Filled.Inventory2">
مدیریت پکیج
</MudNavLink>
<MudNavLink Match="NavLinkMatch.Prefix"
Href="@(RouteConstance.Products)"
Icon="@Icons.Material.Filled.ShoppingBag">
مدیریت محصول
</MudNavLink>
<MudNavLink Match="NavLinkMatch.Prefix"
Href="@(RouteConstance.Category)"
Icon="@Icons.Material.Filled.Category">
مدیریت دسته‌بندی
</MudNavLink>
<MudNavLink Match="NavLinkMatch.Prefix"
Href="@(RouteConstance.UserPage)"
Icon="@Icons.Material.Filled.People">
مدیریت کاربر
</MudNavLink>
<MudNavLink Match="NavLinkMatch.Prefix"
Href="@(RouteConstance.Role)"
Icon="@Icons.Material.Filled.AdminPanelSettings">
مدیریت نقش
</MudNavLink>
</Authorized>
</AuthorizeView>
<MudNavLink Match="NavLinkMatch.Prefix" OnClick="Signout">خروج از حساب</MudNavLink>
<MudDivider Class="my-2" />
<MudNavLink Match="NavLinkMatch.Prefix"
Color="Color.Error"
Icon="@Icons.Material.Filled.Logout"
OnClick="Signout">
خروج از حساب
</MudNavLink>
</MudNavMenu>
@@ -24,4 +62,4 @@
await LocalStorageService.RemoveItemAsync("AuthToken");
Navigation.NavigateTo("/Login");
}
}
}

View File

@@ -0,0 +1,33 @@
.nav-menu {
padding: 1.5rem 0.5rem 1.5rem 0.5rem;
}
.nav-menu__title {
padding: 0 0.75rem 0.25rem 0.75rem;
color: rgba(0, 0, 0, 0.6);
}
.nav-menu .mud-divider {
opacity: 0.7;
}
.nav-menu .mud-nav-link {
margin: 0.15rem 0.4rem;
border-radius: 999px;
padding-inline: 0.75rem 1rem;
font-weight: 500;
}
.nav-menu .mud-nav-link .mud-nav-link-text {
margin-right: 0.5rem;
}
.nav-menu .mud-nav-link.mud-nav-link-active {
background-color: rgba(3, 128, 192, 0.12);
color: #0380C0;
font-weight: 600;
}
.nav-menu .mud-nav-link.mud-nav-link-active .mud-nav-link-icon {
color: #0380C0;
}

View File

@@ -11,6 +11,7 @@
@using MudBlazor
@using Mapster
@using DateTimeConverterCL
@using Microsoft.AspNetCore.Components.Authorization
@attribute [Authorize(Roles = "Administrator, Admin, Author")]
@@ -20,3 +21,4 @@
@inject IJSRuntime jsRuntime
@inject NavigationManager Navigation
@inject ILocalStorageService LocalStorageService
@inject AuthenticationStateProvider AuthenticationStateProvider

View File

@@ -9,6 +9,17 @@
url('../fonts/ttf/IRANSansWeb(FaNum)_Medium.ttf') format('truetype');
}
html, body {
margin: 0;
padding: 0;
height: 100%;
background-color: #f5f5f5;
}
body {
font-family: "Vazir", "IRANSans", Tahoma, "Segoe UI", Arial, sans-serif;
}
.loading-progress {
position: relative;
display: block;
@@ -79,4 +90,4 @@ h1:focus {
.blazor-error-boundary::after {
content: "An error has occurred."
}
}