feat: Implement role-based access control and manual payment management

- Added role-based permission checks in NavMenu for dashboard and management links.
- Created ManualPaymentDialog for creating and managing manual payments.
- Implemented TransactionDetailsDialog for displaying transaction details.
- Developed Transactions page for viewing and filtering transactions.
- Introduced RolePermissionsDialog for managing role permissions.
- Established AuthorizationService to handle permission checks and user roles.
- Enhanced UI components with MudBlazor for better user experience.
This commit is contained in:
masoodafar-web
2025-12-05 17:27:23 +03:30
parent b89f801742
commit 5cec4e9313
24 changed files with 1329 additions and 114 deletions

View File

@@ -59,6 +59,7 @@ public static class ConfigureServices
services.AddGrpcServices(configuration);
// Application Services
services.AddScoped<BackOffice.Services.Authorization.IAuthorizationService, BackOffice.Services.Authorization.AuthorizationService>();
services.AddScoped<IDiscountProductService, DiscountProductService>();
services.AddScoped<IDiscountCategoryService, DiscountCategoryService>();
services.AddScoped<IDiscountOrderService, DiscountOrderService>();

View File

@@ -17,7 +17,7 @@
<MudText Typo="Typo.h6">لیست اعضا</MudText>
<MudSpacer />
<MudStack Row="true" Spacing="2">
<MudSwitch @bind-Value="_filterIsActive"
<MudSwitch T="bool" @bind-Value="_filterIsActive"
Color="Color.Success"
Label="فقط فعال‌ها" />
<MudButton Variant="Variant.Filled"

View File

@@ -0,0 +1,97 @@
@using BackOffice.BFF.ManualPayment.Protobuf
@using BackOffice.Pages.Payment.Components
<MudDialog>
<DialogContent>
@if (Mode == ManualPaymentDialogMode.Create)
{
<MudStack Spacing="2">
<MudText Typo="Typo.h6">ثبت پرداخت دستی جدید</MudText>
<MudNumericField T="long"
Label="شناسه کاربر"
Variant="Variant.Outlined"
@bind-Value="_createModel.UserId" />
<MudNumericField T="long"
Label="مبلغ"
Variant="Variant.Outlined"
Adornment="Adornment.End"
AdornmentText="ریال"
@bind-Value="_createModel.Amount" />
<MudTextField T="string"
Label="نوع / توضیح نوع"
Variant="Variant.Outlined"
@bind-Value="_createModel.TypeDisplay" />
<MudTextField T="string"
Label="توضیحات"
Variant="Variant.Outlined"
Lines="3"
@bind-Value="_createModel.Description" />
<MudTextField T="string"
Label="شماره مرجع (اختیاری)"
Variant="Variant.Outlined"
@bind-Value="_createModel.ReferenceNumber" />
</MudStack>
}
else if (Model is not null)
{
<MudStack Spacing="2">
<MudText Typo="Typo.h6">جزئیات پرداخت دستی</MudText>
<MudText Typo="Typo.body2">شناسه: @Model.Id</MudText>
<MudText Typo="Typo.body2">کاربر: @Model.UserFullName (@Model.UserId)</MudText>
<MudText Typo="Typo.body2">مبلغ: @Model.Amount.ToString("N0") ریال</MudText>
<MudText Typo="Typo.body2">نوع: @Model.TypeDisplay</MudText>
<MudText Typo="Typo.body2">وضعیت: @Model.StatusDisplay</MudText>
@if (!string.IsNullOrWhiteSpace(Model.ReferenceNumber))
{
<MudText Typo="Typo.body2">شماره مرجع: @Model.ReferenceNumber</MudText>
}
<MudText Typo="Typo.body2">توضیحات: @Model.Description</MudText>
@if (!string.IsNullOrWhiteSpace(Model.RejectionReason))
{
<MudAlert Severity="Severity.Error">
دلیل رد: @Model.RejectionReason
</MudAlert>
}
@if (Model.Status == (int)ManualPaymentDialogStatus.Pending)
{
<MudTextField T="string"
Label="یادداشت تایید / دلیل رد"
Variant="Variant.Outlined"
Lines="3"
@bind-Value="_adminNote" />
}
</MudStack>
}
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Outlined" Color="Color.Default" OnClick="Close">
بستن
</MudButton>
@if (Mode == ManualPaymentDialogMode.Create)
{
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="CreateAsync">
ثبت
</MudButton>
}
else if (Model is not null && Model.Status == (int)ManualPaymentDialogStatus.Pending)
{
<MudButton Variant="Variant.Filled" Color="Color.Success" OnClick="ApproveAsync">
تایید
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Error" OnClick="RejectAsync">
رد
</MudButton>
}
</DialogActions>
</MudDialog>

View File

