feat: Add network tree visualization and Persian date service
All checks were successful
Build and Deploy / build (push) Successful in 2m39s
All checks were successful
Build and Deploy / build (push) Successful in 2m39s
This commit is contained in:
@@ -116,13 +116,13 @@
|
|||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.Category.Protobuf" Version="0.0.4" />
|
<PackageReference Include="Foursat.BackOffice.BFF.Category.Protobuf" Version="0.0.4" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.ClubMembership.Protobuf" Version="0.0.3" />
|
<PackageReference Include="Foursat.BackOffice.BFF.ClubMembership.Protobuf" Version="0.0.6" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.Commission.Protobuf" Version="0.0.3" />
|
<PackageReference Include="Foursat.BackOffice.BFF.Commission.Protobuf" Version="0.0.7" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.Common.Protobuf" Version="0.0.2" />
|
<PackageReference Include="Foursat.BackOffice.BFF.Common.Protobuf" Version="0.0.2" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.Configuration.Protobuf" Version="1.0.3" />
|
<PackageReference Include="Foursat.BackOffice.BFF.Configuration.Protobuf" Version="1.0.6" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.DiscountCategory.Protobuf" Version="0.0.2" />
|
<PackageReference Include="Foursat.BackOffice.BFF.DiscountCategory.Protobuf" Version="0.0.2" />
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.ManualPayment.Protobuf" Version="0.0.2" />
|
<PackageReference Include="Foursat.BackOffice.BFF.ManualPayment.Protobuf" Version="0.0.2" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.NetworkMembership.Protobuf" Version="0.0.2" />
|
<PackageReference Include="Foursat.BackOffice.BFF.NetworkMembership.Protobuf" Version="0.0.7" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.Otp.Protobuf" Version="0.0.112" />
|
<PackageReference Include="Foursat.BackOffice.BFF.Otp.Protobuf" Version="0.0.112" />
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@
|
|||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.Tag.Protobuf" Version="0.0.2" />
|
<PackageReference Include="Foursat.BackOffice.BFF.Tag.Protobuf" Version="0.0.2" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.User.Protobuf" Version="0.0.112" />
|
<PackageReference Include="Foursat.BackOffice.BFF.User.Protobuf" Version="0.0.113" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.UserRole.Protobuf" Version="0.0.112" />
|
<PackageReference Include="Foursat.BackOffice.BFF.UserRole.Protobuf" Version="0.0.112" />
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.UserAddress.Protobuf" Version="0.0.112" />
|
<PackageReference Include="Foursat.BackOffice.BFF.UserAddress.Protobuf" Version="0.0.112" />
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ using BackOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
|||||||
using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
||||||
using BackOffice.BFF.UserRole.Protobuf.Protos.UserRole;
|
using BackOffice.BFF.UserRole.Protobuf.Protos.UserRole;
|
||||||
using BackOffice.BFF.Category.Protobuf.Protos.Category;
|
using BackOffice.BFF.Category.Protobuf.Protos.Category;
|
||||||
using BackOffice.BFF.Commission.Protobuf;
|
using Foursat.BackOffice.BFF.Commission.Protos;
|
||||||
using BackOffice.BFF.NetworkMembership.Protobuf;
|
using Foursat.BackOffice.BFF.ClubMembership.Protos;
|
||||||
|
using Foursat.BackOffice.BFF.Configuration.Protos;
|
||||||
|
using Foursat.BackOffice.BFF.NetworkMembership.Protos;
|
||||||
|
using Foursat.BackOffice.BFF.Health.Protobuf;
|
||||||
|
|
||||||
// TODO: Create these proto projects - temporarily disabled
|
// TODO: Create these proto projects - temporarily disabled
|
||||||
// using BackOffice.BFF.DiscountProduct.Protobuf.Protos.DiscountProduct;
|
// using BackOffice.BFF.DiscountProduct.Protobuf.Protos.DiscountProduct;
|
||||||
@@ -32,9 +35,9 @@ using Microsoft.AspNetCore.Components.Authorization;
|
|||||||
using MudBlazor.Services;
|
using MudBlazor.Services;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Foursat.BackOffice.BFF.ClubMembership.Protobuf;
|
|
||||||
using Foursat.BackOffice.BFF.Configuration.Protobuf;
|
|
||||||
using Foursat.BackOffice.BFF.Health.Protobuf;
|
using Foursat.BackOffice.BFF.Health.Protobuf;
|
||||||
|
using Foursat.BackOffice.BFF.NetworkMembership.Protos;
|
||||||
|
|
||||||
|
|
||||||
namespace Microsoft.Extensions.DependencyInjection;
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -60,6 +63,9 @@ public static class ConfigureServices
|
|||||||
services.AddMudServices();
|
services.AddMudServices();
|
||||||
services.AddGrpcServices(configuration);
|
services.AddGrpcServices(configuration);
|
||||||
|
|
||||||
|
// Persian DateTime Service
|
||||||
|
services.AddSingleton<BackOffice.Services.IPersianDateTimeService, BackOffice.Services.PersianDateTimeService>();
|
||||||
|
|
||||||
// Application Services
|
// Application Services
|
||||||
services.AddScoped<BackOffice.Services.Authorization.IAuthorizationService, BackOffice.Services.Authorization.AuthorizationService>();
|
services.AddScoped<BackOffice.Services.Authorization.IAuthorizationService, BackOffice.Services.Authorization.AuthorizationService>();
|
||||||
// TODO: Re-enable when proto projects are created
|
// TODO: Re-enable when proto projects are created
|
||||||
|
|||||||
15
src/BackOffice/Pages/AutoComplete/UserAutoComplete.razor
Normal file
15
src/BackOffice/Pages/AutoComplete/UserAutoComplete.razor
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
@using BackOffice.BFF.User.Protobuf.Protos.User
|
||||||
|
|
||||||
|
<MudAutocomplete T="GetAllUserByFilterResponseModel"
|
||||||
|
Label="@Label"
|
||||||
|
Value="@_item"
|
||||||
|
DebounceInterval="500"
|
||||||
|
ValueChanged="@((e) => OnSelected(e))"
|
||||||
|
ToStringFunc="@(e=> e == null ? null : $"{e.FirstName} {e.LastName} ({e.Mobile})")"
|
||||||
|
SearchFunc="@Search"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Clearable="true"
|
||||||
|
ShowProgressIndicator="true"
|
||||||
|
CoerceText="false"
|
||||||
|
ResetValueOnEmptyText="true"
|
||||||
|
OnClearButtonClick="()=> OnSelected(null)"/>
|
||||||
89
src/BackOffice/Pages/AutoComplete/UserAutoComplete.razor.cs
Normal file
89
src/BackOffice/Pages/AutoComplete/UserAutoComplete.razor.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
using BackOffice.BFF.User.Protobuf.Protos.User;
|
||||||
|
using Grpc.Core;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.AutoComplete;
|
||||||
|
|
||||||
|
public partial class UserAutoComplete
|
||||||
|
{
|
||||||
|
[Inject] public UserContract.UserContractClient UserContract { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public string Label { get; set; } = "انتخاب کاربر";
|
||||||
|
[Parameter] public long? SelectedUserId { get; set; }
|
||||||
|
[Parameter] public EventCallback<long?> SelectedUserIdChanged { get; set; }
|
||||||
|
|
||||||
|
private GetAllUserByFilterResponseModel _item;
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
if (SelectedUserId.HasValue && SelectedUserId.Value > 0 && _item?.Id != SelectedUserId.Value)
|
||||||
|
{
|
||||||
|
await LoadUser(SelectedUserId.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<GetAllUserByFilterResponseModel>> Search(string value, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value) || value.Length < 2)
|
||||||
|
return new List<GetAllUserByFilterResponseModel>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new GetAllUserByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new() { PageNumber = 1, PageSize = 100 },
|
||||||
|
Filter = new()
|
||||||
|
{
|
||||||
|
|
||||||
|
SearchText = value,
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await UserContract.GetAllUserByFilterAsync(request, cancellationToken: cancellationToken);
|
||||||
|
return response.Models?.ToList() ?? new List<GetAllUserByFilterResponseModel>();
|
||||||
|
}
|
||||||
|
catch (RpcException ex) when (ex.StatusCode == StatusCode.Cancelled)
|
||||||
|
{
|
||||||
|
return new List<GetAllUserByFilterResponseModel>();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new List<GetAllUserByFilterResponseModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnSelected(GetAllUserByFilterResponseModel selected)
|
||||||
|
{
|
||||||
|
_item = selected;
|
||||||
|
var userId = selected?.Id;
|
||||||
|
SelectedUserId = userId;
|
||||||
|
await SelectedUserIdChanged.InvokeAsync(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadUser(long userId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new GetAllUserByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new() { PageNumber = 1, PageSize = 1 },
|
||||||
|
Filter = new()
|
||||||
|
{
|
||||||
|
Id = userId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await UserContract.GetAllUserByFilterAsync(request);
|
||||||
|
|
||||||
|
if (response.Models != null && response.Models.Count > 0)
|
||||||
|
{
|
||||||
|
_item = response.Models[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Silent fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
@using Google.Protobuf.WellKnownTypes
|
@using Google.Protobuf.WellKnownTypes
|
||||||
@using BackOffice.Pages.Club.Components
|
@using BackOffice.Pages.Club.Components
|
||||||
@using Foursat.BackOffice.BFF.ClubMembership.Protobuf
|
@using Foursat.BackOffice.BFF.ClubMembership.Protos
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||||
<MudText Typo="Typo.h4" Class="mb-4">مدیریت اعضای باشگاه</MudText>
|
<MudText Typo="Typo.h4" Class="mb-4">مدیریت اعضای باشگاه</MudText>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Components;
|
|||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
using BackOffice.Pages.Club.Components;
|
using BackOffice.Pages.Club.Components;
|
||||||
using Foursat.BackOffice.BFF.ClubMembership.Protobuf;
|
using Foursat.BackOffice.BFF.ClubMembership.Protos;
|
||||||
|
|
||||||
namespace BackOffice.Pages.Club;
|
namespace BackOffice.Pages.Club;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@using Foursat.BackOffice.BFF.ClubMembership.Protobuf
|
@using Foursat.BackOffice.BFF.ClubMembership.Protos
|
||||||
|
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@using Foursat.BackOffice.BFF.ClubMembership.Protobuf
|
@using Foursat.BackOffice.BFF.ClubMembership.Protos
|
||||||
|
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@using Foursat.BackOffice.BFF.ClubMembership.Protobuf
|
@using Foursat.BackOffice.BFF.ClubMembership.Protos
|
||||||
|
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@page "/club/statistics"
|
@page "/club/statistics"
|
||||||
|
|
||||||
@using MudBlazor
|
@using MudBlazor
|
||||||
@using Foursat.BackOffice.BFF.ClubMembership.Protobuf
|
@using Foursat.BackOffice.BFF.ClubMembership.Protos
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||||
<MudText Typo="Typo.h4" GutterBottom="true">آمار باشگاه</MudText>
|
<MudText Typo="Typo.h4" GutterBottom="true">آمار باشگاه</MudText>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@using BackOffice.BFF.Commission.Protobuf
|
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||||
|
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@page "/commission/dashboard"
|
@page "/commission/dashboard"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
@using BackOffice.BFF.Commission.Protobuf
|
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||||
@using MudBlazor
|
@using MudBlazor
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
<MudItem xs="12" sm="6" md="3">
|
<MudItem xs="12" sm="6" md="3">
|
||||||
<MudCard Elevation="2">
|
<MudCard Elevation="2">
|
||||||
<MudCardContent>
|
<MudCardContent>
|
||||||
<MudText Typo="Typo.h6" Color="Color.Primary">Pool هفتگی</MudText>
|
<MudText Typo="Typo.h6" Color="Color.Primary">استخر هفتگی</MudText>
|
||||||
<MudText Typo="Typo.h4">@(_poolData?.TotalPoolAmount.ToString("N0") ?? "0") ریال</MudText>
|
<MudText Typo="Typo.h4">@(_poolData?.TotalPoolAmount.ToString("N0") ?? "0") ریال</MudText>
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">هفته @(_currentWeekNumber)</MudText>
|
<MudText Typo="Typo.body2" Color="Color.Secondary">هفته @(_currentWeekNumberPersian)</MudText>
|
||||||
</MudCardContent>
|
</MudCardContent>
|
||||||
</MudCard>
|
</MudCard>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
@@ -28,9 +28,9 @@
|
|||||||
<MudItem xs="12" sm="6" md="3">
|
<MudItem xs="12" sm="6" md="3">
|
||||||
<MudCard Elevation="2">
|
<MudCard Elevation="2">
|
||||||
<MudCardContent>
|
<MudCardContent>
|
||||||
<MudText Typo="Typo.h6" Color="Color.Success">تعداد بالانسها</MudText>
|
<MudText Typo="Typo.h6" Color="Color.Success">تعداد تعادلها</MudText>
|
||||||
<MudText Typo="Typo.h4">@(_poolData?.TotalBalances.ToString("N0") ?? "0")</MudText>
|
<MudText Typo="Typo.h4">@(_poolData?.TotalBalances.ToString("N0") ?? "0")</MudText>
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">مجموع بالانسهای فعال</MudText>
|
<MudText Typo="Typo.body2" Color="Color.Secondary">مجموع تعادل های فعال</MudText>
|
||||||
</MudCardContent>
|
</MudCardContent>
|
||||||
</MudCard>
|
</MudCard>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
@@ -38,9 +38,9 @@
|
|||||||
<MudItem xs="12" sm="6" md="3">
|
<MudItem xs="12" sm="6" md="3">
|
||||||
<MudCard Elevation="2">
|
<MudCard Elevation="2">
|
||||||
<MudCardContent>
|
<MudCardContent>
|
||||||
<MudText Typo="Typo.h6" Color="Color.Warning">ارزش هر بالانس</MudText>
|
<MudText Typo="Typo.h6" Color="Color.Warning">ارزش هر تعادل</MudText>
|
||||||
<MudText Typo="Typo.h4">@(_poolData?.ValuePerBalance.ToString("N0") ?? "0") ریال</MudText>
|
<MudText Typo="Typo.h4">@(_poolData?.ValuePerBalance.ToString("N0") ?? "0") ریال</MudText>
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">قیمت واحد بالانس</MudText>
|
<MudText Typo="Typo.body2" Color="Color.Secondary">قیمت واحد تعادل</MudText>
|
||||||
</MudCardContent>
|
</MudCardContent>
|
||||||
</MudCard>
|
</MudCard>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
@@ -57,7 +57,9 @@
|
|||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
||||||
@if (_poolData?.CalculatedAt != null)
|
@if (_poolData?.CalculatedAt != null)
|
||||||
{
|
{
|
||||||
@($"در تاریخ {_poolData.CalculatedAt.ToDateTime().ToLocalTime():yyyy/MM/dd}")
|
var calculatedDate = _poolData.CalculatedAt.ToDateTime().ToLocalTime();
|
||||||
|
var persianDate = PersianDateTime.ConvertToPersianDateTime(calculatedDate);
|
||||||
|
@($"در تاریخ {persianDate}")
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -73,12 +75,90 @@
|
|||||||
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||||||
<MudText Typo="Typo.h6">انتخاب هفته:</MudText>
|
<MudText Typo="Typo.h6">انتخاب هفته:</MudText>
|
||||||
<MudTextField @bind-Value="_currentWeekNumber"
|
|
||||||
Label="شماره هفته"
|
@if (_allWeeks.Any())
|
||||||
Variant="Variant.Outlined"
|
{
|
||||||
Margin="Margin.Dense"
|
<MudSelect T="string"
|
||||||
HelperText="فرمت: YYYY-Www (مثلاً 2025-W48)"
|
Value="_currentWeekNumber"
|
||||||
Style="max-width: 200px;" />
|
Label="شماره هفته"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
AnchorOrigin="Origin.BottomCenter"
|
||||||
|
Style="min-width: 400px;"
|
||||||
|
ValueChanged="OnWeekChanged">
|
||||||
|
|
||||||
|
@if (_availableWeeks?.CurrentWeek != null)
|
||||||
|
{
|
||||||
|
<MudSelectItem Value="@_availableWeeks.CurrentWeek.WeekNumber">
|
||||||
|
<MudText>
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.Today" Size="Size.Small" Class="ml-2" />
|
||||||
|
@_availableWeeks.CurrentWeek.DisplayText
|
||||||
|
</MudText>
|
||||||
|
</MudSelectItem>
|
||||||
|
<MudDivider />
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (_availableWeeks?.CalculatedWeeks?.Any() == true)
|
||||||
|
{
|
||||||
|
<MudListSubheader>هفتههای محاسبه شده</MudListSubheader>
|
||||||
|
@foreach (var week in _availableWeeks.CalculatedWeeks)
|
||||||
|
{
|
||||||
|
<MudSelectItem Value="@week.WeekNumber">
|
||||||
|
<MudText>
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Small" Class="ml-2" />
|
||||||
|
@week.DisplayText
|
||||||
|
@if (week.TotalPoolAmount > 0)
|
||||||
|
{
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="Color.Success" Class="mr-2">
|
||||||
|
@week.TotalPoolAmount.ToString("N0") ریال
|
||||||
|
</MudChip>
|
||||||
|
}
|
||||||
|
</MudText>
|
||||||
|
</MudSelectItem>
|
||||||
|
}
|
||||||
|
<MudDivider />
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (_availableWeeks?.PendingWeeks?.Any() == true)
|
||||||
|
{
|
||||||
|
<MudListSubheader>هفتههای محاسبه نشده</MudListSubheader>
|
||||||
|
@foreach (var week in _availableWeeks.PendingWeeks)
|
||||||
|
{
|
||||||
|
<MudSelectItem Value="@week.WeekNumber">
|
||||||
|
<MudText>
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.AccessTime" Color="Color.Warning" Size="Size.Small" Class="ml-2" />
|
||||||
|
@week.DisplayText
|
||||||
|
</MudText>
|
||||||
|
</MudSelectItem>
|
||||||
|
}
|
||||||
|
<MudDivider />
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (_availableWeeks?.FutureWeeks?.Any() == true)
|
||||||
|
{
|
||||||
|
<MudListSubheader>هفتههای آینده</MudListSubheader>
|
||||||
|
@foreach (var week in _availableWeeks.FutureWeeks)
|
||||||
|
{
|
||||||
|
<MudSelectItem Value="@week.WeekNumber">
|
||||||
|
<MudText>
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.CalendarMonth" Color="Color.Info" Size="Size.Small" Class="ml-2" />
|
||||||
|
@week.DisplayText
|
||||||
|
</MudText>
|
||||||
|
</MudSelectItem>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudTextField @bind-Value="_currentWeekNumber"
|
||||||
|
Label="شماره هفته"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
HelperText="فرمت: YYYY-Www (مثلاً 2025-W48)"
|
||||||
|
Style="max-width: 200px;" />
|
||||||
|
}
|
||||||
|
|
||||||
<MudButton Variant="Variant.Filled"
|
<MudButton Variant="Variant.Filled"
|
||||||
Color="Color.Primary"
|
Color="Color.Primary"
|
||||||
OnClick="LoadPoolData"
|
OnClick="LoadPoolData"
|
||||||
@@ -92,11 +172,11 @@
|
|||||||
@if (_poolData != null)
|
@if (_poolData != null)
|
||||||
{
|
{
|
||||||
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
||||||
<MudText Typo="Typo.h6" Class="mb-3">جزئیات Pool</MudText>
|
<MudText Typo="Typo.h6" Class="mb-3">جزئیات استخر</MudText>
|
||||||
<MudSimpleTable Hover="true" Dense="true">
|
<MudSimpleTable Hover="true" Dense="true">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>شناسه Pool:</strong></td>
|
<td><strong>شناسه استخر:</strong></td>
|
||||||
<td>@_poolData.Id</td>
|
<td>@_poolData.Id</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -104,15 +184,15 @@
|
|||||||
<td>@_poolData.WeekNumber</td>
|
<td>@_poolData.WeekNumber</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>مجموع Pool:</strong></td>
|
<td><strong>مجموع استخر:</strong></td>
|
||||||
<td>@_poolData.TotalPoolAmount.ToString("N0") ریال</td>
|
<td>@_poolData.TotalPoolAmount.ToString("N0") ریال</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>تعداد بالانسها:</strong></td>
|
<td><strong>تعداد تعادل:</strong></td>
|
||||||
<td>@_poolData.TotalBalances</td>
|
<td>@_poolData.TotalBalances</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>ارزش هر بالانس:</strong></td>
|
<td><strong>ارزش هر تعادل:</strong></td>
|
||||||
<td>@_poolData.ValuePerBalance.ToString("N0") ریال</td>
|
<td>@_poolData.ValuePerBalance.ToString("N0") ریال</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -1,22 +1,71 @@
|
|||||||
using BackOffice.BFF.Commission.Protobuf;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Foursat.BackOffice.BFF.Commission.Protos;
|
||||||
|
using BackOffice.Services;
|
||||||
|
|
||||||
namespace BackOffice.Pages.Commission;
|
namespace BackOffice.Pages.Commission;
|
||||||
|
|
||||||
public partial class Dashboard
|
public partial class Dashboard
|
||||||
{
|
{
|
||||||
[Inject] public CommissionContract.CommissionContractClient CommissionContract { get; set; }
|
[Inject] public CommissionContract.CommissionContractClient CommissionContract { get; set; }
|
||||||
|
[Inject] public IPersianDateTimeService PersianDateTime { get; set; }
|
||||||
|
|
||||||
private bool _isLoading = true;
|
private bool _isLoading = true;
|
||||||
private string _currentWeekNumber = string.Empty;
|
private string _currentWeekNumber = string.Empty;
|
||||||
|
private string _currentWeekNumberPersian = string.Empty; // هفته به شمسی
|
||||||
private GetWeeklyCommissionPoolResponse? _poolData;
|
private GetWeeklyCommissionPoolResponse? _poolData;
|
||||||
|
|
||||||
|
// Available weeks data
|
||||||
|
private Foursat.BackOffice.BFF.Commission.Protos.GetAvailableWeeksResponse? _availableWeeks;
|
||||||
|
private List<Foursat.BackOffice.BFF.Commission.Protos.WeekInfo> _allWeeks = new();
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
// محاسبه شماره هفته جاری
|
// بارگذاری لیست هفتههای قابل انتخاب
|
||||||
|
await LoadAvailableWeeks();
|
||||||
|
|
||||||
|
// محاسبه شماره هفته جاری (میلادی برای API)
|
||||||
_currentWeekNumber = GetCurrentWeekNumber();
|
_currentWeekNumber = GetCurrentWeekNumber();
|
||||||
|
// تبدیل به شمسی برای نمایش
|
||||||
|
_currentWeekNumberPersian = PersianDateTime.ConvertWeekNumberToPersian(_currentWeekNumber);
|
||||||
|
|
||||||
|
await LoadPoolData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadAvailableWeeks()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new GetAvailableWeeksRequest
|
||||||
|
{
|
||||||
|
FutureWeeksCount = 4,
|
||||||
|
PastWeeksCount = 12
|
||||||
|
};
|
||||||
|
|
||||||
|
_availableWeeks = await CommissionContract.GetAvailableWeeksAsync(request);
|
||||||
|
|
||||||
|
// ترکیب همه هفتهها برای dropdown
|
||||||
|
_allWeeks.Clear();
|
||||||
|
if (_availableWeeks.CurrentWeek != null)
|
||||||
|
_allWeeks.Add(_availableWeeks.CurrentWeek);
|
||||||
|
_allWeeks.AddRange(_availableWeeks.CalculatedWeeks);
|
||||||
|
_allWeeks.AddRange(_availableWeeks.PendingWeeks);
|
||||||
|
_allWeeks.AddRange(_availableWeeks.FutureWeeks);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا در بارگذاری لیست هفتهها: {ex.Message}", Severity.Warning);
|
||||||
|
// Fallback to current week
|
||||||
|
_currentWeekNumber = GetCurrentWeekNumber();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnWeekChanged(string weekNumber)
|
||||||
|
{
|
||||||
|
_currentWeekNumber = weekNumber;
|
||||||
|
_currentWeekNumberPersian = PersianDateTime.ConvertWeekNumberToPersian(weekNumber);
|
||||||
await LoadPoolData();
|
await LoadPoolData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +82,7 @@ public partial class Dashboard
|
|||||||
};
|
};
|
||||||
|
|
||||||
_poolData = await CommissionContract.GetWeeklyCommissionPoolAsync(request);
|
_poolData = await CommissionContract.GetWeeklyCommissionPoolAsync(request);
|
||||||
|
Console.WriteLine(JsonSerializer.Serialize(_poolData));
|
||||||
Snackbar.Add($"اطلاعات Pool هفته {_currentWeekNumber} بارگذاری شد", Severity.Success);
|
Snackbar.Add($"اطلاعات Pool هفته {_currentWeekNumber} بارگذاری شد", Severity.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -59,9 +108,16 @@ public partial class Dashboard
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// TODO: Call CalculateWeeklyBalances, CalculateWeeklyCommissionPool, ProcessUserPayouts
|
await CommissionContract.TriggerWeeklyCalculationAsync(new TriggerWeeklyCalculationRequest()
|
||||||
|
{
|
||||||
|
WeekNumber = _currentWeekNumber,
|
||||||
|
ForceRecalculate = true,
|
||||||
|
SkipBalances = false,
|
||||||
|
SkipPayouts = false,
|
||||||
|
SkipPool = false,
|
||||||
|
});
|
||||||
Snackbar.Add("محاسبه با موفقیت آغاز شد. این عملیات ممکن است چند دقیقه طول بکشد.", Severity.Info);
|
Snackbar.Add("محاسبه با موفقیت آغاز شد. این عملیات ممکن است چند دقیقه طول بکشد.", Severity.Info);
|
||||||
|
|
||||||
// Reload data after a delay
|
// Reload data after a delay
|
||||||
await Task.Delay(2000);
|
await Task.Delay(2000);
|
||||||
await LoadPoolData();
|
await LoadPoolData();
|
||||||
@@ -77,7 +133,7 @@ public partial class Dashboard
|
|||||||
{
|
{
|
||||||
var today = DateTime.Now;
|
var today = DateTime.Now;
|
||||||
var calendar = CultureInfo.CurrentCulture.Calendar;
|
var calendar = CultureInfo.CurrentCulture.Calendar;
|
||||||
var weekOfYear = calendar.GetWeekOfYear(today, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
|
var weekOfYear = calendar.GetWeekOfYear(today, CalendarWeekRule.FirstDay, DayOfWeek.Saturday);
|
||||||
return $"{today.Year}-W{weekOfYear:D2}";
|
return $"{today.Year}-W{weekOfYear:D2}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
@page "/commission/payouts"
|
@page "/commission/payouts"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
@using BackOffice.BFF.Commission.Protobuf
|
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||||
@using Google.Protobuf.WellKnownTypes
|
@using Google.Protobuf.WellKnownTypes
|
||||||
@using MudBlazor
|
@using MudBlazor
|
||||||
|
|
||||||
@@ -61,7 +61,14 @@
|
|||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
|
|
||||||
<PropertyColumn Property="x => x.WeekNumber" Title="هفته" />
|
<PropertyColumn Property="x => x.WeekNumber" Title="هفته">
|
||||||
|
<CellTemplate>
|
||||||
|
@{
|
||||||
|
var persianWeek = PersianDateTime.ConvertWeekNumberToPersian(context.Item.WeekNumber);
|
||||||
|
}
|
||||||
|
<MudText Typo="Typo.body2">@persianWeek</MudText>
|
||||||
|
</CellTemplate>
|
||||||
|
</PropertyColumn>
|
||||||
|
|
||||||
<PropertyColumn Property="x => x.BalancesEarned" Title="بالانسها">
|
<PropertyColumn Property="x => x.BalancesEarned" Title="بالانسها">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@@ -97,7 +104,10 @@
|
|||||||
|
|
||||||
<PropertyColumn Property="x => x.Created" Title="تاریخ ایجاد">
|
<PropertyColumn Property="x => x.Created" Title="تاریخ ایجاد">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@context.Item.Created.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm")
|
@{
|
||||||
|
var persianDate = PersianDateTime.ConvertToPersianDateTime(context.Item.Created.ToDateTime().ToLocalTime());
|
||||||
|
}
|
||||||
|
@persianDate
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
using BackOffice.BFF.Commission.Protobuf;
|
using Foursat.BackOffice.BFF.Commission.Protos;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
using BackOffice.Pages.Commission.Components;
|
using BackOffice.Pages.Commission.Components;
|
||||||
|
using BackOffice.Services;
|
||||||
|
|
||||||
namespace BackOffice.Pages.Commission;
|
namespace BackOffice.Pages.Commission;
|
||||||
|
|
||||||
public partial class UserPayouts
|
public partial class UserPayouts
|
||||||
{
|
{
|
||||||
[Inject] public CommissionContract.CommissionContractClient CommissionContract { get; set; }
|
[Inject] public CommissionContract.CommissionContractClient CommissionContract { get; set; }
|
||||||
|
[Inject] public IPersianDateTimeService PersianDateTime { get; set; }
|
||||||
|
|
||||||
private MudDataGrid<UserCommissionPayoutModel> _gridData;
|
private MudDataGrid<UserCommissionPayoutModel> _gridData;
|
||||||
private long? _filterUserId;
|
private long? _filterUserId;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@page "/commission/reports"
|
@page "/commission/reports"
|
||||||
|
|
||||||
@using MudBlazor
|
@using MudBlazor
|
||||||
@using BackOffice.BFF.Commission.Protobuf
|
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||||
@using Google.Protobuf.WellKnownTypes
|
@using Google.Protobuf.WellKnownTypes
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
@using MudBlazor
|
@using MudBlazor
|
||||||
@using BackOffice.BFF.Commission.Protobuf
|
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||||
@using Google.Protobuf.WellKnownTypes
|
@using Google.Protobuf.WellKnownTypes
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using System.Text
|
@using System.Text
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@page "/commission/withdrawals"
|
@page "/commission/withdrawals"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
@using BackOffice.BFF.Commission.Protobuf
|
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||||
<MudText Typo="Typo.h4" Class="mb-4">درخواستهای برداشت</MudText>
|
<MudText Typo="Typo.h4" Class="mb-4">درخواستهای برداشت</MudText>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using BackOffice.BFF.Commission.Protobuf;
|
using Foursat.BackOffice.BFF.Commission.Protos;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
using GrpcWithdrawalRequestModel = BackOffice.BFF.Commission.Protobuf.WithdrawalRequestModel;
|
using GrpcWithdrawalRequestModel = Foursat.BackOffice.BFF.Commission.Protos.WithdrawalRequestModel;
|
||||||
using GrpcGetWithdrawalRequestsRequest = BackOffice.BFF.Commission.Protobuf.GetWithdrawalRequestsRequest;
|
using GrpcGetWithdrawalRequestsRequest = Foursat.BackOffice.BFF.Commission.Protos.GetWithdrawalRequestsRequest;
|
||||||
|
|
||||||
namespace BackOffice.Pages.Commission;
|
namespace BackOffice.Pages.Commission;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
@page "/dashboard/overview"
|
@page "/dashboard/overview"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
@using BackOffice.BFF.Commission.Protobuf
|
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||||
@using BackOffice.BFF.NetworkMembership.Protobuf
|
@using Foursat.BackOffice.BFF.ClubMembership.Protos
|
||||||
@using Foursat.BackOffice.BFF.ClubMembership.Protobuf
|
|
||||||
@using Google.Protobuf.WellKnownTypes
|
@using Google.Protobuf.WellKnownTypes
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||||
@@ -301,7 +300,7 @@
|
|||||||
var daysOffset = DayOfWeek.Monday - jan1.DayOfWeek;
|
var daysOffset = DayOfWeek.Monday - jan1.DayOfWeek;
|
||||||
var firstMonday = jan1.AddDays(daysOffset);
|
var firstMonday = jan1.AddDays(daysOffset);
|
||||||
var cal = System.Globalization.CultureInfo.CurrentCulture.Calendar;
|
var cal = System.Globalization.CultureInfo.CurrentCulture.Calendar;
|
||||||
var weekNum = cal.GetWeekOfYear(now, System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
|
var weekNum = cal.GetWeekOfYear(now, System.Globalization.CalendarWeekRule.FirstDay, DayOfWeek.Saturday);
|
||||||
return $"{now.Year}-W{weekNum:D2}";
|
return $"{now.Year}-W{weekNum:D2}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
@page "/network/balances"
|
@page "/network/balances"
|
||||||
|
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
@using MudBlazor
|
@using MudBlazor
|
||||||
@using BackOffice.BFF.Commission.Protobuf
|
@using Foursat.BackOffice.BFF.Commission.Protos
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||||
<MudText Typo="Typo.h4" GutterBottom="true">گزارش موجودیهای هفتگی</MudText>
|
<MudText Typo="Typo.h4" GutterBottom="true">گزارش موجودیهای هفتگی</MudText>
|
||||||
@@ -13,10 +14,12 @@
|
|||||||
<MudCardContent>
|
<MudCardContent>
|
||||||
<MudGrid>
|
<MudGrid>
|
||||||
<MudItem xs="12" md="3">
|
<MudItem xs="12" md="3">
|
||||||
<MudNumericField @bind-Value="_filterUserId"
|
@* <MudNumericField @bind-Value="_filterUserId" *@
|
||||||
Label="شناسه کاربر"
|
@* Label="شناسه کاربر" *@
|
||||||
Variant="Variant.Outlined"
|
@* Variant="Variant.Outlined" *@
|
||||||
Min="0" />
|
@* Min="0" /> *@
|
||||||
|
<UserAutoComplete Label="جستجوی کاربر"
|
||||||
|
@bind-SelectedUserId="_filterUserId" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="3">
|
<MudItem xs="12" md="3">
|
||||||
<MudTextField @bind-Value="_filterWeekNumber"
|
<MudTextField @bind-Value="_filterWeekNumber"
|
||||||
@@ -61,7 +64,7 @@
|
|||||||
</MudButton>
|
</MudButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MudDataGrid T="UserWeeklyBalanceModel"
|
<MudDataGrid T="UserWeeklyBalanceModel" @ref="_grid"
|
||||||
ServerData="@(new Func<GridState<UserWeeklyBalanceModel>, Task<GridData<UserWeeklyBalanceModel>>>(ServerReload))"
|
ServerData="@(new Func<GridState<UserWeeklyBalanceModel>, Task<GridData<UserWeeklyBalanceModel>>>(ServerReload))"
|
||||||
Filterable="true"
|
Filterable="true"
|
||||||
Hover="true">
|
Hover="true">
|
||||||
@@ -149,7 +152,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Inject] public CommissionContract.CommissionContractClient CommissionClient { get; set; }
|
[Inject] public CommissionContract.CommissionContractClient CommissionClient { get; set; }
|
||||||
|
private MudDataGrid <UserWeeklyBalanceModel> _grid { get; set; }
|
||||||
private long? _filterUserId = null;
|
private long? _filterUserId = null;
|
||||||
private string _filterWeekNumber = "";
|
private string _filterWeekNumber = "";
|
||||||
private int? _minBalance = null;
|
private int? _minBalance = null;
|
||||||
@@ -221,7 +224,7 @@
|
|||||||
|
|
||||||
private async Task ApplyFilter()
|
private async Task ApplyFilter()
|
||||||
{
|
{
|
||||||
StateHasChanged();
|
await _grid.ReloadServerData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CalculateTotals(List<UserWeeklyBalanceModel> items)
|
private void CalculateTotals(List<UserWeeklyBalanceModel> items)
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
@page "/network/tree"
|
@page "/network/tree"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
@inject IJSRuntime JS
|
||||||
|
|
||||||
@using BackOffice.BFF.NetworkMembership.Protobuf
|
@using Foursat.BackOffice.BFF.NetworkMembership.Protos
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-4">
|
||||||
<MudText Typo="Typo.h4" Class="mb-4">درخت شبکه</MudText>
|
<MudText Typo="Typo.h4" Class="mb-4">درخت شبکه</MudText>
|
||||||
|
|
||||||
<MudPaper Class="pa-4 mb-4">
|
<MudPaper Class="pa-4 mb-4">
|
||||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">
|
||||||
<MudNumericField @bind-Value="_searchUserId"
|
<div style="max-width: 300px; min-width: 250px;">
|
||||||
Label="شناسه کاربر"
|
<UserAutoComplete Label="جستجوی کاربر"
|
||||||
Variant="Variant.Outlined"
|
@bind-SelectedUserId="_searchUserId" />
|
||||||
Style="max-width: 200px;" />
|
</div>
|
||||||
<MudButton Variant="Variant.Filled"
|
<MudButton Variant="Variant.Filled"
|
||||||
Color="Color.Primary"
|
Color="Color.Primary"
|
||||||
OnClick="LoadTree"
|
OnClick="LoadTree"
|
||||||
@@ -42,8 +44,13 @@
|
|||||||
}
|
}
|
||||||
else if (_treeData != null && _treeData.Nodes.Any())
|
else if (_treeData != null && _treeData.Nodes.Any())
|
||||||
{
|
{
|
||||||
<MudPaper Class="pa-4">
|
<MudPaper Class="pa-4" Style="min-height: 800px;">
|
||||||
<MudDataGrid T="NetworkTreeNodeModel" Items="@_treeData.Nodes" Hover="true" Filterable="true">
|
<div id="network-tree-container" style="width: 100%; height: 800px; overflow: auto;"></div>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
<MudPaper Class="pa-4 mt-4">
|
||||||
|
<MudText Typo="Typo.h6" Class="mb-3">جدول اعضای شبکه</MudText>
|
||||||
|
<MudDataGrid T="NetworkTreeNodeModel" Items="@_treeData.Nodes" Hover="true" Filterable="true" Dense="true">
|
||||||
<Columns>
|
<Columns>
|
||||||
<PropertyColumn Property="x => x.UserId" Title="شناسه کاربر" />
|
<PropertyColumn Property="x => x.UserId" Title="شناسه کاربر" />
|
||||||
<PropertyColumn Property="x => x.UserName" Title="نام کاربر" />
|
<PropertyColumn Property="x => x.UserName" Title="نام کاربر" />
|
||||||
@@ -62,17 +69,28 @@
|
|||||||
|
|
||||||
<PropertyColumn Property="x => x.IsActive" Title="وضعیت">
|
<PropertyColumn Property="x => x.IsActive" Title="وضعیت">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudChip T="string"
|
@if (context.Item.IsActive!=null)
|
||||||
Color="@(context.Item.IsActive ? Color.Success : Color.Error)"
|
{
|
||||||
Size="Size.Small">
|
|
||||||
@(context.Item.IsActive ? "فعال" : "غیرفعال")
|
<MudChip T="string"
|
||||||
|
Color="@((bool)context.Item.IsActive ? Color.Success : Color.Error)"
|
||||||
|
Size="Size.Small">
|
||||||
|
@((bool)context.Item.IsActive ? "فعال" : "غیرفعال")
|
||||||
</MudChip>
|
</MudChip>
|
||||||
|
}
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
|
|
||||||
<PropertyColumn Property="x => x.JoinedAt" Title="تاریخ عضویت">
|
<PropertyColumn Property="x => x.JoinedAt" Title="تاریخ عضویت">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@context.Item.JoinedAt.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd")
|
@if (context.Item.JoinedAt != null)
|
||||||
|
{
|
||||||
|
@context.Item.JoinedAt.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Default">-</MudText>
|
||||||
|
}
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
|
|
||||||
@@ -104,41 +122,84 @@
|
|||||||
[Inject] public NetworkMembershipContract.NetworkMembershipContractClient NetworkContract { get; set; }
|
[Inject] public NetworkMembershipContract.NetworkMembershipContractClient NetworkContract { get; set; }
|
||||||
[Inject] public NavigationManager NavigationManager { get; set; }
|
[Inject] public NavigationManager NavigationManager { get; set; }
|
||||||
|
|
||||||
private long _searchUserId;
|
private long? _searchUserId;
|
||||||
private GetNetworkTreeResponse _treeData;
|
private GetNetworkTreeResponse _treeData;
|
||||||
private bool _isLoading;
|
private bool _isLoading;
|
||||||
private int _totalMembers;
|
private int _totalMembers;
|
||||||
private int _leftCount;
|
private int _leftCount;
|
||||||
private int _rightCount;
|
private int _rightCount;
|
||||||
|
private DotNetObjectReference<NetworkTreeViewer> _dotNetRef;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_dotNetRef = DotNetObjectReference.Create(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
await JS.InvokeVoidAsync("NetworkTreeViewer.setDotNetReference", _dotNetRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LoadTree()
|
private async Task LoadTree()
|
||||||
{
|
{
|
||||||
if (_searchUserId <= 0)
|
if (!_searchUserId.HasValue || _searchUserId.Value <= 0)
|
||||||
{
|
{
|
||||||
Snackbar.Add("لطفاً شناسه کاربر را وارد کنید", Severity.Warning);
|
Snackbar.Add("لطفاً کاربر را انتخاب کنید", Severity.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
|
StateHasChanged(); // Force render to show loading state
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var request = new GetNetworkTreeRequest { RootUserId = _searchUserId };
|
var request = new GetNetworkTreeRequest { UserId = _searchUserId.Value, MaxDepth = 20 };
|
||||||
_treeData = await NetworkContract.GetNetworkTreeAsync(request);
|
_treeData = await NetworkContract.GetNetworkTreeAsync(request);
|
||||||
|
|
||||||
CalculateStats();
|
CalculateStats();
|
||||||
|
|
||||||
|
_isLoading = false;
|
||||||
|
StateHasChanged(); // Render the container first
|
||||||
|
|
||||||
|
await Task.Delay(100); // Wait for DOM to be ready
|
||||||
|
await RenderTree();
|
||||||
|
|
||||||
Snackbar.Add($"درخت بارگذاری شد - {_treeData.Nodes.Count} عضو", Severity.Success);
|
Snackbar.Add($"درخت بارگذاری شد - {_treeData.Nodes.Count} عضو", Severity.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Snackbar.Add($"خطا در بارگذاری درخت: {ex.Message}", Severity.Error);
|
Snackbar.Add($"خطا در بارگذاری درخت: {ex.Message}", Severity.Error);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task RenderTree()
|
||||||
|
{
|
||||||
|
if (_treeData == null || !_treeData.Nodes.Any()) return;
|
||||||
|
|
||||||
|
var jsNodes = _treeData.Nodes.Select(n => new
|
||||||
|
{
|
||||||
|
userId = n.UserId,
|
||||||
|
userName = n.UserName,
|
||||||
|
parentId = n.ParentId,
|
||||||
|
networkLevel = n.NetworkLevel,
|
||||||
|
networkLeg = n.NetworkLeg,
|
||||||
|
isActive = n.IsActive ?? false
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
await JS.InvokeVoidAsync("NetworkTreeViewer.initialize", "network-tree-container", jsNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public async Task OnNodeClicked(long userId)
|
||||||
|
{
|
||||||
|
_searchUserId = userId;
|
||||||
|
await LoadTree();
|
||||||
|
}
|
||||||
|
|
||||||
private void CalculateStats()
|
private void CalculateStats()
|
||||||
{
|
{
|
||||||
if (_treeData == null || !_treeData.Nodes.Any()) return;
|
if (_treeData == null || !_treeData.Nodes.Any()) return;
|
||||||
@@ -152,4 +213,9 @@
|
|||||||
{
|
{
|
||||||
NavigationManager.NavigateTo($"/network/user-info/{userId}");
|
NavigationManager.NavigateTo($"/network/user-info/{userId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_dotNetRef?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@page "/network/statistics"
|
@page "/network/statistics"
|
||||||
|
|
||||||
|
@using Foursat.BackOffice.BFF.NetworkMembership.Protos
|
||||||
@using MudBlazor
|
@using MudBlazor
|
||||||
@using BackOffice.BFF.NetworkMembership.Protobuf
|
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||||
<MudText Typo="Typo.h4" GutterBottom="true">آمار شبکه</MudText>
|
<MudText Typo="Typo.h4" GutterBottom="true">آمار شبکه</MudText>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
@page "/network/user-info/{UserId:long}"
|
@page "/network/user-info/{UserId:long}"
|
||||||
|
@using Foursat.BackOffice.BFF.NetworkMembership.Protos
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
@using BackOffice.BFF.NetworkMembership.Protobuf
|
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-4">
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||||
<MudBreadcrumbs Items="_breadcrumbs" Class="mb-4"></MudBreadcrumbs>
|
<MudBreadcrumbs Items="_breadcrumbs" Class="mb-4"></MudBreadcrumbs>
|
||||||
|
|
||||||
@if (_isLoading)
|
@if (_isLoading)
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
else if (_userInfo != null)
|
else if (_userInfo != null)
|
||||||
{
|
{
|
||||||
<MudGrid>
|
<MudGrid>
|
||||||
|
<!-- اطلاعات کاربر -->
|
||||||
<MudItem xs="12" md="6">
|
<MudItem xs="12" md="6">
|
||||||
<MudCard>
|
<MudCard>
|
||||||
<MudCardHeader>
|
<MudCardHeader>
|
||||||
@@ -33,6 +34,34 @@
|
|||||||
<td><strong>نام کاربر:</strong></td>
|
<td><strong>نام کاربر:</strong></td>
|
||||||
<td>@_userInfo.UserName</td>
|
<td>@_userInfo.UserName</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>موبایل:</strong></td>
|
||||||
|
<td>
|
||||||
|
@_userInfo.Mobile
|
||||||
|
@if (_userInfo.IsMobileVerified)
|
||||||
|
{
|
||||||
|
<MudChip T="string" Color="Color.Success" Size="Size.Small" Icon="@Icons.Material.Filled.Verified">تایید شده</MudChip>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@if (!string.IsNullOrEmpty(_userInfo.Email))
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td><strong>ایمیل:</strong></td>
|
||||||
|
<td>@_userInfo.Email</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(_userInfo.NationalCode))
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td><strong>کد ملی:</strong></td>
|
||||||
|
<td>@_userInfo.NationalCode</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
<tr>
|
||||||
|
<td><strong>کد ارجاع:</strong></td>
|
||||||
|
<td><MudChip T="string" Color="Color.Info" Size="Size.Small">@_userInfo.ReferralCode</MudChip></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>موقعیت:</strong></td>
|
<td><strong>موقعیت:</strong></td>
|
||||||
<td>
|
<td>
|
||||||
@@ -43,13 +72,9 @@
|
|||||||
</MudChip>
|
</MudChip>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td><strong>عمق در درخت:</strong></td>
|
|
||||||
<td>@_userInfo.NetworkLevel</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>تاریخ عضویت:</strong></td>
|
<td><strong>تاریخ عضویت:</strong></td>
|
||||||
<td>@_userInfo.JoinedAt.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm")</td>
|
<td>@PersianDateTime.ConvertToPersianDateTime(_userInfo.JoinedAt.ToDateTime().ToLocalTime())</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</MudSimpleTable>
|
</MudSimpleTable>
|
||||||
@@ -57,6 +82,7 @@
|
|||||||
</MudCard>
|
</MudCard>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
|
<!-- ساختار شبکه -->
|
||||||
<MudItem xs="12" md="6">
|
<MudItem xs="12" md="6">
|
||||||
<MudCard>
|
<MudCard>
|
||||||
<MudCardHeader>
|
<MudCardHeader>
|
||||||
@@ -67,17 +93,20 @@
|
|||||||
<MudCardContent>
|
<MudCardContent>
|
||||||
<MudSimpleTable Dense="true">
|
<MudSimpleTable Dense="true">
|
||||||
<tbody>
|
<tbody>
|
||||||
@if (_userInfo.ParentId.HasValue && _userInfo.ParentId.Value > 0)
|
@if (_userInfo.ParentId.HasValue && _userInfo.ParentId.Value > 0)
|
||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>والد:</strong></td>
|
<td><strong>والد:</strong></td>
|
||||||
<td>
|
<td>
|
||||||
<MudButton Size="Size.Small"
|
<MudStack Spacing="1">
|
||||||
Variant="Variant.Text"
|
<MudButton Size="Size.Small"
|
||||||
Color="Color.Primary"
|
Variant="Variant.Text"
|
||||||
OnClick="@(() => NavigateToUser(_userInfo.ParentId))">
|
Color="Color.Primary"
|
||||||
@_userInfo.ParentName (ID: @_userInfo.ParentId)
|
OnClick="@(() => NavigateToUser(_userInfo.ParentId))">
|
||||||
</MudButton>
|
@_userInfo.ParentName (ID: @_userInfo.ParentId)
|
||||||
|
</MudButton>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">@_userInfo.ParentMobile</MudText>
|
||||||
|
</MudStack>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@@ -86,12 +115,21 @@
|
|||||||
<td>
|
<td>
|
||||||
@if (_userInfo.LeftChildId > 0)
|
@if (_userInfo.LeftChildId > 0)
|
||||||
{
|
{
|
||||||
<MudButton Size="Size.Small"
|
<MudStack Spacing="1">
|
||||||
Variant="Variant.Text"
|
<MudButton Size="Size.Small"
|
||||||
Color="Color.Success"
|
Variant="Variant.Text"
|
||||||
OnClick="@(() => NavigateToUser(_userInfo.LeftChildId))">
|
Color="Color.Success"
|
||||||
@_userInfo.LeftChildName (ID: @_userInfo.LeftChildId)
|
OnClick="@(() => NavigateToUser(_userInfo.LeftChildId))">
|
||||||
</MudButton>
|
@_userInfo.LeftChildName (ID: @_userInfo.LeftChildId)
|
||||||
|
</MudButton>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">@_userInfo.LeftChildMobile</MudText>
|
||||||
|
@if (_userInfo.LeftChildJoinedAt != null)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||||
|
عضو شده: @PersianDateTime.ConvertToPersianDate(_userInfo.LeftChildJoinedAt.ToDateTime().ToLocalTime())
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -104,12 +142,21 @@
|
|||||||
<td>
|
<td>
|
||||||
@if (_userInfo.RightChildId > 0)
|
@if (_userInfo.RightChildId > 0)
|
||||||
{
|
{
|
||||||
<MudButton Size="Size.Small"
|
<MudStack Spacing="1">
|
||||||
Variant="Variant.Text"
|
<MudButton Size="Size.Small"
|
||||||
Color="Color.Warning"
|
Variant="Variant.Text"
|
||||||
OnClick="@(() => NavigateToUser(_userInfo.RightChildId))">
|
Color="Color.Warning"
|
||||||
@_userInfo.RightChildName (ID: @_userInfo.RightChildId)
|
OnClick="@(() => NavigateToUser(_userInfo.RightChildId))">
|
||||||
</MudButton>
|
@_userInfo.RightChildName (ID: @_userInfo.RightChildId)
|
||||||
|
</MudButton>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">@_userInfo.RightChildMobile</MudText>
|
||||||
|
@if (_userInfo.RightChildJoinedAt != null)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||||
|
عضو شده: @PersianDateTime.ConvertToPersianDate(_userInfo.RightChildJoinedAt.ToDateTime().ToLocalTime())
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -123,21 +170,183 @@
|
|||||||
</MudCard>
|
</MudCard>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
|
<!-- آمار شبکه -->
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
<MudCard>
|
<MudCard>
|
||||||
<MudCardHeader>
|
<MudCardHeader>
|
||||||
<CardHeaderContent>
|
<CardHeaderContent>
|
||||||
<MudText Typo="Typo.h6">آمار شبکه</MudText>
|
<MudText Typo="Typo.h6">آمار کامل شبکه</MudText>
|
||||||
</CardHeaderContent>
|
</CardHeaderContent>
|
||||||
</MudCardHeader>
|
</MudCardHeader>
|
||||||
<MudCardContent>
|
<MudCardContent>
|
||||||
<MudAlert Severity="Severity.Info">
|
<MudGrid>
|
||||||
<MudText>آمار تجمعی شبکه در نسخه بعدی اضافه خواهد شد</MudText>
|
<MudItem xs="12" sm="6" md="3">
|
||||||
</MudAlert>
|
<MudPaper Class="pa-4" Elevation="0">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.AccountTree" Color="Color.Primary" Size="Size.Large" />
|
||||||
|
<MudText Typo="Typo.h4">@_userInfo.TotalNetworkSize</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Secondary">کل اعضای شبکه</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudPaper Class="pa-4" Elevation="0">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.TrendingUp" Color="Color.Success" Size="Size.Large" />
|
||||||
|
<MudText Typo="Typo.h4">@_userInfo.TotalLeftLegMembers</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Secondary">اعضای شاخه چپ</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudPaper Class="pa-4" Elevation="0">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.TrendingDown" Color="Color.Warning" Size="Size.Large" />
|
||||||
|
<MudText Typo="Typo.h4">@_userInfo.TotalRightLegMembers</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Secondary">اعضای شاخه راست</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudPaper Class="pa-4" Elevation="0">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.Layers" Color="Color.Info" Size="Size.Large" />
|
||||||
|
<MudText Typo="Typo.h4">@_userInfo.MaxNetworkDepth</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Secondary">حداکثر عمق شبکه</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudPaper Class="pa-4" Elevation="0">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Large" />
|
||||||
|
<MudText Typo="Typo.h4">@_userInfo.ActiveMembersInNetwork</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Secondary">اعضای فعال (پکیج خریداری)</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudPaper Class="pa-4" Elevation="0">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.Cancel" Color="Color.Error" Size="Size.Large" />
|
||||||
|
<MudText Typo="Typo.h4">@_userInfo.InactiveMembersInNetwork</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Secondary">اعضای غیرفعال</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
</MudCardContent>
|
</MudCardContent>
|
||||||
</MudCard>
|
</MudCard>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
|
<!-- آمار مالی -->
|
||||||
|
<MudItem xs="12" md="6">
|
||||||
|
<MudCard>
|
||||||
|
<MudCardHeader>
|
||||||
|
<CardHeaderContent>
|
||||||
|
<MudText Typo="Typo.h6">آمار مالی و کمیسیون</MudText>
|
||||||
|
</CardHeaderContent>
|
||||||
|
</MudCardHeader>
|
||||||
|
<MudCardContent>
|
||||||
|
<MudSimpleTable Dense="true">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>کل کمیسیون کسب شده:</strong></td>
|
||||||
|
<td>
|
||||||
|
<MudText Typo="Typo.body1" Color="Color.Primary">
|
||||||
|
<strong>@_userInfo.TotalEarnedCommission.ToString("N0") ریال</strong>
|
||||||
|
</MudText>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>کمیسیون پرداخت شده:</strong></td>
|
||||||
|
<td>
|
||||||
|
<MudText Typo="Typo.body1" Color="Color.Success">
|
||||||
|
@_userInfo.TotalPaidCommission.ToString("N0") ریال
|
||||||
|
</MudText>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>کمیسیون در انتظار:</strong></td>
|
||||||
|
<td>
|
||||||
|
<MudText Typo="Typo.body1" Color="Color.Warning">
|
||||||
|
@_userInfo.PendingCommission.ToString("N0") ریال
|
||||||
|
</MudText>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>تعداد بالانس کسب شده:</strong></td>
|
||||||
|
<td>
|
||||||
|
<MudChip T="string" Color="Color.Info" Size="Size.Small">
|
||||||
|
@_userInfo.TotalBalancesEarned بالانس
|
||||||
|
</MudChip>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</MudSimpleTable>
|
||||||
|
</MudCardContent>
|
||||||
|
</MudCard>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<!-- وضعیت پکیج و دایا -->
|
||||||
|
<MudItem xs="12" md="6">
|
||||||
|
<MudCard>
|
||||||
|
<MudCardHeader>
|
||||||
|
<CardHeaderContent>
|
||||||
|
<MudText Typo="Typo.h6">وضعیت پکیج و دایا</MudText>
|
||||||
|
</CardHeaderContent>
|
||||||
|
</MudCardHeader>
|
||||||
|
<MudCardContent>
|
||||||
|
<MudSimpleTable Dense="true">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>پکیج طلایی:</strong></td>
|
||||||
|
<td>
|
||||||
|
@if (_userInfo.HasPurchasedGoldenPackage)
|
||||||
|
{
|
||||||
|
<MudChip T="string" Color="Color.Success" Size="Size.Small" Icon="@Icons.Material.Filled.CheckCircle">
|
||||||
|
خریداری شده
|
||||||
|
</MudChip>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||||
|
روش: @GetPackagePurchaseMethodText(_userInfo.PackagePurchaseMethod)
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudChip T="string" Color="Color.Default" Size="Size.Small">خریداری نشده</MudChip>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>اعتبار دایا:</strong></td>
|
||||||
|
<td>
|
||||||
|
@if (_userInfo.HasReceivedDayaCredit)
|
||||||
|
{
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudChip T="string" Color="Color.Success" Size="Size.Small" Icon="@Icons.Material.Filled.CheckCircle">
|
||||||
|
دریافت شده
|
||||||
|
</MudChip>
|
||||||
|
@if (_userInfo.DayaCreditReceivedAt != null)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||||
|
تاریخ: @PersianDateTime.ConvertToPersianDate(_userInfo.DayaCreditReceivedAt.ToDateTime().ToLocalTime())
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudChip T="string" Color="Color.Default" Size="Size.Small">دریافت نشده</MudChip>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</MudSimpleTable>
|
||||||
|
</MudCardContent>
|
||||||
|
</MudCard>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<!-- عملیات -->
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
<MudCard>
|
<MudCard>
|
||||||
<MudCardHeader>
|
<MudCardHeader>
|
||||||
@@ -155,9 +364,15 @@
|
|||||||
</MudButton>
|
</MudButton>
|
||||||
<MudButton Variant="Variant.Outlined"
|
<MudButton Variant="Variant.Outlined"
|
||||||
Color="Color.Info"
|
Color="Color.Info"
|
||||||
StartIcon="@Icons.Material.Filled.History"
|
StartIcon="@Icons.Material.Filled.Money"
|
||||||
OnClick="ViewHistory">
|
OnClick="@(() => NavigationManager.NavigateTo($"/commission/payouts?userId={UserId}"))">
|
||||||
تاریخچه تغییرات
|
Payout های کاربر
|
||||||
|
</MudButton>
|
||||||
|
<MudButton Variant="Variant.Outlined"
|
||||||
|
Color="Color.Secondary"
|
||||||
|
StartIcon="@Icons.Material.Filled.Refresh"
|
||||||
|
OnClick="LoadUserInfo">
|
||||||
|
بروزرسانی
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudCardContent>
|
</MudCardContent>
|
||||||
@@ -177,6 +392,7 @@
|
|||||||
[Parameter] public long UserId { get; set; }
|
[Parameter] public long UserId { get; set; }
|
||||||
[Inject] public NetworkMembershipContract.NetworkMembershipContractClient NetworkContract { get; set; }
|
[Inject] public NetworkMembershipContract.NetworkMembershipContractClient NetworkContract { get; set; }
|
||||||
[Inject] public NavigationManager NavigationManager { get; set; }
|
[Inject] public NavigationManager NavigationManager { get; set; }
|
||||||
|
[Inject] public BackOffice.Services.IPersianDateTimeService PersianDateTime { get; set; }
|
||||||
|
|
||||||
private GetUserNetworkResponse _userInfo;
|
private GetUserNetworkResponse _userInfo;
|
||||||
private bool _isLoading = true;
|
private bool _isLoading = true;
|
||||||
@@ -217,10 +433,15 @@
|
|||||||
NavigationManager.NavigateTo($"/network/user-info/{parentIdValue.Value}");
|
NavigationManager.NavigateTo($"/network/user-info/{parentIdValue.Value}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ViewHistory()
|
private string GetPackagePurchaseMethodText(int method)
|
||||||
{
|
{
|
||||||
// Future enhancement: Show historical changes (parent changes, status updates, etc.)
|
return method switch
|
||||||
// Requires: Historical tracking table in CMS database
|
{
|
||||||
Snackbar.Add("این قابلیت به زودی اضافه خواهد شد", Severity.Info);
|
0 => "خریداری نشده",
|
||||||
|
1 => "خرید با پول",
|
||||||
|
2 => "خرید با بالانس",
|
||||||
|
3 => "هدیه سیستم",
|
||||||
|
_ => "نامشخص"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@page "/system/configuration"
|
@page "/system/configuration"
|
||||||
@attribute [Authorize(Roles = "Administrator")]
|
@attribute [Authorize(Roles = "Administrator")]
|
||||||
|
|
||||||
@using Foursat.BackOffice.BFF.Configuration.Protobuf
|
@using Foursat.BackOffice.BFF.Configuration.Protos
|
||||||
@using MudBlazor
|
@using MudBlazor
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||||
|
|||||||
@@ -31,11 +31,11 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>آخرین اجرا:</strong></td>
|
<td><strong>آخرین اجرا:</strong></td>
|
||||||
<td>@_lastRunTime.ToString("yyyy/MM/dd HH:mm:ss")</td>
|
<td>@PersianDateTime.ConvertToPersianDateTime(_lastRunTime)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>اجرای بعدی:</strong></td>
|
<td><strong>اجرای بعدی:</strong></td>
|
||||||
<td>@_nextRunTime.ToString("yyyy/MM/dd HH:mm:ss")</td>
|
<td>@PersianDateTime.ConvertToPersianDateTime(_nextRunTime)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>تعداد اجراهای موفق:</strong></td>
|
<td><strong>تعداد اجراهای موفق:</strong></td>
|
||||||
@@ -143,8 +143,8 @@
|
|||||||
<MudTh>پیام خطا</MudTh>
|
<MudTh>پیام خطا</MudTh>
|
||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
<MudTd DataLabel="زمان">@context.ExecutionTime.ToString("yyyy/MM/dd HH:mm:ss")</MudTd>
|
<MudTd DataLabel="زمان">@PersianDateTime.ConvertToPersianDateTime(context.ExecutionTime)</MudTd>
|
||||||
<MudTd DataLabel="هفته">@context.WeekNumber</MudTd>
|
<MudTd DataLabel="هفته">@PersianDateTime.ConvertWeekNumberToPersian(context.WeekNumber)</MudTd>
|
||||||
<MudTd DataLabel="وضعیت">
|
<MudTd DataLabel="وضعیت">
|
||||||
<MudChip T="string"
|
<MudChip T="string"
|
||||||
Color="@(context.Status == "موفق" ? Color.Success : Color.Error)"
|
Color="@(context.Status == "موفق" ? Color.Success : Color.Error)"
|
||||||
@@ -176,7 +176,8 @@
|
|||||||
</MudContainer>
|
</MudContainer>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Inject] public BackOffice.BFF.Commission.Protobuf.CommissionContract.CommissionContractClient CommissionClient { get; set; }
|
[Inject] public Foursat.BackOffice.BFF.Commission.Protos.CommissionContract.CommissionContractClient CommissionClient { get; set; }
|
||||||
|
[Inject] public BackOffice.Services.IPersianDateTimeService PersianDateTime { get; set; }
|
||||||
|
|
||||||
private WorkerStatus _workerStatus = WorkerStatus.Running;
|
private WorkerStatus _workerStatus = WorkerStatus.Running;
|
||||||
private DateTime _lastRunTime = DateTime.Now.AddHours(-2);
|
private DateTime _lastRunTime = DateTime.Now.AddHours(-2);
|
||||||
@@ -191,7 +192,7 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var statusResponse = await CommissionClient.GetWorkerStatusAsync(new BackOffice.BFF.Commission.Protobuf.GetWorkerStatusRequest());
|
var statusResponse = await CommissionClient.GetWorkerStatusAsync(new Foursat.BackOffice.BFF.Commission.Protos.GetWorkerStatusRequest());
|
||||||
_workerStatus = statusResponse.IsRunning ? WorkerStatus.Running : WorkerStatus.Paused;
|
_workerStatus = statusResponse.IsRunning ? WorkerStatus.Running : WorkerStatus.Paused;
|
||||||
_lastRunTime = statusResponse.LastRunAt?.ToDateTime() ?? DateTime.MinValue;
|
_lastRunTime = statusResponse.LastRunAt?.ToDateTime() ?? DateTime.MinValue;
|
||||||
_nextRunTime = statusResponse.NextScheduledRun?.ToDateTime() ?? DateTime.MinValue;
|
_nextRunTime = statusResponse.NextScheduledRun?.ToDateTime() ?? DateTime.MinValue;
|
||||||
@@ -217,7 +218,7 @@
|
|||||||
|
|
||||||
var confirmed = await DialogService.ShowMessageBox(
|
var confirmed = await DialogService.ShowMessageBox(
|
||||||
"اجرای دستی محاسبات",
|
"اجرای دستی محاسبات",
|
||||||
$"آیا از اجرای محاسبات برای هفته {_manualWeekNumber} اطمینان دارید؟",
|
$"آیا از اجرای محاسبات برای هفته {PersianDateTime.ConvertWeekNumberToPersian(_manualWeekNumber)} اطمینان دارید؟",
|
||||||
yesText: "بله، اجرا شود", cancelText: "لغو");
|
yesText: "بله، اجرا شود", cancelText: "لغو");
|
||||||
|
|
||||||
if (confirmed == true)
|
if (confirmed == true)
|
||||||
@@ -225,13 +226,14 @@
|
|||||||
_isProcessing = true;
|
_isProcessing = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var request = new BackOffice.BFF.Commission.Protobuf.TriggerWeeklyCalculationRequest
|
var request = new Foursat.BackOffice.BFF.Commission.Protos.TriggerWeeklyCalculationRequest
|
||||||
{
|
{
|
||||||
WeekNumber = _manualWeekNumber
|
WeekNumber = _manualWeekNumber
|
||||||
};
|
};
|
||||||
await CommissionClient.TriggerWeeklyCalculationAsync(request);
|
await CommissionClient.TriggerWeeklyCalculationAsync(request);
|
||||||
|
|
||||||
Snackbar.Add($"محاسبات هفته {_manualWeekNumber} با موفقیت آغاز شد", Severity.Success);
|
var persianWeek = PersianDateTime.ConvertWeekNumberToPersian(_manualWeekNumber);
|
||||||
|
Snackbar.Add($"محاسبات هفته {persianWeek} با موفقیت آغاز شد", Severity.Success);
|
||||||
_manualWeekNumber = "";
|
_manualWeekNumber = "";
|
||||||
await RefreshLog();
|
await RefreshLog();
|
||||||
}
|
}
|
||||||
@@ -271,7 +273,7 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var request = new BackOffice.BFF.Commission.Protobuf.GetWorkerExecutionLogsRequest
|
var request = new Foursat.BackOffice.BFF.Commission.Protos.GetWorkerExecutionLogsRequest
|
||||||
{
|
{
|
||||||
PageIndex = 1,
|
PageIndex = 1,
|
||||||
PageSize = 20
|
PageSize = 20
|
||||||
|
|||||||
@@ -29,27 +29,27 @@ public class AuthorizationService : IAuthorizationService
|
|||||||
if (cachedPermissions == null || cachedPermissions.Count == 0)
|
if (cachedPermissions == null || cachedPermissions.Count == 0)
|
||||||
{
|
{
|
||||||
// فعلاً بر اساس Role ساده تصمیم میگیریم تا زمانی که BFF Permission API آماده شود
|
// فعلاً بر اساس Role ساده تصمیم میگیریم تا زمانی که BFF Permission API آماده شود
|
||||||
var role = await GetUserRoleAsync();
|
var roles = await GetUserRolesAsync();
|
||||||
if (string.IsNullOrWhiteSpace(role))
|
if (roles == null || roles.Count == 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SuperAdmin: همه دسترسیها
|
// SuperAdmin: همه دسترسیها
|
||||||
if (string.Equals(role, "Administrator", StringComparison.OrdinalIgnoreCase))
|
if (roles.Any(r => string.Equals(r, "Administrator", StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Admin: اجازه دسترسی به بیشتر صفحات مدیریتی
|
// Admin: اجازه دسترسی به بیشتر صفحات مدیریتی
|
||||||
if (string.Equals(role, "Admin", StringComparison.OrdinalIgnoreCase))
|
if (roles.Any(r => string.Equals(r, "Admin", StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
// فعلاً همه permissionهای UI را برای Admin آزاد میکنیم
|
// فعلاً همه permissionهای UI را برای Admin آزاد میکنیم
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inspector: فقط view
|
// Inspector: فقط view
|
||||||
if (string.Equals(role, "Inspector", StringComparison.OrdinalIgnoreCase))
|
if (roles.Any(r => string.Equals(r, "Inspector", StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
return permission.EndsWith(".view", StringComparison.OrdinalIgnoreCase);
|
return permission.EndsWith(".view", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
@@ -61,6 +61,12 @@ public class AuthorizationService : IAuthorizationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string?> GetUserRoleAsync()
|
public async Task<string?> GetUserRoleAsync()
|
||||||
|
{
|
||||||
|
var roles = await GetUserRolesAsync();
|
||||||
|
return roles?.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<string>?> GetUserRolesAsync()
|
||||||
{
|
{
|
||||||
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||||
var user = authState.User;
|
var user = authState.User;
|
||||||
@@ -70,8 +76,8 @@ public class AuthorizationService : IAuthorizationService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var roleClaim = user.FindFirst(ClaimTypes.Role) ?? user.FindFirst("role");
|
var roleClaims = user.FindAll(ClaimTypes.Role).Select(c => c.Value).ToList();
|
||||||
return roleClaim?.Value;
|
return roleClaims.Count > 0 ? roleClaims : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
161
src/BackOffice/Services/PersianDateTimeService.cs
Normal file
161
src/BackOffice/Services/PersianDateTimeService.cs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace BackOffice.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// سرویس تبدیل و نمایش تاریخ شمسی
|
||||||
|
/// </summary>
|
||||||
|
public interface IPersianDateTimeService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// دریافت شماره هفته فعلی به فرمت شمسی
|
||||||
|
/// فرمت: "1404-W23"
|
||||||
|
/// </summary>
|
||||||
|
string GetCurrentWeekNumber();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تبدیل شماره هفته میلادی به شمسی
|
||||||
|
/// ورودی: "2025-W48" (میلادی)
|
||||||
|
/// خروجی: "1404-W23" (شمسی)
|
||||||
|
/// </summary>
|
||||||
|
string ConvertWeekNumberToPersian(string gregorianWeekNumber);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تبدیل تاریخ میلادی به شمسی با فرمت کامل
|
||||||
|
/// خروجی: "1404/09/21"
|
||||||
|
/// </summary>
|
||||||
|
string ConvertToPersianDate(DateTime dateTime);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تبدیل تاریخ میلادی به شمسی با فرمت کامل + ساعت
|
||||||
|
/// خروجی: "1404/09/21 - 14:30"
|
||||||
|
/// </summary>
|
||||||
|
string ConvertToPersianDateTime(DateTime dateTime);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نمایش بازه هفته به شمسی
|
||||||
|
/// خروجی: "شنبه 1404/09/15 تا جمعه 1404/09/21"
|
||||||
|
/// </summary>
|
||||||
|
string GetWeekRangeDisplay(string gregorianWeekNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PersianDateTimeService : IPersianDateTimeService
|
||||||
|
{
|
||||||
|
private readonly PersianCalendar _persianCalendar = new();
|
||||||
|
private static readonly string[] PersianDayNames =
|
||||||
|
{ "یکشنبه", "دوشنبه", "سهشنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه" };
|
||||||
|
|
||||||
|
public string GetCurrentWeekNumber()
|
||||||
|
{
|
||||||
|
var now = DateTime.Now;
|
||||||
|
return GetPersianWeekNumber(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ConvertWeekNumberToPersian(string gregorianWeekNumber)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Parse: "2025-W48"
|
||||||
|
var parts = gregorianWeekNumber.Split('-');
|
||||||
|
if (parts.Length != 2 || !parts[1].StartsWith("W"))
|
||||||
|
return gregorianWeekNumber;
|
||||||
|
|
||||||
|
var year = int.Parse(parts[0]);
|
||||||
|
var week = int.Parse(parts[1].Replace("W", ""));
|
||||||
|
|
||||||
|
// محاسبه تاریخ میلادی این هفته
|
||||||
|
var jan1 = new DateTime(year, 1, 1);
|
||||||
|
var jan1DayOfWeek = (int)jan1.DayOfWeek;
|
||||||
|
var daysToFirstSaturday = jan1DayOfWeek == 6 ? 0 : (6 - jan1DayOfWeek + 7) % 7;
|
||||||
|
var firstSaturday = jan1.AddDays(daysToFirstSaturday);
|
||||||
|
var weekStart = firstSaturday.AddDays((week - 1) * 7);
|
||||||
|
|
||||||
|
// تبدیل به شمسی
|
||||||
|
return GetPersianWeekNumber(weekStart);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return gregorianWeekNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ConvertToPersianDate(DateTime dateTime)
|
||||||
|
{
|
||||||
|
var year = _persianCalendar.GetYear(dateTime);
|
||||||
|
var month = _persianCalendar.GetMonth(dateTime);
|
||||||
|
var day = _persianCalendar.GetDayOfMonth(dateTime);
|
||||||
|
|
||||||
|
return $"{year:0000}/{month:00}/{day:00}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ConvertToPersianDateTime(DateTime dateTime)
|
||||||
|
{
|
||||||
|
var persianDate = ConvertToPersianDate(dateTime);
|
||||||
|
var time = dateTime.ToString("HH:mm");
|
||||||
|
|
||||||
|
return $"{persianDate} - {time}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetWeekRangeDisplay(string gregorianWeekNumber)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (startDate, endDate) = ParseGregorianWeekNumber(gregorianWeekNumber);
|
||||||
|
|
||||||
|
var startPersian = ConvertToPersianDate(startDate);
|
||||||
|
var endPersian = ConvertToPersianDate(endDate);
|
||||||
|
|
||||||
|
var startDay = PersianDayNames[(int)startDate.DayOfWeek];
|
||||||
|
var endDay = PersianDayNames[(int)endDate.DayOfWeek];
|
||||||
|
|
||||||
|
return $"{startDay} {startPersian} تا {endDay} {endPersian}";
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return gregorianWeekNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// محاسبه شماره هفته شمسی (شنبه محور)
|
||||||
|
/// </summary>
|
||||||
|
private string GetPersianWeekNumber(DateTime dateTime)
|
||||||
|
{
|
||||||
|
var persianYear = _persianCalendar.GetYear(dateTime);
|
||||||
|
var dayOfYear = _persianCalendar.GetDayOfYear(dateTime);
|
||||||
|
|
||||||
|
// محاسبه اولین روز سال شمسی
|
||||||
|
var firstDayOfYear = _persianCalendar.ToDateTime(persianYear, 1, 1, 0, 0, 0, 0);
|
||||||
|
var firstDayOfWeek = (int)firstDayOfYear.DayOfWeek;
|
||||||
|
|
||||||
|
// محاسبه تعداد روزها تا اولین شنبه
|
||||||
|
// شنبه = 6, یکشنبه = 0, دوشنبه = 1, ...
|
||||||
|
var daysToFirstSaturday = firstDayOfWeek == 6 ? 0 : (6 - firstDayOfWeek + 7) % 7;
|
||||||
|
|
||||||
|
// محاسبه شماره هفته
|
||||||
|
var adjustedDayOfYear = dayOfYear + daysToFirstSaturday - 1;
|
||||||
|
var weekNumber = (adjustedDayOfYear / 7) + 1;
|
||||||
|
|
||||||
|
return $"{persianYear}-W{weekNumber:D2}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تبدیل شماره هفته میلادی به بازه تاریخی
|
||||||
|
/// </summary>
|
||||||
|
private (DateTime startDate, DateTime endDate) ParseGregorianWeekNumber(string weekNumber)
|
||||||
|
{
|
||||||
|
var parts = weekNumber.Split('-');
|
||||||
|
var year = int.Parse(parts[0]);
|
||||||
|
var week = int.Parse(parts[1].Replace("W", ""));
|
||||||
|
|
||||||
|
var jan1 = new DateTime(year, 1, 1);
|
||||||
|
var jan1DayOfWeek = (int)jan1.DayOfWeek;
|
||||||
|
var daysToFirstSaturday = jan1DayOfWeek == 6 ? 0 : (6 - jan1DayOfWeek + 7) % 7;
|
||||||
|
var firstSaturday = jan1.AddDays(daysToFirstSaturday);
|
||||||
|
|
||||||
|
var weekStart = firstSaturday.AddDays((week - 1) * 7);
|
||||||
|
var weekEnd = weekStart.AddDays(6);
|
||||||
|
|
||||||
|
return (weekStart, weekEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
// "GwUrl": "https://bogw.kbs1.ir",
|
// "GwUrl": "https://bogw.kbs1.ir",
|
||||||
"GwUrl": "https://backoffice-bff.foursat.afrino.co",
|
"GwUrl": "https://backoffice-bff.foursat.afrino.co",
|
||||||
|
// "GwUrl": "https://localhost:6468",
|
||||||
"Authentication": {
|
"Authentication": {
|
||||||
//"Authority": "https://localhost:5001",
|
//"Authority": "https://localhost:5001",
|
||||||
"Authority": "https://ids.afrino.co/",
|
"Authority": "https://ids.afrino.co/",
|
||||||
|
|||||||
@@ -34,8 +34,10 @@
|
|||||||
<a class="dismiss">🗙</a>
|
<a class="dismiss">🗙</a>
|
||||||
</div>
|
</div>
|
||||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||||
|
<script src="/js/d3.v7.min.js"></script>
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
<script src="/js/quill.js"></script>
|
<script src="/js/quill.js"></script>
|
||||||
|
<script src="/js/network-tree.js"></script>
|
||||||
<script src="_content/Tizzani.MudBlazor.HtmlEditor/quill-blot-formatter.min.js"></script> <!-- optional; for image resize -->
|
<script src="_content/Tizzani.MudBlazor.HtmlEditor/quill-blot-formatter.min.js"></script> <!-- optional; for image resize -->
|
||||||
<script src="_framework/blazor.webassembly.js"></script>
|
<script src="_framework/blazor.webassembly.js"></script>
|
||||||
<script>navigator.serviceWorker.register('service-worker.js');</script>
|
<script>navigator.serviceWorker.register('service-worker.js');</script>
|
||||||
|
|||||||
2
src/BackOffice/wwwroot/js/d3.v7.min.js
vendored
Normal file
2
src/BackOffice/wwwroot/js/d3.v7.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
210
src/BackOffice/wwwroot/js/network-tree.js
Normal file
210
src/BackOffice/wwwroot/js/network-tree.js
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
// Network Tree Visualization using D3.js
|
||||||
|
window.NetworkTreeViewer = {
|
||||||
|
instance: null,
|
||||||
|
dotNetRef: null,
|
||||||
|
|
||||||
|
setDotNetReference: function(dotNetReference) {
|
||||||
|
this.dotNetRef = dotNetReference;
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function(elementId, data) {
|
||||||
|
const container = document.getElementById(elementId);
|
||||||
|
if (!container) {
|
||||||
|
console.error('Container not found:', elementId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear previous content
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
// Set dimensions - responsive to container
|
||||||
|
const containerRect = container.getBoundingClientRect();
|
||||||
|
const width = containerRect.width || 1200;
|
||||||
|
const height = Math.max(containerRect.height, 800);
|
||||||
|
|
||||||
|
// Create SVG with viewBox for responsiveness
|
||||||
|
const svg = d3.select(`#${elementId}`)
|
||||||
|
.append('svg')
|
||||||
|
.attr('width', '100%')
|
||||||
|
.attr('height', '100%')
|
||||||
|
.attr('viewBox', `0 0 ${width} ${height}`)
|
||||||
|
.attr('preserveAspectRatio', 'xMidYMid meet')
|
||||||
|
.style('background-color', '#fafafa')
|
||||||
|
.call(d3.zoom()
|
||||||
|
.scaleExtent([0.1, 3])
|
||||||
|
.on('zoom', (event) => {
|
||||||
|
g.attr('transform', event.transform);
|
||||||
|
}));
|
||||||
|
|
||||||
|
const g = svg.append('g')
|
||||||
|
.attr('transform', 'translate(40,0)');
|
||||||
|
|
||||||
|
// Create tree layout
|
||||||
|
const tree = d3.tree()
|
||||||
|
.size([height - 100, width - 300])
|
||||||
|
.separation((a, b) => a.parent === b.parent ? 1.5 : 2);
|
||||||
|
|
||||||
|
// Convert flat data to hierarchy
|
||||||
|
const root = this.buildHierarchy(data);
|
||||||
|
|
||||||
|
if (!root) {
|
||||||
|
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #666;">درخت خالی است</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate tree layout
|
||||||
|
const treeData = tree(root);
|
||||||
|
|
||||||
|
// Add zoom reset button
|
||||||
|
const resetButton = svg.append('g')
|
||||||
|
.attr('transform', 'translate(20, 20)')
|
||||||
|
.style('cursor', 'pointer')
|
||||||
|
.on('click', () => {
|
||||||
|
svg.transition()
|
||||||
|
.duration(750)
|
||||||
|
.call(d3.zoom().transform, d3.zoomIdentity);
|
||||||
|
});
|
||||||
|
|
||||||
|
resetButton.append('rect')
|
||||||
|
.attr('width', 80)
|
||||||
|
.attr('height', 30)
|
||||||
|
.attr('rx', 5)
|
||||||
|
.style('fill', '#2196F3')
|
||||||
|
.style('opacity', 0.8);
|
||||||
|
|
||||||
|
resetButton.append('text')
|
||||||
|
.attr('x', 40)
|
||||||
|
.attr('y', 20)
|
||||||
|
.attr('text-anchor', 'middle')
|
||||||
|
.style('fill', 'white')
|
||||||
|
.style('font-size', '12px')
|
||||||
|
.text('بازنشانی');
|
||||||
|
|
||||||
|
// Create links (edges)
|
||||||
|
const link = g.selectAll('.link')
|
||||||
|
.data(treeData.links())
|
||||||
|
.enter().append('path')
|
||||||
|
.attr('class', 'link')
|
||||||
|
.attr('d', d3.linkHorizontal()
|
||||||
|
.x(d => d.y)
|
||||||
|
.y(d => d.x))
|
||||||
|
.style('fill', 'none')
|
||||||
|
.style('stroke', '#ccc')
|
||||||
|
.style('stroke-width', 2);
|
||||||
|
|
||||||
|
// Create nodes
|
||||||
|
const node = g.selectAll('.node')
|
||||||
|
.data(treeData.descendants())
|
||||||
|
.enter().append('g')
|
||||||
|
.attr('class', 'node')
|
||||||
|
.attr('transform', d => `translate(${d.y},${d.x})`);
|
||||||
|
|
||||||
|
// Add circles for nodes
|
||||||
|
node.append('circle')
|
||||||
|
.attr('r', 8)
|
||||||
|
.style('fill', d => d.data.isActive ? '#4caf50' : '#f44336')
|
||||||
|
.style('stroke', '#fff')
|
||||||
|
.style('stroke-width', 2)
|
||||||
|
.style('cursor', 'pointer')
|
||||||
|
.on('click', (event, d) => {
|
||||||
|
if (window.NetworkTreeViewer.dotNetRef) {
|
||||||
|
window.NetworkTreeViewer.dotNetRef.invokeMethodAsync('OnNodeClicked', d.data.userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add user info labels
|
||||||
|
node.append('text')
|
||||||
|
.attr('dy', -15)
|
||||||
|
.attr('text-anchor', 'middle')
|
||||||
|
.style('font-size', '12px')
|
||||||
|
.style('font-weight', 'bold')
|
||||||
|
.style('fill', '#333')
|
||||||
|
.text(d => d.data.userName || `User ${d.data.userId}`);
|
||||||
|
|
||||||
|
// Add level info
|
||||||
|
node.append('text')
|
||||||
|
.attr('dy', 20)
|
||||||
|
.attr('text-anchor', 'middle')
|
||||||
|
.style('font-size', '10px')
|
||||||
|
.style('fill', '#666')
|
||||||
|
.text(d => `سطح ${d.data.level}`);
|
||||||
|
|
||||||
|
// Add leg indicator (Left/Right)
|
||||||
|
node.append('text')
|
||||||
|
.attr('dy', 32)
|
||||||
|
.attr('text-anchor', 'middle')
|
||||||
|
.style('font-size', '9px')
|
||||||
|
.style('fill', d => d.data.networkLeg === 0 ? '#4caf50' : '#ff9800')
|
||||||
|
.text(d => d.data.networkLeg === 0 ? 'چپ' : 'راست');
|
||||||
|
|
||||||
|
// Add legend
|
||||||
|
const legend = svg.append('g')
|
||||||
|
.attr('transform', `translate(${width - 150}, 20)`);
|
||||||
|
|
||||||
|
legend.append('circle')
|
||||||
|
.attr('cx', 0)
|
||||||
|
.attr('cy', 0)
|
||||||
|
.attr('r', 6)
|
||||||
|
.style('fill', '#4caf50');
|
||||||
|
|
||||||
|
legend.append('text')
|
||||||
|
.attr('x', 12)
|
||||||
|
.attr('y', 4)
|
||||||
|
.style('font-size', '12px')
|
||||||
|
.text('فعال');
|
||||||
|
|
||||||
|
legend.append('circle')
|
||||||
|
.attr('cx', 0)
|
||||||
|
.attr('cy', 25)
|
||||||
|
.attr('r', 6)
|
||||||
|
.style('fill', '#f44336');
|
||||||
|
|
||||||
|
legend.append('text')
|
||||||
|
.attr('x', 12)
|
||||||
|
.attr('y', 29)
|
||||||
|
.style('font-size', '12px')
|
||||||
|
.text('غیرفعال');
|
||||||
|
},
|
||||||
|
|
||||||
|
buildHierarchy: function(nodes) {
|
||||||
|
if (!nodes || nodes.length === 0) return null;
|
||||||
|
|
||||||
|
// Find root (parent with no parent or lowest level)
|
||||||
|
const root = nodes.find(n => !n.parentId || n.parentId === 0) || nodes[0];
|
||||||
|
|
||||||
|
const nodeMap = new Map();
|
||||||
|
nodes.forEach(node => {
|
||||||
|
nodeMap.set(node.userId, {
|
||||||
|
userId: node.userId,
|
||||||
|
userName: node.userName,
|
||||||
|
parentId: node.parentId,
|
||||||
|
level: node.networkLevel,
|
||||||
|
networkLeg: node.networkLeg,
|
||||||
|
isActive: node.isActive,
|
||||||
|
children: []
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build parent-child relationships
|
||||||
|
nodes.forEach(node => {
|
||||||
|
if (node.parentId && node.parentId !== node.userId) {
|
||||||
|
const parent = nodeMap.get(node.parentId);
|
||||||
|
const child = nodeMap.get(node.userId);
|
||||||
|
if (parent && child) {
|
||||||
|
parent.children.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return root as D3 hierarchy
|
||||||
|
const rootNode = nodeMap.get(root.userId);
|
||||||
|
return d3.hierarchy(rootNode);
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function(elementId) {
|
||||||
|
const container = document.getElementById(elementId);
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user