@@ -0,0 +1,122 @@
using BackOffice.BFF.ManualPayment.Protobuf;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace BackOffice.Pages.Payment.Components;
public enum ManualPaymentDialogMode
{
Create = 0,
Details = 1
}
public enum ManualPaymentDialogStatus
{
Pending = 0,
Approved = 1,
Rejected = 2,
Cancelled = 3
}
public partial class ManualPaymentDialog
{
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
[Inject] public ManualPaymentContract.ManualPaymentContractClient ManualPaymentClient { get; set; } = default!;
[Inject] public ISnackbar Snackbar { get; set; } = default!;
[Parameter] public ManualPaymentDialogMode Mode { get; set; }
[Parameter] public ManualPaymentModel? Model { get; set; }
private ManualPaymentModel _createModel = new();
private string? _adminNote;
private async Task CreateAsync()
{
try
{
var request = new CreateManualPaymentRequest
{
UserId = _createModel.UserId,
Amount = _createModel.Amount,
Type = _createModel.Type,
Description = _createModel.Description
};
if (!string.IsNullOrWhiteSpace(_createModel.ReferenceNumber))
{
request.ReferenceNumber = new Google.Protobuf.WellKnownTypes.StringValue
{
Value = _createModel.ReferenceNumber
};
}
await ManualPaymentClient.CreateManualPaymentAsync(request);
Snackbar.Add("پرداخت دستی با موفقیت ثبت شد.", Severity.Success);
MudDialog.Close(DialogResult.Ok(true));
}
catch (Exception ex)
{
Snackbar.Add($"خطا در ثبت پرداخت دستی: {ex.Message}", Severity.Error);
}
}
private async Task ApproveAsync()
{
if (Model is null) return;
try
{
var request = new ApproveManualPaymentRequest
{
ManualPaymentId = Model.Id
};
if (!string.IsNullOrWhiteSpace(_adminNote))
{
request.ApprovalNote = new Google.Protobuf.WellKnownTypes.StringValue
{
Value = _adminNote
};
}
await ManualPaymentClient.ApproveManualPaymentAsync(request);
Snackbar.Add("پرداخت دستی تایید شد.", Severity.Success);
MudDialog.Close(DialogResult.Ok(true));
}
catch (Exception ex)
{
Snackbar.Add($"خطا در تایید پرداخت دستی: {ex.Message}", Severity.Error);
}
}
private async Task RejectAsync()
{
if (Model is null) return;
try
{
if (string.IsNullOrWhiteSpace(_adminNote))
{
Snackbar.Add("لطفاً دلیل رد را وارد کنید.", Severity.Warning);
return;
}
var request = new RejectManualPaymentRequest
{
ManualPaymentId = Model.Id,
RejectionReason = _adminNote
};
await ManualPaymentClient.RejectManualPaymentAsync(request);
Snackbar.Add("پرداخت دستی رد شد.", Severity.Success);
MudDialog.Close(DialogResult.Ok(true));
}
catch (Exception ex)
{
Snackbar.Add($"خطا در رد پرداخت دستی: {ex.Message}", Severity.Error);
}
}
private void Close() => MudDialog.Close(DialogResult.Cancel());
}

View File

@@ -0,0 +1,42 @@
@namespace BackOffice.Pages.Payment.Components
@using MudBlazor
<MudDialog>
<DialogContent>
@if (Model is not null)
{
<MudStack Spacing="2">
<MudText Typo="Typo.h6">جزئیات تراکنش</MudText>
<MudText Typo="Typo.body2">شناسه: @Model.Id</MudText>
<MudText Typo="Typo.body2">RefId: @Model.RefId</MudText>
<MudText Typo="Typo.body2">مبلغ: @Model.Amount.ToString("N0") ریال</MudText>
<MudText Typo="Typo.body2">وضعیت: @StatusText</MudText>
<MudText Typo="Typo.body2">تاریخ پرداخت: @Model.PaymentDate.ToLocalTime().MiladiToJalaliWithTime()</MudText>
@if (!string.IsNullOrWhiteSpace(Model.BankReferenceId))
{
<MudText Typo="Typo.body2">BankReferenceId: @Model.BankReferenceId</MudText>
}
@if (!string.IsNullOrWhiteSpace(Model.TrackingCode))
{
<MudText Typo="Typo.body2">TrackingCode: @Model.TrackingCode</MudText>
}
@if (Model.PaymentFailed && !string.IsNullOrWhiteSpace(Model.PaymentFailedReason))
{
<MudAlert Severity="Severity.Error">
دلیل خطا: @Model.PaymentFailedReason
</MudAlert>
}
</MudStack>
}
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Outlined" Color="Color.Default" OnClick="Close">
بستن
</MudButton>
</DialogActions>
</MudDialog>

View File

@@ -0,0 +1,46 @@
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace BackOffice.Pages.Payment.Components;
public partial class TransactionDetailsDialog
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public TransactionDetailsModel? Model { get; set; }
private string StatusText => Model is null ? string.Empty : GetStatusText(Model.PaymentStatus);
private void Close()
{
MudDialog.Cancel();
}
private static string GetStatusText(int status)
{
return status switch
{
0 => "در انتظار",
1 => "موفق",
2 => "ناموفق",
3 => "بازگشت وجه",
_ => "نامشخص"
};
}
public class TransactionDetailsModel
{
public long Id { get; set; }
public string? RefId { get; set; }
public long Amount { get; set; }
public int PaymentStatus { get; set; }
public DateTime PaymentDate { get; set; }
public string? BankReferenceId { get; set; }
public string? TrackingCode { get; set; }
public bool PaymentFailed { get; set; }
public string? PaymentFailedReason { get; set; }
}
}

View File

@@ -0,0 +1,246 @@
@page "/payment/manual-payments"
@using BackOffice.BFF.ManualPayment.Protobuf
@using BackOffice.Pages.Payment.Components
@using BackOffice.Common.BaseComponents
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
<Filters>
<MudNumericField T="long?"
HideSpinButtons="true"
Clearable="true"
Label="شناسه کاربر"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="_userIdFilter" />
<MudTextField T="string"
Label="شماره مرجع"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="_referenceFilter" />
<MudSelect T="int?"
Clearable="true"
Label="وضعیت"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="_statusFilter">
<MudSelectItem T="int?" Value="@(0)">در انتظار</MudSelectItem>
<MudSelectItem T="int?" Value="@(1)">تایید شده</MudSelectItem>
<MudSelectItem T="int?" Value="@(2)">رد شده</MudSelectItem>
<MudSelectItem T="int?" Value="@(3)">لغو شده</MudSelectItem>
</MudSelect>
<MudSelect T="int?"
Clearable="true"
Label="نوع"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="_typeFilter">
<MudSelectItem T="int?" Value="@(1)">واریز نقدی</MudSelectItem>
<MudSelectItem T="int?" Value="@(2)">شارژ کیف‌پول تخفیف</MudSelectItem>
<MudSelectItem T="int?" Value="@(3)">شارژ کیف‌پول شبکه</MudSelectItem>
<MudSelectItem T="int?" Value="@(4)">تسویه حساب</MudSelectItem>
<MudSelectItem T="int?" Value="@(5)">اصلاح خطا</MudSelectItem>
<MudSelectItem T="int?" Value="@(6)">بازگشت وجه</MudSelectItem>
<MudSelectItem T="int?" Value="@(99)">سایر</MudSelectItem>
</MudSelect>
</Filters>
<Content>
<MudPaper Class="pa-2 mb-2">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.subtitle1">پرداخت‌های دستی</MudText>
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
OnClick="OpenCreateDialog">
ثبت پرداخت دستی جدید
</MudButton>
</MudStack>
</MudPaper>
<MudDataGrid T="ManualPaymentModel"
ServerData="LoadData"
Hover="true"
Dense="true">
<Columns>
<PropertyColumn Property="x => x.Id" Title="شناسه" />
<PropertyColumn Property="x => x.UserId" Title="شناسه کاربر" />
<PropertyColumn Property="x => x.UserFullName" Title="نام کاربر" />
<PropertyColumn Property="x => x.Amount" Title="مبلغ">
<CellTemplate>
@context.Item.Amount.ToString("N0")
</CellTemplate>
</PropertyColumn>
<TemplateColumn Title="نوع">
<CellTemplate>
@GetTypeText(context.Item.Type)
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="وضعیت">
<CellTemplate>
<MudChip T="string"
Size="Size.Small"
Color="@GetStatusColor(context.Item.Status)">
@GetStatusText(context.Item.Status)
</MudChip>
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="تاریخ ایجاد">
<CellTemplate>
@context.Item.Created.ToLocalTime().MiladiToJalaliWithTime()
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="عملیات">
<CellTemplate>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<MudTooltip Text="جزئیات">
<MudIconButton Icon="@Icons.Material.Filled.Info"
Size="Size.Small"
OnClick="@(() => OpenDetails(context.Item))" />
</MudTooltip>
</MudStack>
</CellTemplate>
</TemplateColumn>
</Columns>
</MudDataGrid>
</Content>
</BasePageComponent>
@code {
[Inject] public BackOffice.BFF.ManualPayment.Protobuf.ManualPaymentContract.ManualPaymentContractClient ManualPaymentClient { get; set; } = default!;
[Inject] public IDialogService DialogService { get; set; } = default!;
private BasePageComponent? _basePage;
private long? _userIdFilter;
private string? _referenceFilter;
private int? _statusFilter;
private int? _typeFilter;
private async Task<GridData<ManualPaymentModel>> LoadData(GridState<ManualPaymentModel> state)
{
var pageNumber = state.Page + 1;
var pageSize = state.PageSize;
var request = new GetManualPaymentsRequest
{
PageNumber = pageNumber,
PageSize = pageSize
};
if (_userIdFilter.HasValue)
{
request.UserId = new Google.Protobuf.WellKnownTypes.Int64Value { Value = _userIdFilter.Value };
}
if (_statusFilter.HasValue)
{
request.Status = new Google.Protobuf.WellKnownTypes.Int32Value { Value = _statusFilter.Value };
}
if (_typeFilter.HasValue)
{
request.Type = new Google.Protobuf.WellKnownTypes.Int32Value { Value = _typeFilter.Value };
}
if (!string.IsNullOrWhiteSpace(_referenceFilter))
{
request.ReferenceNumber = new Google.Protobuf.WellKnownTypes.StringValue { Value = _referenceFilter };
}
var response = await ManualPaymentClient.GetManualPaymentsAsync(request);
return new GridData<ManualPaymentModel>
{
Items = response.Models.ToList(),
TotalItems = (int)response.MetaData.TotalCount
};
}
private async Task OnFilterSubmit()
{
await _basePage!.ReloadAsync();
}
private async Task OnFilterCleared()
{
_userIdFilter = null;
_referenceFilter = null;
_statusFilter = null;
_typeFilter = null;
await _basePage!.ReloadAsync();
}
private async Task OpenCreateDialog()
{
var parameters = new DialogParameters
{
{ nameof(ManualPaymentDialog.Mode), ManualPaymentDialogMode.Create }
};
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true };
var dialog = DialogService.Show<ManualPaymentDialog>("ثبت پرداخت دستی", parameters, options);
var result = await dialog.Result;
if (!result.Cancelled)
{
await _basePage!.ReloadAsync();
}
}
private async Task OpenDetails(ManualPaymentModel model)
{
var parameters = new DialogParameters
{
{ nameof(ManualPaymentDialog.Mode), ManualPaymentDialogMode.Details },
{ nameof(ManualPaymentDialog.Model), model }
};
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small, FullWidth = true };
var dialog = DialogService.Show<ManualPaymentDialog>($"جزئیات پرداخت دستی #{model.Id}", parameters, options);
var result = await dialog.Result;
if (!result.Cancelled)
{
await _basePage!.ReloadAsync();
}
}
private static string GetTypeText(int type)
{
return type switch
{
1 => "واریز نقدی",
2 => "شارژ کیف‌پول تخفیف",
3 => "شارژ کیف‌پول شبکه",
4 => "تسویه حساب",
5 => "اصلاح خطا",
6 => "بازگشت وجه",
99 => "سایر",
_ => "نامشخص"
};
}
private static string GetStatusText(int status)
{
return status switch
{
0 => "در انتظار",
1 => "تایید شده",
2 => "رد شده",
3 => "لغو شده",
_ => "نامشخص"
};
}
private static Color GetStatusColor(int status)
{
return status switch
{
0 => Color.Warning,
1 => Color.Success,
2 => Color.Error,
3 => Color.Default,
_ => Color.Default
};
}
}

View File

@@ -0,0 +1,82 @@
@page "/payment/transactions"
@using BackOffice.Common.BaseComponents
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
<Filters>
<BackOffice.Common.BaseComponents.DateRangePicker @bind-From="_fromDate" @bind-To="_toDate" Label="بازه تاریخی" />
<MudSelect T="int?"
Clearable="true"
Label="وضعیت پرداخت"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="_statusFilter">
<MudSelectItem T="int?" Value="@(0)">در انتظار</MudSelectItem>
<MudSelectItem T="int?" Value="@(1)">موفق</MudSelectItem>
<MudSelectItem T="int?" Value="@(2)">ناموفق</MudSelectItem>
<MudSelectItem T="int?" Value="@(3)">بازگشت وجه</MudSelectItem>
</MudSelect>
<MudSelect T="int?"
Clearable="true"
Label="نوع تراکنش"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="_typeFilter">
<MudSelectItem T="int?" Value="@(0)">نامشخص</MudSelectItem>
<MudSelectItem T="int?" Value="@(1)">خرید پکیج</MudSelectItem>
<MudSelectItem T="int?" Value="@(2)">پرداخت دستی</MudSelectItem>
<MudSelectItem T="int?" Value="@(3)">شارژ کیف‌پول</MudSelectItem>
<MudSelectItem T="int?" Value="@(4)">بازگشت وجه</MudSelectItem>
</MudSelect>
</Filters>
<Content>
<MudPaper Class="pa-2 mb-2">
<MudText Typo="Typo.subtitle1">مدیریت تراکنش‌ها</MudText>
</MudPaper>
<MudDataGrid T="TransactionModel"
ServerData="LoadData"
Hover="true"
Dense="true">
<Columns>
<PropertyColumn Property="x => x.Id" Title="شناسه" />
<PropertyColumn Property="x => x.RefId" Title="شماره مرجع" />
<PropertyColumn Property="x => x.Amount" Title="مبلغ">
<CellTemplate>
@context.Item.Amount.ToString("N0")
</CellTemplate>
</PropertyColumn>
<TemplateColumn Title="وضعیت">
<CellTemplate>
<MudChip T="string"
Size="Size.Small"
Color="@GetStatusColor(context.Item.PaymentStatus)">
@GetStatusText(context.Item.PaymentStatus)
</MudChip>
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="تاریخ پرداخت">
<CellTemplate>
@context.Item.PaymentDate.ToLocalTime().MiladiToJalaliWithTime()
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="نوع">
<CellTemplate>
@GetTypeText(context.Item.Type)
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="عملیات">
<CellTemplate>
<MudTooltip Text="جزئیات">
<MudIconButton Icon="@Icons.Material.Filled.Info"
Size="Size.Small"
OnClick="@(() => OpenDetails(context.Item))" />
</MudTooltip>
</CellTemplate>
</TemplateColumn>
</Columns>
</MudDataGrid>
</Content>
</BasePageComponent>

View File

@@ -0,0 +1,94 @@
using BackOffice.Common.BaseComponents;
using MudBlazor;
namespace BackOffice.Pages.Payment;
public partial class Transactions
{
private BasePageComponent? _basePage;
private DateTime? _fromDate;
private DateTime? _toDate;
private int? _statusFilter;
private int? _typeFilter;
private async Task<GridData<TransactionModel>> LoadData(GridState<TransactionModel> state)
{
// TODO: Connect to BackOffice.BFF Transactions when API is ready
await Task.CompletedTask;
return new GridData<TransactionModel>
{
Items = Array.Empty<TransactionModel>(),
TotalItems = 0
};
}
private Task OnFilterSubmit()
{
_basePage?.ReloadGrid();
return Task.CompletedTask;
}
private Task OnFilterCleared()
{
_fromDate = null;
_toDate = null;
_statusFilter = null;
_typeFilter = null;
_basePage?.ReloadGrid();
return Task.CompletedTask;
}
private void OpenDetails(TransactionModel model)
{
// TODO: Open TransactionDetailsDialog with model
}
private static string GetStatusText(int status)
{
return status switch
{
0 => "در انتظار",
1 => "موفق",
2 => "ناموفق",
3 => "بازگشت وجه",
_ => "نامشخص"
};
}
private static Color GetStatusColor(int status)
{
return status switch
{
0 => Color.Warning,
1 => Color.Success,
2 => Color.Error,
3 => Color.Info,
_ => Color.Default
};
}
private static string GetTypeText(int type)
{
return type switch
{
1 => "خرید پکیج",
2 => "پرداخت دستی",
3 => "شارژ کیف‌پول",
4 => "بازگشت وجه",
_ => "نامشخص"
};
}
private class TransactionModel
{
public long Id { get; set; }
public string? RefId { get; set; }
public long Amount { get; set; }
public int PaymentStatus { get; set; }
public DateTime PaymentDate { get; set; }
public int Type { get; set; }
}
}

View File

@@ -1,7 +1,8 @@
using BackOffice.BFF.Products.Protobuf.Protos.Products;
using BackOffice.Common.BaseComponents;
using BackOffice.Common.Utilities;
using BackOffice.Pages.Tag.Components;
// TODO: Uncomment when Tag proto project is created
// using BackOffice.Pages.Tag.Components;
using BackOffice.Pages.Products.Components;
using Mapster;
using Microsoft.AspNetCore.Components;
@@ -239,16 +240,20 @@ public partial class ProductsMainPage
Navigation.NavigateTo($"{RouteConstance.ProductCategories}{model.Id}");
}
// TODO: Enable when Tag proto project is created
public async Task OpenTagAssignment(DataModel model)
{
var parameters = new DialogParameters<AssignTagsDialog>
{
{ x => x.ProductId, model.Id },
{ x => x.ProductTitle, model.Title }
};
// var parameters = new DialogParameters<AssignTagsDialog>
// {
// { x => x.ProductId, model.Id },
// { x => x.ProductTitle, model.Title }
// };
await DialogService.ShowAsync<AssignTagsDialog>("مدیریت تگ‌های محصول", parameters,
new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small, FullWidth = true });
// await DialogService.ShowAsync<AssignTagsDialog>("مدیریت تگ‌های محصول", parameters,
// new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small, FullWidth = true });
await Task.CompletedTask;
Snackbar.Add("قابلیت تگ‌گذاری در حال توسعه است", Severity.Info);
}
public async Task OpenImagePreview(string imagePath, string title)

View File

@@ -0,0 +1,39 @@
@namespace BackOffice.Pages.Role.Components
@using MudBlazor
<MudDialog>
<DialogContent>
<MudStack Spacing="2">
<MudText Typo="Typo.h6">دسترسی‌های نقش @RoleName</MudText>
@foreach (var category in _categories)
{
<MudExpansionPanels Elevation="0">
<MudExpansionPanel Text="@category.DisplayName" Expanded="false">
<MudStack Spacing="1">
@foreach (var permission in category.Permissions)
{
<MudCheckBox T="bool"
Label="@permission.DisplayName"
LabelPosition="LabelPosition.End"
Color="Color.Primary"
Value="@_selectedPermissions.Contains(permission.Key)"
ValueChanged="(checkedValue => OnPermissionChanged(checkedValue, permission.Key))" />
}
</MudStack>
</MudExpansionPanel>
</MudExpansionPanels>
}
</MudStack>
</DialogContent>
<DialogActions>
<MudButton Color="Color.Default" Variant="Variant.Text" OnClick="Cancel">
انصراف
</MudButton>
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="SaveAsync">
ذخیره
</MudButton>
</DialogActions>
</MudDialog>

View File

@@ -0,0 +1,162 @@
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace BackOffice.Pages.Role.Components;
public partial class RolePermissionsDialog
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public string RoleName { get; set; } = string.Empty;
private readonly List<PermissionCategory> _categories = new();
private readonly HashSet<string> _selectedPermissions = new(StringComparer.OrdinalIgnoreCase);
protected override void OnInitialized()
{
base.OnInitialized();
_categories.AddRange(GetPermissionCategories());
// پیش‌انتخاب براساس RoleName (هم‌راستا با BFF RolePermissionConfig)
if (string.Equals(RoleName, "SuperAdmin", StringComparison.OrdinalIgnoreCase))
{
foreach (var permission in _categories.SelectMany(c => c.Permissions))
{
_selectedPermissions.Add(permission.Key);
}
}
else if (string.Equals(RoleName, "Admin", StringComparison.OrdinalIgnoreCase))
{
foreach (var permission in _categories.SelectMany(c => c.Permissions))
{
if (!permission.Key.StartsWith("settings.", StringComparison.OrdinalIgnoreCase))
{
_selectedPermissions.Add(permission.Key);
}
}
}
else if (string.Equals(RoleName, "Inspector", StringComparison.OrdinalIgnoreCase))
{
foreach (var permission in _categories.SelectMany(c => c.Permissions))
{
if (permission.Key.EndsWith(".view", StringComparison.OrdinalIgnoreCase))
{
_selectedPermissions.Add(permission.Key);
}
}
}
}
private void OnPermissionChanged(bool isChecked, string permissionKey)
{
if (isChecked)
{
_selectedPermissions.Add(permissionKey);
}
else
{
_selectedPermissions.Remove(permissionKey);
}
}
private Task SaveAsync()
{
// TODO: اتصال به API واقعی RBAC در BFF/CMS برای ذخیره RolePermissions
MudDialog.Close(DialogResult.Ok(_selectedPermissions.ToList()));
return Task.CompletedTask;
}
private void Cancel()
{
MudDialog.Cancel();
}
private static IEnumerable<PermissionCategory> GetPermissionCategories()
{
return new[]
{
new PermissionCategory(
"orders",
"سفارش‌ها",
new[]
{
new PermissionItem("orders.view", "مشاهده سفارش‌ها"),
new PermissionItem("orders.create", "ایجاد سفارش"),
new PermissionItem("orders.update", "ویرایش سفارش"),
new PermissionItem("orders.delete", "حذف سفارش"),
new PermissionItem("orders.cancel", "لغو سفارش"),
new PermissionItem("orders.approve", "تایید سفارش / عملیات حساس")
}),
new PermissionCategory(
"products",
"محصولات",
new[]
{
new PermissionItem("products.view", "مشاهده محصولات"),
new PermissionItem("products.create", "ایجاد محصول"),
new PermissionItem("products.update", "ویرایش محصول"),
new PermissionItem("products.delete", "حذف محصول")
}),
new PermissionCategory(
"users",
"کاربران",
new[]
{
new PermissionItem("users.view", "مشاهده کاربران"),
new PermissionItem("users.update", "ویرایش کاربران"),
new PermissionItem("users.delete", "حذف کاربران")
}),
new PermissionCategory(
"commission",
"کمیسیون و برداشت‌ها",
new[]
{
new PermissionItem("commission.view", "مشاهده گزارش‌های کمیسیون"),
new PermissionItem("commission.approve_withdrawal", "تایید درخواست برداشت")
}),
new PermissionCategory(
"discountshop",
"فروشگاه تخفیفی",
new[]
{
new PermissionItem("discountshop.manage", "مدیریت فروشگاه تخفیفی (محصولات/سفارش‌ها/گزارش فروش)")
}),
new PermissionCategory(
"publicmessages",
"پیام‌های عمومی",
new[]
{
new PermissionItem("publicmessages.view", "مشاهده پیام‌های عمومی"),
new PermissionItem("publicmessages.create", "ایجاد پیام عمومی"),
new PermissionItem("publicmessages.update", "ویرایش پیام عمومی"),
new PermissionItem("publicmessages.publish", "انتشار/بایگانی پیام‌ها")
}),
new PermissionCategory(
"settings",
"تنظیمات و سیستم",
new[]
{
new PermissionItem("settings.view", "مشاهده تنظیمات"),
new PermissionItem("settings.manage_configuration", "مدیریت تنظیمات سیستم"),
new PermissionItem("settings.manage_vat", "مدیریت تنظیمات VAT"),
new PermissionItem("system.alerts.view", "مشاهده هشدارهای سیستم"),
new PermissionItem("system.health.view", "مشاهده وضعیت سلامت سیستم")
}),
new PermissionCategory(
"reports",
"گزارش‌ها",
new[]
{
new PermissionItem("reports.view", "مشاهده گزارش‌ها")
})
};
}
private record PermissionCategory(string Key, string DisplayName, IReadOnlyList<PermissionItem> Permissions);
private record PermissionItem(string Key, string DisplayName);
}

View File

@@ -41,6 +41,15 @@
<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.Security"
Size="Size.Small"
Color="Color.Primary"
ButtonType="ButtonType.Button"
OnClick="@(() => EditPermissions(context.Item))"
Style="cursor:pointer;" />
</MudTooltip>
</MudStack>
</CellTemplate>
</TemplateColumn>
@@ -56,4 +65,3 @@

View File

@@ -93,4 +93,22 @@ public partial class RoleMainPage
_request = new() { Filter = new() { } };
ReLoadData();
}
private async Task EditPermissions(DataModel model)
{
var parameters = new DialogParameters<RolePermissionsDialog>
{
{ x => x.RoleName, model.Name }
};
var options = new DialogOptions
{
CloseButton = true,
FullWidth = true,
MaxWidth = MaxWidth.Medium
};
var dialog = await DialogService.ShowAsync<RolePermissionsDialog>($"دسترسی‌های نقش {model.Title}", parameters, options);
await dialog.Result;
}
}

View File

@@ -30,11 +30,11 @@
<MudSelectItem Value="@("en")">English</MudSelectItem>
</MudSelect>
<MudSwitch @bind-Value="_darkMode"
<MudSwitch T="bool" @bind-Value="_darkMode"
Color="Color.Primary"
Label="حالت تاریک" />
<MudSwitch @bind-Value="_compactMode"
<MudSwitch T="bool" @bind-Value="_compactMode"
Color="Color.Secondary"
Label="حالت فشرده" />
@@ -67,15 +67,15 @@
</MudCardHeader>
<MudCardContent>
<MudStack Spacing="3">
<MudSwitch @bind-Value="_emailNotifications"
<MudSwitch T="bool" @bind-Value="_emailNotifications"
Color="Color.Primary"
Label="دریافت اعلان‌های ایمیل" />
<MudSwitch @bind-Value="_smsNotifications"
<MudSwitch T="bool" @bind-Value="_smsNotifications"
Color="Color.Secondary"
Label="دریافت اعلان‌های پیامک" />
<MudSwitch @bind-Value="_systemNotifications"
<MudSwitch T="bool" @bind-Value="_systemNotifications"
Color="Color.Info"
Label="اعلان‌های سیستمی" />
@@ -163,7 +163,7 @@
</MudCardHeader>
<MudCardContent>
<MudStack Spacing="3">
<MudSwitch @bind-Value="_twoFactorEnabled"
<MudSwitch T="bool" @bind-Value="_twoFactorEnabled"
Color="Color.Success"
Label="فعال‌سازی احراز هویت دو مرحله‌ای" />

View File

@@ -81,13 +81,13 @@
</MudItem>
<MudItem xs="12">
<MudSwitch @bind-Value="_commissionConfig.AutoCalculationEnabled"
<MudSwitch T="bool" @bind-Value="_commissionConfig.AutoCalculationEnabled"
Color="Color.Primary"
Label="محاسبه خودکار هفتگی فعال باشد" />
</MudItem>
<MudItem xs="12">
<MudSwitch @bind-Value="_commissionConfig.AutoPayoutEnabled"
<MudSwitch T="bool" @bind-Value="_commissionConfig.AutoPayoutEnabled"
Color="Color.Primary"
Label="پرداخت خودکار فعال باشد" />
</MudItem>
@@ -158,19 +158,19 @@
</MudItem>
<MudItem xs="12">
<MudSwitch @bind-Value="_networkConfig.BinaryTreeEnabled"
<MudSwitch T="bool" @bind-Value="_networkConfig.BinaryTreeEnabled"
Color="Color.Primary"
Label="سیستم باینری فعال باشد" />
</MudItem>
<MudItem xs="12">
<MudSwitch @bind-Value="_networkConfig.AutoPlacementEnabled"
<MudSwitch T="bool" @bind-Value="_networkConfig.AutoPlacementEnabled"
Color="Color.Primary"
Label="جایگذاری خودکار در درخت" />
</MudItem>
<MudItem xs="12">
<MudSwitch @bind-Value="_networkConfig.SpilloverEnabled"
<MudSwitch T="bool" @bind-Value="_networkConfig.SpilloverEnabled"
Color="Color.Primary"
Label="Spillover فعال باشد"
HelperText="اعضای اضافی به پایین درخت منتقل شوند" />
@@ -244,19 +244,19 @@
</MudItem>
<MudItem xs="12">
<MudSwitch @bind-Value="_clubConfig.AutoRenewalEnabled"
<MudSwitch T="bool" @bind-Value="_clubConfig.AutoRenewalEnabled"
Color="Color.Primary"
Label="تمدید خودکار عضویت" />
</MudItem>
<MudItem xs="12">
<MudSwitch @bind-Value="_clubConfig.EmailNotificationsEnabled"
<MudSwitch T="bool" @bind-Value="_clubConfig.EmailNotificationsEnabled"
Color="Color.Primary"
Label="ارسال ایمیل یادآوری انقضا" />
</MudItem>
<MudItem xs="12">
<MudSwitch @bind-Value="_clubConfig.SmsNotificationsEnabled"
<MudSwitch T="bool" @bind-Value="_clubConfig.SmsNotificationsEnabled"
Color="Color.Primary"
Label="ارسال پیامک یادآوری انقضا" />
</MudItem>
@@ -372,6 +372,38 @@
</MudStack>
</MudCardContent>
</MudCard>
<MudCard Class="mt-4" Elevation="0">
<MudCardContent>
<MudText Typo="Typo.h6" Class="mb-2">تنظیمات مالیات بر ارزش افزوده (VAT)</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary" Class="mb-2">
درصد پیش‌فرض VAT که روی سفارش‌ها اعمال می‌شود.
</MudText>
<MudGrid>
<MudItem xs="12" md="4">
<MudNumericField @bind-Value="_vatConfig.DefaultVatPercentage"
Label="درصد VAT پیش‌فرض"
Variant="Variant.Outlined"
Adornment="Adornment.End"
AdornmentText="%"
Min="0"
Max="100" />
</MudItem>
</MudGrid>
<MudDivider Class="my-4" />
<MudStack Row="true" Justify="Justify.FlexEnd" Spacing="2">
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="SaveVatConfig"
StartIcon="@Icons.Material.Filled.Save">
ذخیره تنظیمات VAT
</MudButton>
</MudStack>
</MudCardContent>
</MudCard>
</MudTabPanel>
</MudTabs>
</MudContainer>
@@ -383,6 +415,7 @@
private NetworkConfig _networkConfig = new();
private ClubConfig _clubConfig = new();
private SystemConfig _systemConfig = new();
private VatConfig _vatConfig = new();
private Dictionary<string, string> _configurations = new();
@@ -453,6 +486,11 @@
TwoFactorAuthEnabled = GetBoolConfig("System.TwoFactorAuth", true),
EmailVerificationRequired = GetBoolConfig("System.EmailVerification", true)
};
_vatConfig = new VatConfig
{
DefaultVatPercentage = GetIntConfig("DefaultVatPercentage", 10)
};
}
catch (Exception ex)
{
@@ -543,6 +581,19 @@
}
}
private async Task SaveVatConfig()
{
try
{
await SaveConfig("DefaultVatPercentage", _vatConfig.DefaultVatPercentage.ToString(), 0);
Snackbar.Add("تنظیمات VAT با موفقیت ذخیره شد", Severity.Success);
}
catch (Exception ex)
{
Snackbar.Add($"خطا در ذخیره تنظیمات VAT: {ex.Message}", Severity.Error);
}
}
private async Task SaveConfig(string key, string value, int scope)
{
var request = new BackOffice.BFF.Configuration.Protobuf.CreateOrUpdateConfigurationRequest
@@ -631,4 +682,9 @@
public bool TwoFactorAuthEnabled { get; set; }
public bool EmailVerificationRequired { get; set; }
}
private class VatConfig
{
public int DefaultVatPercentage { get; set; }
}
}

View File

@@ -16,7 +16,7 @@
<MudTextField T="string" @bind-Value="Model.PostalCode" Disabled="_isLoading" Label="کد پستی" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudSwitch @bind-Value="Model.IsDefault" Color="Color.Primary" Label="ایا پیش فرض است" LabelPosition="LabelPosition.End" />
<MudSwitch T="bool" @bind-Value="Model.IsDefault" Color="Color.Primary" Label="ایا پیش فرض است" LabelPosition="LabelPosition.End" />
</MudItem>
</MudStack>

View File

@@ -17,7 +17,7 @@
<MudTextField T="string" @bind-Value="Model.PostalCode" Disabled="_isLoading" Label="کد پستی" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudSwitch @bind-Value="Model.IsDefault" Color="Color.Primary" Label="ایا پیش فرض است" LabelPosition="LabelPosition.End" />
<MudSwitch T="bool" @bind-Value="Model.IsDefault" Color="Color.Primary" Label="ایا پیش فرض است" LabelPosition="LabelPosition.End" />
</MudItem>
</MudStack>
</DialogContent>

View File

@@ -20,7 +20,7 @@
Required="true"
Lines="3" />
<MudSwitch @bind-Checked="_refundPayment" Color="Color.Primary" />
<MudSwitch T="bool" @bind-Value="_refundPayment" Color="Color.Primary" />
<MudText Typo="Typo.body2">
بازگشت وجه به کاربر (در صورت پرداخت موفق)
</MudText>

View File

@@ -51,6 +51,21 @@
}
</MudText>
@if (_model.VatAmount > 0)
{
<MudDivider Class="my-2" />
<MudText Typo="Typo.subtitle2">جزئیات مالیات بر ارزش افزوده</MudText>
<MudText Typo="Typo.body2">
مبلغ کالا (قبل از مالیات): @_model.VatBaseAmount.ToString("N0") تومان
</MudText>
<MudText Typo="Typo.body2">
مالیات (@_model.VatPercentage.ToString("0")٪): @_model.VatAmount.ToString("N0") تومان
</MudText>
<MudText Typo="Typo.body2">
مبلغ نهایی (شامل مالیات): @_model.VatTotalAmount.ToString("N0") تومان
</MudText>
}
<MudDivider Class="my-2" />
<MudText Typo="Typo.subtitle2">Timeline وضعیت</MudText>
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">

View File

@@ -131,6 +131,16 @@
<Columns>
<PropertyColumn Property="x => x.Id" Title="شناسه"/>
<PropertyColumn Property="x => x.Amount" Title="مبلغ"/>
<PropertyColumn Property="x => x.VatAmount" Title="مبلغ VAT">
<CellTemplate>
@(context.Item.VatAmount > 0 ? context.Item.VatAmount.ToString("N0") : "-")
</CellTemplate>
</PropertyColumn>
<PropertyColumn Property="x => x.VatPercentage" Title="درصد VAT">
<CellTemplate>
@(context.Item.VatPercentage > 0 ? $"{context.Item.VatPercentage:0}٪" : "-")
</CellTemplate>
</PropertyColumn>
<PropertyColumn Property="x => x.UserFullName" Title="نام کاربر"/>
<PropertyColumn Property="x => x.UserNationalCode" Title="کدملی"/>
<TemplateColumn Title="روش پرداخت">

View File

@@ -0,0 +1,77 @@
using System.Security.Claims;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
namespace BackOffice.Services.Authorization;
public class AuthorizationService : IAuthorizationService
{
private const string PermissionsCacheKey = "BackOffice.Permissions";
private readonly ILocalStorageService _localStorage;
private readonly AuthenticationStateProvider _authenticationStateProvider;
public AuthorizationService(
ILocalStorageService localStorage,
AuthenticationStateProvider authenticationStateProvider)
{
_localStorage = localStorage;
_authenticationStateProvider = authenticationStateProvider;
}
public async Task<bool> HasPermissionAsync(string permission)
{
if (string.IsNullOrWhiteSpace(permission))
{
return true;
}
var cachedPermissions = await _localStorage.GetItemAsync<HashSet<string>>(PermissionsCacheKey);
if (cachedPermissions == null || cachedPermissions.Count == 0)
{
// فعلاً بر اساس Role ساده تصمیم می‌گیریم تا زمانی که BFF Permission API آماده شود
var role = await GetUserRoleAsync();
if (string.IsNullOrWhiteSpace(role))
{
return false;
}
// SuperAdmin: همه دسترسی‌ها
if (string.Equals(role, "SuperAdmin", StringComparison.OrdinalIgnoreCase))
{
return true;
}
// Admin: اجازه دسترسی به بیشتر صفحات مدیریتی
if (string.Equals(role, "Admin", StringComparison.OrdinalIgnoreCase))
{
// فعلاً همه permissionهای UI را برای Admin آزاد می‌کنیم
return true;
}
// Inspector: فقط view
if (string.Equals(role, "Inspector", StringComparison.OrdinalIgnoreCase))
{
return permission.EndsWith(".view", StringComparison.OrdinalIgnoreCase);
}
return false;
}
return cachedPermissions.Contains(permission, StringComparer.OrdinalIgnoreCase);
}
public async Task<string?> GetUserRoleAsync()
{
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity is not { IsAuthenticated: true })
{
return null;
}
var roleClaim = user.FindFirst(ClaimTypes.Role) ?? user.FindFirst("role");
return roleClaim?.Value;
}
}

View File

@@ -0,0 +1,9 @@
namespace BackOffice.Services.Authorization;
public interface IAuthorizationService
{
Task<bool> HasPermissionAsync(string permission);
Task<string?> GetUserRoleAsync();
}

View File

@@ -1,9 +1,12 @@
@using Microsoft.AspNetCore.Components.Authorization
@inject BackOffice.Services.Authorization.IAuthorizationService AuthorizationService
<MudNavMenu Bordered="false" Class="nav-menu">
<MudText Class="nav-menu__title" Typo="Typo.subtitle2">منوی اصلی</MudText>
<MudDivider Class="mb-2" />
@if (CanViewDashboard)
{
<MudNavLink Match="NavLinkMatch.Prefix"
Href="/"
Icon="@Icons.Material.Filled.Dashboard">
@@ -15,6 +18,7 @@
Icon="@Icons.Material.Filled.ViewQuilt">
نمای کلی سیستم
</MudNavLink>
}
<MudDivider Class="my-2" />
<MudText Class="nav-menu__title" Typo="Typo.subtitle2">کمیسیون و شبکه</MudText>
@@ -83,47 +87,68 @@
<AuthorizeView Roles="Administrator">
<Authorized>
@if (CanManagePackages)
{
<MudNavLink Match="NavLinkMatch.Prefix"
Href="@(RouteConstance.Package)"
Icon="@Icons.Material.Filled.Inventory2">
مدیریت پکیج
</MudNavLink>
}
@if (CanManageProducts)
{
<MudNavLink Match="NavLinkMatch.Prefix"
Href="@(RouteConstance.Products)"
Icon="@Icons.Material.Filled.ShoppingBag">
مدیریت محصول
</MudNavLink>
}
@if (CanViewOrders)
{
<MudNavLink Match="NavLinkMatch.Prefix"
Href="@(RouteConstance.Orders)"
Icon="@Icons.Material.Filled.ReceiptLong">
سفارش‌ها
</MudNavLink>
}
@if (CanManageCategories)
{
<MudNavLink Match="NavLinkMatch.Prefix"
Href="@(RouteConstance.Category)"
Icon="@Icons.Material.Filled.Category">
مدیریت دسته‌بندی
</MudNavLink>
}
@if (CanManageTags)
{
<MudNavLink Match="NavLinkMatch.Prefix"
Href="/tags"
Icon="@Icons.Material.Filled.Label">
مدیریت تگ‌ها
</MudNavLink>
}
@if (CanManageUsers)
{
<MudNavLink Match="NavLinkMatch.Prefix"
Href="@(RouteConstance.UserPage)"
Icon="@Icons.Material.Filled.People">
مدیریت کاربر
</MudNavLink>
}
@if (CanManageRoles)
{
<MudNavLink Match="NavLinkMatch.Prefix"
Href="@(RouteConstance.Role)"
Icon="@Icons.Material.Filled.AdminPanelSettings">
مدیریت نقش
</MudNavLink>
}
</Authorized>
</AuthorizeView>
@@ -132,6 +157,8 @@
<AuthorizeView Roles="Administrator">
<Authorized>
@if (CanManageDiscountShop)
{
<MudNavGroup Title="فروشگاه تخفیفی" Icon="@Icons.Material.Filled.Discount" Expanded="false">
<MudNavLink Match="NavLinkMatch.Prefix"
Href="/discount-products"
@@ -157,12 +184,16 @@
گزارش فروش
</MudNavLink>
</MudNavGroup>
}
@if (CanManagePublicMessages)
{
<MudNavLink Match="NavLinkMatch.Prefix"
Href="/public-messages"
Icon="@Icons.Material.Filled.Campaign">
پیام‌های عمومی
</MudNavLink>
}
</Authorized>
</AuthorizeView>
@@ -171,23 +202,32 @@
<AuthorizeView Roles="Administrator">
<Authorized>
@if (CanViewSystemAlerts)
{
<MudNavLink Match="NavLinkMatch.Prefix"
Href="/system/alerts"
Icon="@Icons.Material.Filled.NotificationsActive">
مدیریت هشدارها
</MudNavLink>
}
@if (CanViewSystemHealth)
{
<MudNavLink Match="NavLinkMatch.Prefix"
Href="/system/health"
Icon="@Icons.Material.Filled.MonitorHeart">
سلامت سیستم
</MudNavLink>
}
@if (CanManageSystemConfiguration)
{
<MudNavLink Match="NavLinkMatch.Prefix"
Href="/system/configuration"
Icon="@Icons.Material.Filled.Tune">
تنظیمات سیستم
</MudNavLink>
}
</Authorized>
</AuthorizeView>
@@ -215,4 +255,50 @@
await LocalStorageService.RemoveItemAsync("AuthToken");
Navigation.NavigateTo("/Login");
}
private bool _initialized;
private bool CanViewDashboard;
private bool CanManagePackages;
private bool CanManageProducts;
private bool CanViewOrders;
private bool CanManageCategories;
private bool CanManageTags;
private bool CanManageUsers;
private bool CanManageRoles;
private bool CanManageDiscountShop;
private bool CanManagePublicMessages;
private bool CanViewSystemAlerts;
private bool CanViewSystemHealth;
private bool CanManageSystemConfiguration;
protected override async Task OnInitializedAsync()
{
await InitializePermissionsAsync();
}
private async Task InitializePermissionsAsync()
{
if (_initialized)
{
return;
}
CanViewDashboard = await AuthorizationService.HasPermissionAsync("dashboard.view");
CanManagePackages = await AuthorizationService.HasPermissionAsync("packages.manage");
CanManageProducts = await AuthorizationService.HasPermissionAsync("products.view");
CanViewOrders = await AuthorizationService.HasPermissionAsync("orders.view");
CanManageCategories = await AuthorizationService.HasPermissionAsync("categories.manage");
CanManageTags = await AuthorizationService.HasPermissionAsync("tags.manage");
CanManageUsers = await AuthorizationService.HasPermissionAsync("users.view");
CanManageRoles = await AuthorizationService.HasPermissionAsync("roles.manage");
CanManageDiscountShop = await AuthorizationService.HasPermissionAsync("discountshop.manage");
CanManagePublicMessages = await AuthorizationService.HasPermissionAsync("publicmessages.view");
CanViewSystemAlerts = await AuthorizationService.HasPermissionAsync("system.alerts.view");
CanViewSystemHealth = await AuthorizationService.HasPermissionAsync("system.health.view");
CanManageSystemConfiguration = await AuthorizationService.HasPermissionAsync("settings.manage_configuration");
_initialized = true;
StateHasChanged();
}
}